diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..5e3347d --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,3 @@ +extends: + - ./lint/eslintrc-gjs.yml + - ./lint/eslintrc-shell.yml diff --git a/appIndicator.js b/appIndicator.js index 0097211..592f827 100644 --- a/appIndicator.js +++ b/appIndicator.js @@ -14,18 +14,18 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -const Clutter = imports.gi.Clutter -const Cogl = imports.gi.Cogl -const GdkPixbuf = imports.gi.GdkPixbuf -const Gio = imports.gi.Gio -const GLib = imports.gi.GLib -const GObject = imports.gi.GObject -const Gtk = imports.gi.Gtk -const St = imports.gi.St -const Shell = imports.gi.Shell +const Clutter = imports.gi.Clutter; +const Cogl = imports.gi.Cogl; +const GdkPixbuf = imports.gi.GdkPixbuf; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Gtk = imports.gi.Gtk; +const St = imports.gi.St; +const Shell = imports.gi.Shell; const Extension = imports.misc.extensionUtils.getCurrentExtension(); -const Signals = imports.signals +const Signals = imports.signals; const DBusMenu = Extension.imports.dbusMenu; const IconCache = Extension.imports.iconCache; @@ -45,13 +45,13 @@ APPLICATION: 'ApplicationStatus', COMMUNICATIONS: 'Communications', SYSTEM: 'SystemServices', - HARDWARE: 'Hardware' + HARDWARE: 'Hardware', }; var SNIStatus = { PASSIVE: 'Passive', ACTIVE: 'Active', - NEEDS_ATTENTION: 'NeedsAttention' + NEEDS_ATTENTION: 'NeedsAttention', }; const SNIconType = { @@ -67,26 +67,26 @@ var AppIndicator = class AppIndicators_AppIndicator { constructor(service, bus_name, object) { - this.busName = bus_name - this._uniqueId = bus_name + object + this.busName = bus_name; + this._uniqueId = bus_name + object; this._accumulatedSignals = new Set(); - let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem) - - //HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need + let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem); + + // HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need // to specify G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES this._cancellable = new Gio.Cancellable(); this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session, - g_interface_name: interface_info.name, - g_interface_info: interface_info, - g_name: bus_name, - g_object_path: object, - g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES }) + g_interface_name: interface_info.name, + g_interface_info: interface_info, + g_name: bus_name, + g_object_path: object, + g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES }); this._setupProxy(); - Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged') - Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal) - Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged') + Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged'); + Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal); + Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged'); if (service !== bus_name && service.match(Util.BUS_ADDRESS_REGEX)) { this._uniqueId = service; @@ -164,7 +164,7 @@ get: () => { const v = this._proxy.get_cached_property(name); return v ? v.deep_unpack() : null; - } + }, }); } @@ -189,19 +189,19 @@ let prop = null; if (signal.startsWith('New')) - prop = signal.substr(3) + prop = signal.substr(3); else if (signal.startsWith('XAyatanaNew')) - prop = 'XAyatana' + signal.substr(11) + prop = `XAyatana${signal.substr(11)}`; if (!prop) return; [prop, `${prop}Name`, `${prop}Pixmap`].filter(p => this._proxyPropertyList.includes(p)).forEach(p => - Util.refreshPropertyOnProxy(this._proxy, p, { - skipEqualityCheck: p.endsWith('Pixmap'), - }) - ); + Util.refreshPropertyOnProxy(this._proxy, p, { + skipEqualityCheck: p.endsWith('Pixmap'), + }), + ); } async _onProxySignal(_proxy, _sender, signal, _params) { @@ -214,29 +214,34 @@ GLib.PRIORITY_DEFAULT_IDLE, MAX_UPDATE_FREQUENCY, this._cancellable); try { await this._signalsAccumulator; - this._accumulatedSignals.forEach((s) => this._translateNewSignals(s)); + this._accumulatedSignals.forEach(s => this._translateNewSignals(s)); this._accumulatedSignals.clear(); } finally { delete this._signalsAccumulator; } } - //public property getters + // public property getters get title() { return this._proxy.Title; } + get id() { return this._proxy.Id; } + get uniqueId() { return this._uniqueId; } + get status() { return this._proxy.Status; } + get label() { return this._proxy.XAyatanaLabel; } + get menuPath() { if (this._proxy.Menu == '/NO_DBUSMENU') return null; @@ -248,24 +253,24 @@ return [ this._proxy.AttentionIconName, this._proxy.AttentionIconPixmap, - this._proxy.IconThemePath - ] + this._proxy.IconThemePath, + ]; } get icon() { return [ this._proxy.IconName, this._proxy.IconPixmap, - this._proxy.IconThemePath - ] + this._proxy.IconThemePath, + ]; } get overlayIcon() { return [ this._proxy.OverlayIconName, this._proxy.OverlayIconPixmap, - this._proxy.IconThemePath - ] + this._proxy.IconThemePath, + ]; } get hasNameOwner() { @@ -281,39 +286,39 @@ let props = Object.keys(changed.unpack()); let signalsToEmit = new Set(); - props.forEach((property) => { + props.forEach(property => { // some property changes require updates on our part, // a few need to be passed down to the displaying code // all these can mean that the icon has to be changed if (property == 'Status' || property.startsWith('Icon') || - property.startsWith('AttentionIcon')) { - signalsToEmit.add('icon') - } + property.startsWith('AttentionIcon')) + signalsToEmit.add('icon'); + // same for overlays if (property.startsWith('OverlayIcon')) - signalsToEmit.add('overlay-icon') + signalsToEmit.add('overlay-icon'); // this may make all of our icons invalid if (property == 'IconThemePath') { - signalsToEmit.add('icon') - signalsToEmit.add('overlay-icon') + signalsToEmit.add('icon'); + signalsToEmit.add('overlay-icon'); } // the label will be handled elsewhere if (property == 'XAyatanaLabel') - signalsToEmit.add('label') + signalsToEmit.add('label'); if (property == 'Menu') { if (!this._checkIfReady() && this.isReady) - signalsToEmit.add('menu') + signalsToEmit.add('menu'); } // status updates may cause the indicator to be hidden if (property == 'Status') - signalsToEmit.add('status') + signalsToEmit.add('status'); }); signalsToEmit.forEach(s => this.emit(s)); @@ -324,9 +329,9 @@ } destroy() { - this.emit('destroy') - - this.disconnectAll() + this.emit('destroy'); + + this.disconnectAll(); this._cancellable.cancel(); this._nameWatcher && this._nameWatcher.destroy(); Util.cancelRefreshPropertyOnProxy(this._proxy); @@ -339,19 +344,19 @@ // nor can we call any X11 functions. Luckily, the Activate method usually works fine. // parameters are "an hint to the item where to show eventual windows" [sic] // ... and don't seem to have any effect. - this._proxy.ActivateRemote(0, 0) + this._proxy.ActivateRemote(0, 0); } secondaryActivate() { - this._proxy.SecondaryActivateRemote(0, 0) + this._proxy.SecondaryActivateRemote(0, 0); } scroll(dx, dy) { if (dx != 0) - this._proxy.ScrollRemote(Math.floor(dx), 'horizontal') + this._proxy.ScrollRemote(Math.floor(dx), 'horizontal'); if (dy != 0) - this._proxy.ScrollRemote(Math.floor(dy), 'vertical') + this._proxy.ScrollRemote(Math.floor(dy), 'vertical'); } }; Signals.addSignalMethods(AppIndicator.prototype); @@ -373,17 +378,17 @@ let themeContext = St.ThemeContext.get_for_stage(global.stage); this.height = icon_size * themeContext.scale_factor; - this._indicator = indicator - this._iconSize = icon_size - this._iconCache = new IconCache.IconCache() + this._indicator = indicator; + this._iconSize = icon_size; + this._iconCache = new IconCache.IconCache(); this._cancellable = new Gio.Cancellable(); this._loadingIcons = new Set(); - Util.connectSmart(this._indicator, 'icon', this, '_updateIcon') - Util.connectSmart(this._indicator, 'overlay-icon', this, '_updateOverlayIcon') - Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon') - - Util.connectSmart(themeContext, 'notify::scale-factor', this, (tc) => { + Util.connectSmart(this._indicator, 'icon', this, '_updateIcon'); + Util.connectSmart(this._indicator, 'overlay-icon', this, '_updateOverlayIcon'); + Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon'); + + Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => { this.height = icon_size * tc.scale_factor; this._invalidateIcon(); }); @@ -391,12 +396,12 @@ Util.connectSmart(this._indicator, 'ready', this, () => { this._updateIconClass(); this._invalidateIcon(); - }) - - Util.connectSmart(Gtk.IconTheme.get_default(), 'changed', this, '_invalidateIcon') + }); + + Util.connectSmart(Gtk.IconTheme.get_default(), 'changed', this, '_invalidateIcon'); if (indicator.isReady) - this._invalidateIcon() + this._invalidateIcon(); this.connect('destroy', () => { this._iconCache.destroy(); @@ -420,7 +425,7 @@ // Will look the icon up in the cache, if it's found // it will return it. Otherwise, it will create it and cache it. async _cacheOrCreateIconByName(iconSize, iconName, themePath) { - let {scale_factor} = St.ThemeContext.get_for_stage(global.stage); + let { scale_factor } = St.ThemeContext.get_for_stage(global.stage); let id = `${iconName}@${iconSize * scale_factor}${themePath || ''}`; let gicon = this._iconCache.get(id); @@ -498,7 +503,7 @@ } else { this.icon_size = this._iconSize; return new Gio.FileIcon({ - file: Gio.File.new_for_path(path) + file: Gio.File.new_for_path(path), }); } } catch (e) { @@ -510,15 +515,15 @@ _getIconInfo(name, themePath, size, scale) { let path = null; - if (name && name[0] == "/") { - //HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it. + if (name && name[0] == '/') { + // HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it. path = name; } else if (name) { // we manually look up the icon instead of letting st.icon do it for us // this allows us to sneak in an indicator provided search path and to avoid ugly upscaled icons // indicator-application looks up a special "panel" variant, we just replicate that here - name = name + "-panel"; + name += '-panel'; // icon info as returned by the lookup let iconInfo = null; @@ -527,7 +532,7 @@ let icon_theme = null; if (themePath) { icon_theme = new Gtk.IconTheme(); - Gtk.IconTheme.get_default().get_search_path().forEach((path) => { + Gtk.IconTheme.get_default().get_search_path().forEach(path => { icon_theme.append_search_path(path); }); icon_theme.append_search_path(themePath); @@ -566,7 +571,7 @@ await new PromiseUtils.IdlePromise(GLib.PRIORITY_LOW, cancellable); for (let j = start; j < end; j += 4) { - let srcAlpha = src[j] + let srcAlpha = src[j]; dest[j] = src[j + 1]; /* red */ dest[j + 1] = src[j + 2]; /* green */ @@ -584,8 +589,8 @@ } async _createIconFromPixmap(iconSize, iconPixmapArray) { - let {scale_factor} = St.ThemeContext.get_for_stage(global.stage); - iconSize = iconSize * scale_factor + let { scale_factor } = St.ThemeContext.get_for_stage(global.stage); + iconSize *= scale_factor; // the pixmap actually is an array of pixmaps with different sizes // we use the one that is smaller or equal the iconSize @@ -595,21 +600,21 @@ const sortedIconPixmapArray = iconPixmapArray.sort((pixmapA, pixmapB) => { // we sort smallest to biggest - const areaA = pixmapA[0] * pixmapA[1] - const areaB = pixmapB[0] * pixmapB[1] - - return areaA - areaB - }) + const areaA = pixmapA[0] * pixmapA[1]; + const areaB = pixmapB[0] * pixmapB[1]; + + return areaA - areaB; + }); const qualifiedIconPixmapArray = sortedIconPixmapArray.filter(pixmap => // we prefer any pixmap that is equal or bigger than our requested size - pixmap[0] >= iconSize && pixmap[1] >= iconSize) - - const iconPixmap = qualifiedIconPixmapArray.length > 0 ? - qualifiedIconPixmapArray[0] : sortedIconPixmapArray.pop() - - const [ width, height, bytes ] = iconPixmap - const rowStride = width * 4 // hopefully this is correct + pixmap[0] >= iconSize && pixmap[1] >= iconSize); + + const iconPixmap = qualifiedIconPixmapArray.length > 0 + ? qualifiedIconPixmapArray[0] : sortedIconPixmapArray.pop(); + + const [width, height, bytes] = iconPixmap; + const rowStride = width * 4; // hopefully this is correct const id = `__PIXMAP_ICON_${width}x${height}`; if (this._loadingIcons.has(id)) { @@ -648,21 +653,19 @@ this.gicon = new Gio.EmblemedIcon({ gicon }); if (!(gicon instanceof GdkPixbuf.Pixbuf)) - gicon.inUse = (this.gicon.get_icon() == gicon); + gicon.inUse = this.gicon.get_icon() == gicon; } else { this.gicon = null; Util.Logger.critical(`unable to update icon for ${this._indicator.id}`); } + } else if (gicon) { + this._emblem = new Gio.Emblem({ icon: gicon }); + + if (!(gicon instanceof GdkPixbuf.Pixbuf)) + gicon.inUse = true; } else { - if (gicon) { - this._emblem = new Gio.Emblem({ icon: gicon }); - - if (!(gicon instanceof GdkPixbuf.Pixbuf)) - gicon.inUse = true; - } else { - this._emblem = null; - Util.Logger.debug(`unable to update icon emblem for ${this._indicator.id}`); - } + this._emblem = null; + Util.Logger.debug(`unable to update icon emblem for ${this._indicator.id}`); } if (this.gicon) { @@ -677,15 +680,15 @@ async _updateIconByType(iconType, iconSize) { let icon; switch (iconType) { - case SNIconType.ATTENTION: - icon = this._indicator.attentionIcon; - break; - case SNIconType.NORMAL: - icon = this._indicator.icon; - break; - case SNIconType.OVERLAY: - icon = this._indicator.overlayIcon; - break; + case SNIconType.ATTENTION: + icon = this._indicator.attentionIcon; + break; + case SNIconType.NORMAL: + icon = this._indicator.icon; + break; + case SNIconType.OVERLAY: + icon = this._indicator.overlayIcon; + break; } const [name, pixmap, theme] = icon; @@ -714,12 +717,12 @@ let { gicon } = this.gicon; if (gicon.inUse) - gicon.inUse = false + gicon.inUse = false; } // we might need to use the AttentionIcon*, which have precedence over the normal icons - let iconType = this._indicator.status == SNIStatus.NEEDS_ATTENTION ? - SNIconType.ATTENTION : SNIconType.NORMAL; + let iconType = this._indicator.status == SNIStatus.NEEDS_ATTENTION + ? SNIconType.ATTENTION : SNIconType.NORMAL; this._updateIconByType(iconType, this._iconSize); } @@ -735,17 +738,17 @@ // KDE hardcodes the overlay icon size to 10px (normal icon size 16px) // we approximate that ratio for other sizes, too. // our algorithms will always pick a smaller one instead of stretching it. - let iconSize = Math.floor(this._iconSize / 1.6) + let iconSize = Math.floor(this._iconSize / 1.6); this._updateIconByType(SNIconType.OVERLAY, iconSize); } // called when the icon theme changes _invalidateIcon() { - this._iconCache.clear() + this._iconCache.clear(); this._cancelLoading(); - this._updateIcon() - this._updateOverlayIcon() + this._updateIcon(); + this._updateOverlayIcon(); } }); diff --git a/dbusMenu.js b/dbusMenu.js index bc5840b..ad485e2 100644 --- a/dbusMenu.js +++ b/dbusMenu.js @@ -13,25 +13,25 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -const Atk = imports.gi.Atk -const Clutter = imports.gi.Clutter -const Gio = imports.gi.Gio -const GLib = imports.gi.GLib -const GdkPixbuf = imports.gi.GdkPixbuf -const PopupMenu = imports.ui.popupMenu -const Signals = imports.signals -const St = imports.gi.St - -const Extension = imports.misc.extensionUtils.getCurrentExtension() - -const DBusInterfaces = Extension.imports.interfaces +const Atk = imports.gi.Atk; +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const GdkPixbuf = imports.gi.GdkPixbuf; +const PopupMenu = imports.ui.popupMenu; +const Signals = imports.signals; +const St = imports.gi.St; + +const Extension = imports.misc.extensionUtils.getCurrentExtension(); + +const DBusInterfaces = Extension.imports.interfaces; const PromiseUtils = Extension.imports.promiseUtils; -const Util = Extension.imports.util - -////////////////////////////////////////////////////////////////////////// +const Util = Extension.imports.util; + +// //////////////////////////////////////////////////////////////////////// // PART ONE: "ViewModel" backend implementation. // Both code and design are inspired by libdbusmenu -////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////// /** * Saves menu property values and handles type checking and defaults @@ -42,15 +42,15 @@ this._props = new Map(); if (initial_properties) { - for (let i in initial_properties) { - this.set(i, initial_properties[i]) - } + for (let i in initial_properties) + this.set(i, initial_properties[i]); + } } set(name, value) { if (name in PropertyStore.MandatedTypes && value && !value.is_of_type(PropertyStore.MandatedTypes[name])) - Util.Logger.warn("Cannot set property "+name+": type mismatch!") + Util.Logger.warn(`Cannot set property ${name}: type mismatch!`); else if (value) this._props.set(name, value); else @@ -62,32 +62,32 @@ if (prop) return prop; else if (name in PropertyStore.DefaultValues) - return PropertyStore.DefaultValues[name] + return PropertyStore.DefaultValues[name]; else - return null + return null; } }; // we list all the properties we know and use here, so we won' have to deal with unexpected type mismatches PropertyStore.MandatedTypes = { - 'visible' : GLib.VariantType.new("b"), - 'enabled' : GLib.VariantType.new("b"), - 'label' : GLib.VariantType.new("s"), - 'type' : GLib.VariantType.new("s"), - 'children-display' : GLib.VariantType.new("s"), - 'icon-name' : GLib.VariantType.new("s"), - 'icon-data' : GLib.VariantType.new("ay"), - 'toggle-type' : GLib.VariantType.new("s"), - 'toggle-state' : GLib.VariantType.new("i") -} + 'visible': GLib.VariantType.new('b'), + 'enabled': GLib.VariantType.new('b'), + 'label': GLib.VariantType.new('s'), + 'type': GLib.VariantType.new('s'), + 'children-display': GLib.VariantType.new('s'), + 'icon-name': GLib.VariantType.new('s'), + 'icon-data': GLib.VariantType.new('ay'), + 'toggle-type': GLib.VariantType.new('s'), + 'toggle-state': GLib.VariantType.new('i'), +}; PropertyStore.DefaultValues = { 'visible': GLib.Variant.new_boolean(true), 'enabled': GLib.Variant.new_boolean(true), - 'label' : GLib.Variant.new_string(''), - 'type' : GLib.Variant.new_string("standard") + 'label': GLib.Variant.new_string(''), + 'type': GLib.Variant.new_string('standard'), // elements not in here must return null -} +}; /** * Represents a single menu item @@ -96,83 +96,83 @@ // will steal the properties object constructor(client, id, properties, children_ids) { - this._client = client - this._id = id - this._propStore = new PropertyStore(properties) - this._children_ids = children_ids + this._client = client; + this._id = id; + this._propStore = new PropertyStore(properties); + this._children_ids = children_ids; } property_get(prop_name) { - let prop = this.property_get_variant(prop_name) - return prop ? prop.get_string()[0] : null + let prop = this.property_get_variant(prop_name); + return prop ? prop.get_string()[0] : null; } property_get_variant(prop_name) { - return this._propStore.get(prop_name) + return this._propStore.get(prop_name); } property_get_bool(prop_name) { - let prop = this.property_get_variant(prop_name) - return prop ? prop.get_boolean() : false + let prop = this.property_get_variant(prop_name); + return prop ? prop.get_boolean() : false; } property_get_int(prop_name) { - let prop = this.property_get_variant(prop_name) - return prop ? prop.get_int32() : 0 + let prop = this.property_get_variant(prop_name); + return prop ? prop.get_int32() : 0; } property_set(prop, value) { - this._propStore.set(prop, value) - - this.emit('property-changed', prop, this.property_get_variant(prop)) + this._propStore.set(prop, value); + + this.emit('property-changed', prop, this.property_get_variant(prop)); } get_children_ids() { - return this._children_ids.concat() // clone it! + return this._children_ids.concat(); // clone it! } add_child(pos, child_id) { - this._children_ids.splice(pos, 0, child_id) - this.emit('child-added', this._client.get_item(child_id), pos) + this._children_ids.splice(pos, 0, child_id); + this.emit('child-added', this._client.get_item(child_id), pos); } remove_child(child_id) { // find it - let pos = -1 + let pos = -1; for (let i = 0; i < this._children_ids.length; ++i) { if (this._children_ids[i] == child_id) { - pos = i - break + pos = i; + break; } } if (pos < 0) { - Util.Logger.critical("Trying to remove child which doesn't exist") + Util.Logger.critical("Trying to remove child which doesn't exist"); } else { - this._children_ids.splice(pos, 1) - this.emit('child-removed', this._client.get_item(child_id)) + this._children_ids.splice(pos, 1); + this.emit('child-removed', this._client.get_item(child_id)); } } move_child(child_id, newpos) { // find the old position - let oldpos = -1 + let oldpos = -1; for (let i = 0; i < this._children_ids.length; ++i) { if (this._children_ids[i] == child_id) { - oldpos = i - break + oldpos = i; + break; } } if (oldpos < 0) { - Util.Logger.critical("tried to move child which wasn't in the list") - return + Util.Logger.critical("tried to move child which wasn't in the list"); + return; } if (oldpos != newpos) { - this._children_ids.splice(oldpos, 1) - this._children_ids.splice(newpos, 0, child_id) - this.emit('child-moved', oldpos, newpos, this._client.get_item(child_id)) + this._children_ids.splice(oldpos, 1); + this._children_ids.splice(newpos, 0, child_id); + this.emit('child-moved', oldpos, newpos, this._client.get_item(child_id)); } } @@ -182,20 +182,20 @@ handle_event(event, data, timestamp) { if (!data) - data = GLib.Variant.new_int32(0) - - this._client.send_event(this._id, event, data, timestamp) + data = GLib.Variant.new_int32(0); + + this._client.send_event(this._id, event, data, timestamp); } get_id() { - return this._id + return this._id; } send_about_to_show() { - this._client.send_about_to_show(this._id) - } -} -Signals.addSignalMethods(DbusMenuItem.prototype) + this._client.send_about_to_show(this._id); + } +}; +Signals.addSignalMethods(DbusMenuItem.prototype); const BusClientProxy = Gio.DBusProxy.makeProxyWrapper(DBusInterfaces.DBusMenu); @@ -211,20 +211,20 @@ busName, busPath, this._clientReady.bind(this), - this._cancellable) + this._cancellable); this._items = new Map([ [ 0, new DbusMenuItem(this, 0, { 'children-display': GLib.Variant.new_string('submenu'), }, []), - ] + ], ]); // will be set to true if a layout update is requested while one is already in progress // then the handler that completes the layout update will request another update - this._flagLayoutUpdateRequired = false - this._flagLayoutUpdateInProgress = false + this._flagLayoutUpdateRequired = false; + this._flagLayoutUpdateInProgress = false; // property requests are queued this._propertiesRequestedFor = new Set(/* ids */); @@ -245,9 +245,9 @@ _requestLayoutUpdate() { if (this._flagLayoutUpdateInProgress) - this._flagLayoutUpdateRequired = true + this._flagLayoutUpdateRequired = true; else - this._beginLayoutUpdate() + this._beginLayoutUpdate(); } async _requestProperties(id) { @@ -264,43 +264,43 @@ _beginRequestProperties() { this._proxy.GetGroupPropertiesRemote( - Array.from(this._propertiesRequestedFor), - [], - this._cancellable, - this._endRequestProperties.bind(this)) + Array.from(this._propertiesRequestedFor), + [], + this._cancellable, + this._endRequestProperties.bind(this)); this._propertiesRequestedFor.clear(); - return false + return false; } _endRequestProperties(result, error) { if (error) { if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) Util.Logger.warn(`Could not retrieve properties: ${error}`); - return + return; } // for some funny reason, the result array is hidden in an array result[0].forEach(([id, properties]) => { let item = this._items.get(id); if (!item) - return + return; for (let prop in properties) - item.property_set(prop, properties[prop]) + item.property_set(prop, properties[prop]); }); } // Traverses the list of cached menu items and removes everyone that is not in the list // so we don't keep alive unused items _gcItems() { - let tag = new Date().getTime() - - let toTraverse = [ 0 ] + let tag = new Date().getTime(); + + let toTraverse = [0]; while (toTraverse.length > 0) { - let item = this.get_item(toTraverse.shift()) - item._dbusClientGcTag = tag - Array.prototype.push.apply(toTraverse, item.get_children_ids()) + let item = this.get_item(toTraverse.shift()); + item._dbusClientGcTag = tag; + Array.prototype.push.apply(toTraverse, item.get_children_ids()); } this._items.forEach((i, id) => { @@ -315,36 +315,36 @@ // we only read the type property, because if the type changes after reading all properties, // the view would have to replace the item completely which we try to avoid this._proxy.GetLayoutRemote(0, -1, - [ 'type', 'children-display' ], + ['type', 'children-display'], this._cancellable, - this._endLayoutUpdate.bind(this)) - - this._flagLayoutUpdateRequired = false - this._flagLayoutUpdateInProgress = true + this._endLayoutUpdate.bind(this)); + + this._flagLayoutUpdateRequired = false; + this._flagLayoutUpdateInProgress = true; } _endLayoutUpdate(result, error) { if (error) { if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) Util.Logger.warn(`While reading menu layout on proxy ${this._proxy.g_name_owner}: ${error}`); - return - } - - let [ revision, root ] = result - this._doLayoutUpdate(root) - this._gcItems() + return; + } + + let [revision, root] = result; + this._doLayoutUpdate(root); + this._gcItems(); if (this._flagLayoutUpdateRequired) - this._beginLayoutUpdate() + this._beginLayoutUpdate(); else - this._flagLayoutUpdateInProgress = false + this._flagLayoutUpdateInProgress = false; } _doLayoutUpdate(item) { - let [ id, properties, children ] = item - - let childrenUnpacked = children.map(c => c.deep_unpack()) - let childrenIds = childrenUnpacked.map(c => c[0]) + let [id, properties, children] = item; + + let childrenUnpacked = children.map(c => c.deep_unpack()); + let childrenIds = childrenUnpacked.map(c => c[0]); // make sure all our children exist childrenUnpacked.forEach(c => this._doLayoutUpdate(c)); @@ -353,28 +353,28 @@ const menuItem = this._items.get(id); if (menuItem) { // we do, update our properties if necessary - for (let prop in properties) { - menuItem.property_set(prop, properties[prop]) - } + for (let prop in properties) + menuItem.property_set(prop, properties[prop]); + // make sure our children are all at the right place, and exist - let oldChildrenIds = menuItem.get_children_ids() + let oldChildrenIds = menuItem.get_children_ids(); for (let i = 0; i < childrenIds.length; ++i) { // try to recycle an old child - let oldChild = -1 + let oldChild = -1; for (let j = 0; j < oldChildrenIds.length; ++j) { if (oldChildrenIds[j] == childrenIds[i]) { - oldChild = oldChildrenIds.splice(j, 1)[0] - break + oldChild = oldChildrenIds.splice(j, 1)[0]; + break; } } if (oldChild < 0) { // no old child found, so create a new one! - menuItem.add_child(i, childrenIds[i]) + menuItem.add_child(i, childrenIds[i]); } else { // old child found, reuse it! - menuItem.move_child(childrenIds[i], i) + menuItem.move_child(childrenIds[i], i); } } @@ -383,10 +383,10 @@ } else { // we don't, so let's create us this._items.set(id, new DbusMenuItem(this, id, properties, childrenIds)); - this._requestProperties(id) - } - - return id + this._requestProperties(id); + } + + return id; } _clientReady(result, error) { @@ -396,11 +396,11 @@ return; } - this._requestLayoutUpdate() + this._requestLayoutUpdate(); // listen for updated layouts and properties - this._proxy.connectSignal("LayoutUpdated", this._onLayoutUpdated.bind(this)) - this._proxy.connectSignal("ItemsPropertiesUpdated", this._onPropertiesUpdated.bind(this)) + this._proxy.connectSignal('LayoutUpdated', this._onLayoutUpdated.bind(this)); + this._proxy.connectSignal('ItemsPropertiesUpdated', this._onPropertiesUpdated.bind(this)); } get_item(id) { @@ -416,73 +416,73 @@ * and don't return a boolean, so we need to support both cases */ let connection = this._proxy.get_connection(); connection.call(this._proxy.get_name(), this._proxy.get_object_path(), - this._proxy.get_interface_name(), 'AboutToShow', - new GLib.Variant("(i)", [id]), null, - Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { - try { - let ret = proxy.call_finish(res); - if ((ret.is_of_type(new GLib.VariantType('(b)')) && + this._proxy.get_interface_name(), 'AboutToShow', + new GLib.Variant('(i)', [id]), null, + Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => { + try { + let ret = proxy.call_finish(res); + if ((ret.is_of_type(new GLib.VariantType('(b)')) && ret.get_child_value(0).get_boolean()) || - ret.is_of_type(new GLib.VariantType('()'))) { - this._requestLayoutUpdate(); + ret.is_of_type(new GLib.VariantType('()'))) + this._requestLayoutUpdate(); + + } catch (e) { + Util.Logger.warn(`Impossible to send about-to-show to menu: ${e}`); } - } catch (e) { - Util.Logger.warn("Impossible to send about-to-show to menu: " + e); - } - }); + }); } send_event(id, event, params, timestamp) { if (!this._proxy) - return + return; this._proxy.EventRemote(id, event, params, timestamp, this._cancellable, - () => { /* we don't care */ }) + () => { /* we don't care */ }); } _onLayoutUpdated() { - this._requestLayoutUpdate() + this._requestLayoutUpdate(); } _onPropertiesUpdated(proxy, name, [changed, removed]) { changed.forEach(([id, props]) => { let item = this._items.get(id); if (!item) - return + return; for (let prop in props) - item.property_set(prop, props[prop]) + item.property_set(prop, props[prop]); }); removed.forEach(([id, propNames]) => { let item = this._items.get(id); if (!item) - return + return; propNames.forEach(propName => item.property_set(propName, null)); }); } destroy() { - this.emit('destroy') + this.emit('destroy'); this._cancellable.cancel(); - Signals._disconnectAll.apply(this._proxy) - - this._proxy = null - } -} -Signals.addSignalMethods(DBusClient.prototype) - -////////////////////////////////////////////////////////////////////////// + Signals._disconnectAll.apply(this._proxy); + + this._proxy = null; + } +}; +Signals.addSignalMethods(DBusClient.prototype); + +// //////////////////////////////////////////////////////////////////////// // PART TWO: "View" frontend implementation. -////////////////////////////////////////////////////////////////////////// +// //////////////////////////////////////////////////////////////////////// // https://bugzilla.gnome.org/show_bug.cgi?id=731514 // GNOME 3.10 and 3.12 can't open a nested submenu. // Patches have been written, but it's not clear when (if?) they will be applied. // We also don't know whether they will be backported to 3.10, so we will work around // it in the meantime. Offending versions can be clearly identified: -const NEED_NESTED_SUBMENU_FIX = '_setOpenedSubMenu' in PopupMenu.PopupMenu.prototype +const NEED_NESTED_SUBMENU_FIX = '_setOpenedSubMenu' in PopupMenu.PopupMenu.prototype; /** * Creates new wrapper menu items and injects methods for managing them at runtime. @@ -491,50 +491,50 @@ * handlers, so any `this` will refer to a menu item create in createItem */ const MenuItemFactory = { - createItem: function(client, dbusItem) { + createItem(client, dbusItem) { // first, decide whether it's a submenu or not - if (dbusItem.property_get("children-display") == "submenu") - var shellItem = new PopupMenu.PopupSubMenuMenuItem("FIXME") - else if (dbusItem.property_get("type") == "separator") - var shellItem = new PopupMenu.PopupSeparatorMenuItem('') + if (dbusItem.property_get('children-display') == 'submenu') + var shellItem = new PopupMenu.PopupSubMenuMenuItem('FIXME'); + else if (dbusItem.property_get('type') == 'separator') + var shellItem = new PopupMenu.PopupSeparatorMenuItem(''); else - var shellItem = new PopupMenu.PopupMenuItem("FIXME") - - shellItem._dbusItem = dbusItem - shellItem._dbusClient = client + var shellItem = new PopupMenu.PopupMenuItem('FIXME'); + + shellItem._dbusItem = dbusItem; + shellItem._dbusClient = client; if (shellItem instanceof PopupMenu.PopupMenuItem) { - shellItem._icon = new St.Icon({ style_class: 'popup-menu-icon', x_align: St.Align.END }) + shellItem._icon = new St.Icon({ style_class: 'popup-menu-icon', x_align: St.Align.END }); shellItem.add_child(shellItem._icon); shellItem.label.x_expand = true; } // initialize our state - MenuItemFactory._updateLabel.call(shellItem) - MenuItemFactory._updateOrnament.call(shellItem) - MenuItemFactory._updateImage.call(shellItem) - MenuItemFactory._updateVisible.call(shellItem) - MenuItemFactory._updateSensitive.call(shellItem) + MenuItemFactory._updateLabel.call(shellItem); + MenuItemFactory._updateOrnament.call(shellItem); + MenuItemFactory._updateImage.call(shellItem); + MenuItemFactory._updateVisible.call(shellItem); + MenuItemFactory._updateSensitive.call(shellItem); // initially create children if (shellItem instanceof PopupMenu.PopupSubMenuMenuItem) { - let children = dbusItem.get_children() - for (let i = 0; i < children.length; ++i) { - shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i])) - } + let children = dbusItem.get_children(); + for (let i = 0; i < children.length; ++i) + shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i])); + } // now, connect various events - Util.connectSmart(dbusItem, 'property-changed', shellItem, MenuItemFactory._onPropertyChanged) - Util.connectSmart(dbusItem, 'child-added', shellItem, MenuItemFactory._onChildAdded) - Util.connectSmart(dbusItem, 'child-removed', shellItem, MenuItemFactory._onChildRemoved) - Util.connectSmart(dbusItem, 'child-moved', shellItem, MenuItemFactory._onChildMoved) - Util.connectSmart(shellItem, 'activate', shellItem, MenuItemFactory._onActivate) + Util.connectSmart(dbusItem, 'property-changed', shellItem, MenuItemFactory._onPropertyChanged); + Util.connectSmart(dbusItem, 'child-added', shellItem, MenuItemFactory._onChildAdded); + Util.connectSmart(dbusItem, 'child-removed', shellItem, MenuItemFactory._onChildRemoved); + Util.connectSmart(dbusItem, 'child-moved', shellItem, MenuItemFactory._onChildMoved); + Util.connectSmart(shellItem, 'activate', shellItem, MenuItemFactory._onActivate); if (shellItem.menu) - Util.connectSmart(shellItem.menu, "open-state-changed", shellItem, MenuItemFactory._onOpenStateChanged) - - return shellItem + Util.connectSmart(shellItem.menu, 'open-state-changed', shellItem, MenuItemFactory._onOpenStateChanged); + + return shellItem; }, _onOpenStateChanged(menu, open) { @@ -542,168 +542,170 @@ if (NEED_NESTED_SUBMENU_FIX) { // close our own submenus if (menu._openedSubMenu) - menu._openedSubMenu.close(false) + menu._openedSubMenu.close(false); // register ourselves and close sibling submenus if (menu._parent._openedSubMenu && menu._parent._openedSubMenu !== menu) - menu._parent._openedSubMenu.close(true) - - menu._parent._openedSubMenu = menu + menu._parent._openedSubMenu.close(true); + + menu._parent._openedSubMenu = menu; } - this._dbusItem.handle_event("opened", null, 0) - this._dbusItem.send_about_to_show() + this._dbusItem.handle_event('opened', null, 0); + this._dbusItem.send_about_to_show(); } else { if (NEED_NESTED_SUBMENU_FIX) { // close our own submenus if (menu._openedSubMenu) - menu._openedSubMenu.close(false) + menu._openedSubMenu.close(false); } - this._dbusItem.handle_event("closed", null, 0) + this._dbusItem.handle_event('closed', null, 0); } }, _onActivate() { - this._dbusItem.handle_event("clicked", GLib.Variant.new("i", 0), 0) + this._dbusItem.handle_event('clicked', GLib.Variant.new('i', 0), 0); }, _onPropertyChanged(dbusItem, prop, value) { - if (prop == "toggle-type" || prop == "toggle-state") - MenuItemFactory._updateOrnament.call(this) - else if (prop == "label") - MenuItemFactory._updateLabel.call(this) - else if (prop == "enabled") - MenuItemFactory._updateSensitive.call(this) - else if (prop == "visible") - MenuItemFactory._updateVisible.call(this) - else if (prop == "icon-name" || prop == "icon-data") - MenuItemFactory._updateImage.call(this) - else if (prop == "type" || prop == "children-display") - MenuItemFactory._replaceSelf.call(this) - //else + if (prop == 'toggle-type' || prop == 'toggle-state') + MenuItemFactory._updateOrnament.call(this); + else if (prop == 'label') + MenuItemFactory._updateLabel.call(this); + else if (prop == 'enabled') + MenuItemFactory._updateSensitive.call(this); + else if (prop == 'visible') + MenuItemFactory._updateVisible.call(this); + else if (prop == 'icon-name' || prop == 'icon-data') + MenuItemFactory._updateImage.call(this); + else if (prop == 'type' || prop == 'children-display') + MenuItemFactory._replaceSelf.call(this); + // else // Util.Logger.debug("Unhandled property change: "+prop) }, _onChildAdded(dbusItem, child, position) { if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { - Util.Logger.warn("Tried to add a child to non-submenu item. Better recreate it as whole") - MenuItemFactory._replaceSelf.call(this) + Util.Logger.warn('Tried to add a child to non-submenu item. Better recreate it as whole'); + MenuItemFactory._replaceSelf.call(this); } else { - this.menu.addMenuItem(MenuItemFactory.createItem(this._dbusClient, child), position) + this.menu.addMenuItem(MenuItemFactory.createItem(this._dbusClient, child), position); } }, _onChildRemoved(dbusItem, child) { if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { - Util.Logger.warn("Tried to remove a child from non-submenu item. Better recreate it as whole") - MenuItemFactory._replaceSelf.call(this) + Util.Logger.warn('Tried to remove a child from non-submenu item. Better recreate it as whole'); + MenuItemFactory._replaceSelf.call(this); } else { // find it! - this.menu._getMenuItems().forEach((item) => { + this.menu._getMenuItems().forEach(item => { if (item._dbusItem == child) - item.destroy() - }) + item.destroy(); + }); } }, _onChildMoved(dbusItem, child, oldpos, newpos) { if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { - Util.Logger.warn("Tried to move a child in non-submenu item. Better recreate it as whole") - MenuItemFactory._replaceSelf.call(this) + Util.Logger.warn('Tried to move a child in non-submenu item. Better recreate it as whole'); + MenuItemFactory._replaceSelf.call(this); } else { - MenuUtils.moveItemInMenu(this.menu, child, newpos) + MenuUtils.moveItemInMenu(this.menu, child, newpos); } }, _updateLabel() { - let label = this._dbusItem.property_get("label").replace(/_([^_])/, "$1") + let label = this._dbusItem.property_get('label').replace(/_([^_])/, '$1'); if (this.label) // especially on GS3.8, the separator item might not even have a hidden label - this.label.set_text(label) + this.label.set_text(label); }, _updateOrnament() { - if (!this.setOrnament) return // separators and alike might not have gotten the polyfill - - if (this._dbusItem.property_get("toggle-type") == "checkmark" && this._dbusItem.property_get_int("toggle-state")) - this.setOrnament(PopupMenu.Ornament.CHECK) - else if (this._dbusItem.property_get("toggle-type") == "radio" && this._dbusItem.property_get_int("toggle-state")) - this.setOrnament(PopupMenu.Ornament.DOT) + if (!this.setOrnament) + return; // separators and alike might not have gotten the polyfill + + if (this._dbusItem.property_get('toggle-type') == 'checkmark' && this._dbusItem.property_get_int('toggle-state')) + this.setOrnament(PopupMenu.Ornament.CHECK); + else if (this._dbusItem.property_get('toggle-type') == 'radio' && this._dbusItem.property_get_int('toggle-state')) + this.setOrnament(PopupMenu.Ornament.DOT); else - this.setOrnament(PopupMenu.Ornament.NONE) + this.setOrnament(PopupMenu.Ornament.NONE); }, _updateImage() { - if (!this._icon) return // might be missing on submenus / separators - - let iconName = this._dbusItem.property_get("icon-name") - let iconData = this._dbusItem.property_get_variant("icon-data") + if (!this._icon) + return; // might be missing on submenus / separators + + let iconName = this._dbusItem.property_get('icon-name'); + let iconData = this._dbusItem.property_get_variant('icon-data'); if (iconName) - this._icon.icon_name = iconName + this._icon.icon_name = iconName; else if (iconData) - this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null) + this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null); }, _updateVisible() { - this.visible = this._dbusItem.property_get_bool("visible") + this.visible = this._dbusItem.property_get_bool('visible'); }, _updateSensitive() { - this.setSensitive(this._dbusItem.property_get_bool("enabled")) + this.setSensitive(this._dbusItem.property_get_bool('enabled')); }, _replaceSelf(newSelf) { // create our new self if needed if (!newSelf) - newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem) + newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem); // first, we need to find our old position - let pos = -1 - let family = this._parent._getMenuItems() + let pos = -1; + let family = this._parent._getMenuItems(); for (let i = 0; i < family.length; ++i) { if (family[i] === this) - pos = i + pos = i; } if (pos < 0) - throw new Error("DBusMenu: can't replace non existing menu item") + throw new Error("DBusMenu: can't replace non existing menu item"); // add our new self while we're still alive - this._parent.addMenuItem(newSelf, pos) + this._parent.addMenuItem(newSelf, pos); // now destroy our old self - this.destroy() - } -} + this.destroy(); + }, +}; /** * Utility functions not necessarily belonging into the item factory */ const MenuUtils = { moveItemInMenu(menu, dbusItem, newpos) { - //HACK: we're really getting into the internals of the PopupMenu implementation + // HACK: we're really getting into the internals of the PopupMenu implementation // First, find our wrapper. Children tend to lie. We do not trust the old positioning. - let family = menu._getMenuItems() + let family = menu._getMenuItems(); for (let i = 0; i < family.length; ++i) { if (family[i]._dbusItem == dbusItem) { // now, remove it - menu.box.remove_child(family[i]) + menu.box.remove_child(family[i]); // and add it again somewhere else if (newpos < family.length && family[newpos] != family[i]) - menu.box.insert_child_below(family[i], family[newpos]) + menu.box.insert_child_below(family[i], family[newpos]); else - menu.box.add(family[i]) + menu.box.add(family[i]); // skip the rest - return + return; } } - } -} + }, +}; /** @@ -714,11 +716,11 @@ var Client = class AppIndicators_Client { constructor(busName, path) { - this._busName = busName - this._busPath = path - this._client = new DBusClient(busName, path) - this._rootMenu = null // the shell menu - this._rootItem = null // the DbusMenuItem for the root + this._busName = busName; + this._busPath = path; + this._client = new DBusClient(busName, path); + this._rootMenu = null; // the shell menu + this._rootItem = null; // the DbusMenuItem for the root } get isReady() { @@ -728,88 +730,89 @@ // this will attach the client to an already existing menu that will be used as the root menu. // it will also connect the client to be automatically destroyed when the menu dies. attachToMenu(menu) { - this._rootMenu = menu - this._rootItem = this._client.get_root() + this._rootMenu = menu; + this._rootItem = this._client.get_root(); // cleanup: remove existing children (just in case) - this._rootMenu.removeAll() + this._rootMenu.removeAll(); if (NEED_NESTED_SUBMENU_FIX) - menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this) + menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this); // connect handlers - Util.connectSmart(menu, 'open-state-changed', this, '_onMenuOpened') - Util.connectSmart(menu, 'destroy', this, 'destroy') - - Util.connectSmart(this._rootItem, 'child-added', this, '_onRootChildAdded') - Util.connectSmart(this._rootItem, 'child-removed', this, '_onRootChildRemoved') - Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved') + Util.connectSmart(menu, 'open-state-changed', this, '_onMenuOpened'); + Util.connectSmart(menu, 'destroy', this, 'destroy'); + + Util.connectSmart(this._rootItem, 'child-added', this, '_onRootChildAdded'); + Util.connectSmart(this._rootItem, 'child-removed', this, '_onRootChildRemoved'); + Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved'); // Dropbox requires us to call AboutToShow(0) first - this._rootItem.send_about_to_show() + this._rootItem.send_about_to_show(); // fill the menu for the first time this._rootItem.get_children().forEach(child => - this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)) + this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)), ); } _setOpenedSubmenu(submenu) { if (!submenu) - return + return; if (submenu._parent != this._rootMenu) - return + return; if (submenu === this._openedSubMenu) - return + return; if (this._openedSubMenu && this._openedSubMenu.isOpen) - this._openedSubMenu.close(true) - - this._openedSubMenu = submenu + this._openedSubMenu.close(true); + + this._openedSubMenu = submenu; } _onRootChildAdded(dbusItem, child, position) { - this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position) + this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position); } _onRootChildRemoved(dbusItem, child) { // children like to play hide and seek // but we know how to find it for sure! - this._rootMenu._getMenuItems().forEach((item) => { + this._rootMenu._getMenuItems().forEach(item => { if (item._dbusItem == child) - item.destroy() - }) + item.destroy(); + }); } _onRootChildMoved(dbusItem, child, oldpos, newpos) { - MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos) + MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos); } _onMenuOpened(menu, state) { - if (!this._rootItem) return + if (!this._rootItem) + return; if (state) { if (this._openedSubMenu && this._openedSubMenu.isOpen) - this._openedSubMenu.close() - - this._rootItem.handle_event("opened", null, 0) - this._rootItem.send_about_to_show() + this._openedSubMenu.close(); + + this._rootItem.handle_event('opened', null, 0); + this._rootItem.send_about_to_show(); } else { - this._rootItem.handle_event("closed", null, 0) + this._rootItem.handle_event('closed', null, 0); } } destroy() { - this.emit('destroy') + this.emit('destroy'); if (this._client) - this._client.destroy() - - this._client = null - this._rootItem = null - this._rootMenu = null - } -} -Signals.addSignalMethods(Client.prototype) + this._client.destroy(); + + this._client = null; + this._rootItem = null; + this._rootMenu = null; + } +}; +Signals.addSignalMethods(Client.prototype); diff --git a/extension.js b/extension.js index d55a5c5..288cb6d 100644 --- a/extension.js +++ b/extension.js @@ -14,10 +14,10 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. const Gio = imports.gi.Gio; -const Extension = imports.misc.extensionUtils.getCurrentExtension() +const Extension = imports.misc.extensionUtils.getCurrentExtension(); -const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher -const Util = Extension.imports.util +const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher; +const Util = Extension.imports.util; let statusNotifierWatcher = null; let isEnabled = false; @@ -27,19 +27,19 @@ watchDog = new Util.NameWatcher(StatusNotifierWatcher.WATCHER_BUS_NAME); watchDog.connect('vanished', () => maybe_enable_after_name_available()); - //HACK: we want to leave the watchdog alive when disabling the extension, + // HACK: we want to leave the watchdog alive when disabling the extension, // but if we are being reloaded, we destroy it since it could be considered // a leak and spams our log, too. - if (typeof global['--appindicator-extension-on-reload'] == 'function') - global['--appindicator-extension-on-reload']() + if (typeof global['--appindicator-extension-on-reload'] === 'function') + global['--appindicator-extension-on-reload'](); global['--appindicator-extension-on-reload'] = () => { - Util.Logger.debug("Reload detected, destroying old watchdog") + Util.Logger.debug('Reload detected, destroying old watchdog'); watchDog.destroy(); - } + }; } -//FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly. +// FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly. // This will create very bad side effects in case we were not done unowning the name while trying // to own it again. Since g_bus_unown_name doesn't fire any callback when it's done, we need to // monitor the bus manually to find out when the name vanished so we can reclaim it again. diff --git a/iconCache.js b/iconCache.js index ef9516e..2f4c4d0 100644 --- a/iconCache.js +++ b/iconCache.js @@ -14,8 +14,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -const GLib = imports.gi.GLib -const Gio = imports.gi.Gio +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; const Extension = imports.misc.extensionUtils.getCurrentExtension(); const PromiseUtils = Extension.imports.promiseUtils; @@ -34,7 +34,7 @@ var IconCache = class AppIndicators_IconCache { constructor() { this._cache = new Map(); - this._lifetime = new Map(); //we don't want to attach lifetime to the object + this._lifetime = new Map(); // we don't want to attach lifetime to the object } add(id, icon) { @@ -83,7 +83,7 @@ // marks all the icons as removable, if something doesn't claim them before weakClear() { - this._cache.forEach((icon) => icon.inUse = false); + this._cache.forEach(icon => icon.inUse = false); this._checkGC(); } @@ -109,12 +109,12 @@ let cacheIsEmpty = this._cache.size == 0; if (!cacheIsEmpty && !this._gcTimeout) { - Util.Logger.debug("IconCache: garbage collector started"); + Util.Logger.debug('IconCache: garbage collector started'); this._gcTimeout = new PromiseUtils.TimeoutSecondsPromise(GC_INTERVAL, GLib.PRIORITY_LOW); await this._gcTimeout; } else if (cacheIsEmpty && this._gcTimeout) { - Util.Logger.debug("IconCache: garbage collector stopped"); + Util.Logger.debug('IconCache: garbage collector stopped'); this._gcTimeout.cancel(); delete this._gcTimeout; } @@ -123,13 +123,13 @@ _gc() { let time = new Date().getTime(); this._cache.forEach((icon, id) => { - if (icon.inUse) { + if (icon.inUse) Util.Logger.debug(`IconCache: ${id} is in use.`); - } else if (this._lifetime.get(id) < time) { + else if (this._lifetime.get(id) < time) this._remove(id); - } else { + else Util.Logger.debug(`IconCache: ${id} survived this round.`); - } + }); return true; diff --git a/indicator-test-tool/testTool.js b/indicator-test-tool/testTool.js index 365b17d..ebb4ab5 100755 --- a/indicator-test-tool/testTool.js +++ b/indicator-test-tool/testTool.js @@ -34,256 +34,256 @@ (() => { -var app = new Gtk.Application({ - application_id: null -}); - -var window = null; - -app.connect("activate", () => { - window.present(); -}); - -app.connect("startup", () => { - window = new Gtk.ApplicationWindow({ - title: "test", - application: app + var app = new Gtk.Application({ + application_id: null, }); - let getRandomIcon = () => - iconsPool[Math.floor(Math.random() * (iconsPool.length - 1))]; - - let setRandomIconPath = () => { - let iconName = getRandomIcon(); - let iconInfo = Gtk.IconTheme.get_default().lookup_icon(iconName, - 16, Gtk.IconLookupFlags.GENERIC_FALLBACK); - let iconFile = Gio.File.new_for_path(iconInfo.get_filename()); - let [, extension] = iconFile.get_basename().split('.'); - let newName = `${iconName}-${Math.floor(Math.random() * 100)}.${extension}`; - let newFile = Gio.File.new_for_path( - `${GLib.dir_make_tmp('indicator-test-XXXXXX')}/${newName}`); - iconFile.copy(newFile, Gio.FileCopyFlagsOVERWRITE, null, null); - - indicator.set_icon_theme_path(newFile.get_parent().get_path()); - indicator.set_icon(newFile.get_basename()); - } - - var menu = new Gtk.Menu(); - - var item = Gtk.MenuItem.new_with_label("A standard item"); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Foo"); - menu.append(item); - - item = Gtk.ImageMenuItem.new_with_label("Calculator"); - item.image = Gtk.Image.new_from_icon_name("gnome-calculator", Gtk.IconSize.MENU); - menu.append(item); - - item = Gtk.CheckMenuItem.new_with_label("Check me!"); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Blub"); - let sub = new Gtk.Menu(); - item.set_submenu(sub); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Blubdablub"); - sub.append(item); - - item = new Gtk.SeparatorMenuItem(); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Foo"); - menu.append(item); - - let submenu = new Gtk.Menu(); - item.set_submenu(submenu); - - item = Gtk.MenuItem.new_with_label("Hello"); - submenu.append(item); - - item = Gtk.MenuItem.new_with_label("Nested"); - submenu.append(item); - - let submenu1 = new Gtk.Menu(); - item.set_submenu(submenu1); - - item = Gtk.MenuItem.new_with_label("Another nested"); - submenu.append(item); - - let submenu2 = new Gtk.Menu(); - item.set_submenu(submenu2); - - item = Gtk.MenuItem.new_with_label("Some other item"); - submenu1.append(item); - - item = Gtk.MenuItem.new_with_label("abcdefg"); - submenu2.append(item); - - item = new Gtk.SeparatorMenuItem(); - menu.append(item); - - var group = []; - - for (let i = 0; i < 5; ++i) { - item = Gtk.RadioMenuItem.new_with_label(group, "Example Radio "+i); - group = Gtk.RadioMenuItem.prototype.get_group.apply(item)//.get_group(); - if (i == 1) - item.set_active(true); - menu.append(item); - } - - item = new Gtk.SeparatorMenuItem(); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Set Label"); - item.connect('activate', () => { - indicator.set_label(''+new Date().getTime(), 'Blub'); + var window = null; + + app.connect('activate', () => { + window.present(); }); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Unset Label"); - item.connect('activate', () => { - indicator.set_label('', ''); - }) - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Autodestroy Label"); - item.connect('activate', () => { - let i = 30; - GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { - indicator.set_label(i > 0 ? `Label timeout ${i--}` : '', ''); - return (i >= 0); - }); - }) - menu.append(item); - - item = Gtk.MenuItem.new_with_label('Set Random icon'); - item.connect('activate', () => indicator.set_icon(getRandomIcon())); - menu.append(item); - - item = Gtk.MenuItem.new_with_label('Set Random custom theme icon'); - item.connect('activate', setRandomIconPath); - menu.append(item); - - item = Gtk.CheckMenuItem.new_with_label('Toggle Label and Icon'); - item.connect('activate', (item) => { - if (item.get_active()) { + + app.connect('startup', () => { + window = new Gtk.ApplicationWindow({ + title: 'test', + application: app, + }); + + let getRandomIcon = () => + iconsPool[Math.floor(Math.random() * (iconsPool.length - 1))]; + + let setRandomIconPath = () => { + let iconName = getRandomIcon(); + let iconInfo = Gtk.IconTheme.get_default().lookup_icon(iconName, + 16, Gtk.IconLookupFlags.GENERIC_FALLBACK); + let iconFile = Gio.File.new_for_path(iconInfo.get_filename()); + let [, extension] = iconFile.get_basename().split('.'); + let newName = `${iconName}-${Math.floor(Math.random() * 100)}.${extension}`; + let newFile = Gio.File.new_for_path( + `${GLib.dir_make_tmp('indicator-test-XXXXXX')}/${newName}`); + iconFile.copy(newFile, Gio.FileCopyFlagsOVERWRITE, null, null); + + indicator.set_icon_theme_path(newFile.get_parent().get_path()); + indicator.set_icon(newFile.get_basename()); + }; + + var menu = new Gtk.Menu(); + + var item = Gtk.MenuItem.new_with_label('A standard item'); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Foo'); + menu.append(item); + + item = Gtk.ImageMenuItem.new_with_label('Calculator'); + item.image = Gtk.Image.new_from_icon_name('gnome-calculator', Gtk.IconSize.MENU); + menu.append(item); + + item = Gtk.CheckMenuItem.new_with_label('Check me!'); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Blub'); + let sub = new Gtk.Menu(); + item.set_submenu(sub); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Blubdablub'); + sub.append(item); + + item = new Gtk.SeparatorMenuItem(); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Foo'); + menu.append(item); + + let submenu = new Gtk.Menu(); + item.set_submenu(submenu); + + item = Gtk.MenuItem.new_with_label('Hello'); + submenu.append(item); + + item = Gtk.MenuItem.new_with_label('Nested'); + submenu.append(item); + + let submenu1 = new Gtk.Menu(); + item.set_submenu(submenu1); + + item = Gtk.MenuItem.new_with_label('Another nested'); + submenu.append(item); + + let submenu2 = new Gtk.Menu(); + item.set_submenu(submenu2); + + item = Gtk.MenuItem.new_with_label('Some other item'); + submenu1.append(item); + + item = Gtk.MenuItem.new_with_label('abcdefg'); + submenu2.append(item); + + item = new Gtk.SeparatorMenuItem(); + menu.append(item); + + var group = []; + + for (let i = 0; i < 5; ++i) { + item = Gtk.RadioMenuItem.new_with_label(group, `Example Radio ${i}`); + group = Gtk.RadioMenuItem.prototype.get_group.apply(item);// .get_group(); + if (i === 1) + item.set_active(true); + menu.append(item); + } + + item = new Gtk.SeparatorMenuItem(); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Set Label'); + item.connect('activate', () => { indicator.set_label(`${new Date().getTime()}`, 'Blub'); - item.connect('activate', () => indicator.set_icon(getRandomIcon())); - } else { + }); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Unset Label'); + item.connect('activate', () => { indicator.set_label('', ''); - indicator.set_icon(DEFAULT_ICON); - } - }) - menu.append(item); - let toggleBrandingItem = item; - - item = Gtk.CheckMenuItem.new_with_label('Toggle Attention'); - let toggleAttentionId = item.connect('activate', () => { - indicator.set_status(indicator.get_status() != AppIndicator.IndicatorStatus.ATTENTION ? - AppIndicator.IndicatorStatus.ATTENTION : - AppIndicator.IndicatorStatus.ACTIVE); + }); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Autodestroy Label'); + item.connect('activate', () => { + let i = 30; + GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => { + indicator.set_label(i > 0 ? `Label timeout ${i--}` : '', ''); + return i >= 0; + }); + }); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Set Random icon'); + item.connect('activate', () => indicator.set_icon(getRandomIcon())); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Set Random custom theme icon'); + item.connect('activate', setRandomIconPath); + menu.append(item); + + item = Gtk.CheckMenuItem.new_with_label('Toggle Label and Icon'); + item.connect('activate', it => { + if (it.get_active()) { + indicator.set_label(`${new Date().getTime()}`, 'Blub'); + it.connect('activate', () => indicator.set_icon(getRandomIcon())); + } else { + indicator.set_label('', ''); + indicator.set_icon(DEFAULT_ICON); + } + }); + menu.append(item); + let toggleBrandingItem = item; + + item = Gtk.CheckMenuItem.new_with_label('Toggle Attention'); + let toggleAttentionId = item.connect('activate', () => { + indicator.set_status(indicator.get_status() !== AppIndicator.IndicatorStatus.ATTENTION + ? AppIndicator.IndicatorStatus.ATTENTION + : AppIndicator.IndicatorStatus.ACTIVE); + }); + menu.append(item); + let toggleAttentionItem = item; + + item = new Gtk.SeparatorMenuItem(); + menu.append(item); + + /* Double separaptors test */ + + item = new Gtk.SeparatorMenuItem(); + menu.append(item); + + /* Simulate similar behavior of #226 and #236 */ + item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates'); + item.connect('activate', it => { + if (it.get_active()) { + it._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => { + setRandomIconPath(); + indicator.set_label(`${new Date().getSeconds()}`, ''); + return GLib.SOURCE_CONTINUE; + }); + } else { + GLib.source_remove(item._timeoutID); + delete item._timeoutID; + } + }); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Hide for some time'); + item.connect('activate', () => { + indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE); + GLib.timeout_add(0, 5000, () => { + indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); + return false; + }); + }); + menu.append(item); + + item = Gtk.MenuItem.new_with_label('Close in 5 seconds'); + item.connect('activate', () => { + GLib.timeout_add(0, 5000, () => { + app.quit(); + return false; + }); + }); + menu.append(item); + + menu.show_all(); + + var indicator = AppIndicator.Indicator.new('Hello', 'indicator-test', AppIndicator.IndicatorCategory.APPLICATION_STATUS); + + indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); + indicator.set_icon(DEFAULT_ICON); + indicator.set_attention_icon(ATTENTION_ICON); + indicator.set_menu(menu); + indicator.set_secondary_activate_target(toggleBrandingItem); + + indicator.connect('connection-changed', (_indicator, connected) => { + print(`Signal "connection-changed" emitted. Connected: ${connected}`); + }); + indicator.connect('new-attention-icon', () => { + print('Signal "new-attention-icon" emitted.'); + }); + indicator.connect('new-icon', () => { + let icon = ''; + if (indicator.get_status() === AppIndicator.IndicatorStatus.ATTENTION) + icon = indicator.get_attention_icon(); + else if (indicator.get_status() === AppIndicator.IndicatorStatus.ACTIVE) + icon = indicator.get_icon(); + + print(`Signal "new-icon" emitted. Icon: ${icon}`); + }); + indicator.connect('new-icon-theme-path', (_indicator, path) => { + print(`Signal "new-icon-theme-path" emitted. Path: ${path}`); + }); + indicator.connect('new-label', (_indicator, label, guide) => { + print(`Signal "new-label" emitted. Label: ${label}, Guide: ${guide}`); + }); + indicator.connect('new-status', (_indicator, status) => { + print(`Signal "new-status" emitted. Status: ${status}`); + + toggleAttentionItem.block_signal_handler(toggleAttentionId); + toggleAttentionItem.set_active(status === 'NeedsAttention'); + toggleAttentionItem.unblock_signal_handler(toggleAttentionId); + }); + indicator.connect('scroll-event', (_indicator, steps, direction) => { + print(`Signal "scroll-event" emitted. Steps: ${steps}, Direction: ${direction}`); + let currentIndex = iconsPool.indexOf(indicator.get_icon()); + let iconIndex; + + if (direction === ScrollType.UP) + iconIndex = (currentIndex + 1) % iconsPool.length; + else + iconIndex = (currentIndex <= 0 ? iconsPool.length : currentIndex) - 1; + + + indicator.set_icon(iconsPool[iconIndex]); + }); }); - menu.append(item); - let toggleAttentionItem = item; - - item = new Gtk.SeparatorMenuItem(); - menu.append(item); - - /* Double separaptors test */ - - item = new Gtk.SeparatorMenuItem(); - menu.append(item); - - /* Simulate similar behavior of #226 and #236 */ - item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates'); - item.connect('activate', (item) => { - if (item.get_active()) { - item._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => { - setRandomIconPath(); - indicator.set_label(`${new Date().getSeconds()}`, ''); - return GLib.SOURCE_CONTINUE; - }); - } else { - GLib.source_remove(item._timeoutID); - delete item._timeoutID; - } - }); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Hide for some time"); - item.connect('activate', () => { - indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE); - GLib.timeout_add(0, 5000, () => { - indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); - return false; - }); - }); - menu.append(item); - - item = Gtk.MenuItem.new_with_label("Close in 5 seconds"); - item.connect('activate', () => { - GLib.timeout_add(0, 5000, () => { - app.quit(); - return false; - }); - }); - menu.append(item); - - menu.show_all(); - - var indicator = AppIndicator.Indicator.new("Hello", "indicator-test", AppIndicator.IndicatorCategory.APPLICATION_STATUS); - - indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); - indicator.set_icon(DEFAULT_ICON); - indicator.set_attention_icon(ATTENTION_ICON); - indicator.set_menu(menu); - indicator.set_secondary_activate_target(toggleBrandingItem); - - indicator.connect("connection-changed", (indicator, connected) => { - print(`Signal "connection-changed" emitted. Connected: ${connected}`); - }); - indicator.connect("new-attention-icon", (indicator) => { - print(`Signal "new-attention-icon" emitted.`); - }); - indicator.connect("new-icon", (indicator) => { - let icon = ""; - if (indicator.get_status() == AppIndicator.IndicatorStatus.ATTENTION) - icon = indicator.get_attention_icon(); - else if (indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE) - icon = indicator.get_icon(); - - print(`Signal "new-icon" emitted. Icon: ${icon}`); - }); - indicator.connect("new-icon-theme-path", (indicator, path) => { - print(`Signal "new-icon-theme-path" emitted. Path: ${path}`); - }); - indicator.connect("new-label", (indicator, label, guide) => { - print(`Signal "new-label" emitted. Label: ${label}, Guide: ${guide}`); - }); - indicator.connect("new-status", (indicator, status) => { - print(`Signal "new-status" emitted. Status: ${status}`); - - toggleAttentionItem.block_signal_handler(toggleAttentionId); - toggleAttentionItem.set_active(status == 'NeedsAttention'); - toggleAttentionItem.unblock_signal_handler(toggleAttentionId); - }); - indicator.connect("scroll-event", (indicator, steps, direction) => { - print(`Signal "scroll-event" emitted. Steps: ${steps}, Direction: ${direction}`); - let currentIndex = iconsPool.indexOf(indicator.get_icon()); - let iconIndex; - - if (direction == ScrollType.UP) { - iconIndex = (currentIndex + 1) % iconsPool.length; - } else { - iconIndex = (currentIndex <= 0 ? iconsPool.length : currentIndex) - 1; - } - - indicator.set_icon(iconsPool[iconIndex]); - }); -}); -app.run(ARGV); + app.run(ARGV); })(); diff --git a/indicatorStatusIcon.js b/indicatorStatusIcon.js index 4b6f918..36d426a 100644 --- a/indicatorStatusIcon.js +++ b/indicatorStatusIcon.js @@ -13,6 +13,9 @@ // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +/* exported IndicatorStatusIcon */ + const Clutter = imports.gi.Clutter; const GObject = imports.gi.GObject; const St = imports.gi.St; @@ -20,13 +23,12 @@ const Main = imports.ui.main; const Panel = imports.ui.panel; const PanelMenu = imports.ui.panelMenu; -const PopupMenu = imports.ui.popupMenu; const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; const Extension = ExtensionUtils.getCurrentExtension(); -const AppIndicator = Extension.imports.appIndicator +const AppIndicator = Extension.imports.appIndicator; const DBusMenu = Extension.imports.dbusMenu; const Util = Extension.imports.util; @@ -34,7 +36,7 @@ * IndicatorStatusIcon implements an icon in the system status area */ var IndicatorStatusIcon = GObject.registerClass( -class AppIndicators_IndicatorStatusIcon extends PanelMenu.Button { +class AppIndicatorsIndicatorStatusIcon extends PanelMenu.Button { _init(indicator) { super._init(0.5, indicator.uniqueId); this._indicator = indicator; @@ -46,10 +48,10 @@ this._box.add_child(this._iconBox); - Util.connectSmart(this._indicator, 'ready', this, '_display') - Util.connectSmart(this._indicator, 'menu', this, '_updateMenu') - Util.connectSmart(this._indicator, 'label', this, '_updateLabel') - Util.connectSmart(this._indicator, 'status', this, '_updateStatus') + Util.connectSmart(this._indicator, 'ready', this, '_display'); + Util.connectSmart(this._indicator, 'menu', this, '_updateMenu'); + Util.connectSmart(this._indicator, 'label', this, '_updateLabel'); + Util.connectSmart(this._indicator, 'status', this, '_updateStatus'); Util.connectSmart(this._indicator, 'reset', this, () => { this._updateStatus(); this._updateLabel(); @@ -60,10 +62,10 @@ this._menuClient.destroy(); this._menuClient = null; } - }) + }); if (this._indicator.isReady) - this._display() + this._display(); } _updateLabel() { @@ -71,28 +73,27 @@ if (label) { if (!this._label || !this._labelBin) { this._labelBin = new St.Bin({ - y_align: ExtensionUtils.versionCheck(['3.34'], Config.PACKAGE_VERSION) ? - St.Align.MIDDLE : Clutter.ActorAlign.CENTER, + y_align: ExtensionUtils.versionCheck(['3.34'], Config.PACKAGE_VERSION) + ? St.Align.MIDDLE : Clutter.ActorAlign.CENTER, }); this._label = new St.Label(); this._labelBin.add_actor(this._label); this._box.add_actor(this._labelBin); } this._label.set_text(label); - if (!this._box.contains(this._labelBin)) this._box.add_actor(this._labelBin); //FIXME: why is it suddenly necessary? - } else { - if (this._label) { - this._labelBin.destroy_all_children(); - this._box.remove_actor(this._labelBin); - this._labelBin.destroy(); - delete this._labelBin; - delete this._label; - } + if (!this._box.contains(this._labelBin)) + this._box.add_actor(this._labelBin); // FIXME: why is it suddenly necessary? + } else if (this._label) { + this._labelBin.destroy_all_children(); + this._box.remove_actor(this._labelBin); + this._labelBin.destroy(); + delete this._labelBin; + delete this._label; } } _updateStatus() { - this.visible = this._indicator.status != AppIndicator.SNIStatus.PASSIVE; + this.visible = this._indicator.status !== AppIndicator.SNIStatus.PASSIVE; } _updateMenu() { @@ -104,7 +105,7 @@ if (this._indicator.menuPath) { this._menuClient = new DBusMenu.Client(this._indicator.busName, - this._indicator.menuPath); + this._indicator.menuPath); this._menuClient.attachToMenu(this.menu); } } @@ -114,7 +115,7 @@ this._updateStatus(); this._updateMenu(); - Main.panel.addToStatusArea("appindicator-"+this._indicator.uniqueId, this, 1, 'right') + Main.panel.addToStatusArea(`appindicator-${this._indicator.uniqueId}`, this, 1, 'right'); } vfunc_button_press_event(buttonEvent) { @@ -140,9 +141,9 @@ // event, and we can choose which one we interpret. if (scrollEvent.direction === Clutter.ScrollDirection.SMOOTH) { const event = Clutter.get_current_event(); - let [dx, dy] = event.get_scroll_delta() + let [dx, dy] = event.get_scroll_delta(); - this._indicator.scroll(dx, dy) + this._indicator.scroll(dx, dy); return Clutter.EVENT_STOP; } diff --git a/interfaces.js b/interfaces.js index 4372a16..9554524 100644 --- a/interfaces.js +++ b/interfaces.js @@ -14,31 +14,31 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -var StatusNotifierItem = loadInterfaceXml("StatusNotifierItem.xml") -const Properties = loadInterfaceXml("Properties.xml") -var StatusNotifierWatcher = loadInterfaceXml("StatusNotifierWatcher.xml") -var DBusMenu = loadInterfaceXml("DBusMenu.xml") +var StatusNotifierItem = loadInterfaceXml('StatusNotifierItem.xml'); +const Properties = loadInterfaceXml('Properties.xml'); +var StatusNotifierWatcher = loadInterfaceXml('StatusNotifierWatcher.xml'); +var DBusMenu = loadInterfaceXml('DBusMenu.xml'); // loads a xml file into an in-memory string function loadInterfaceXml(filename) { - let extension = imports.misc.extensionUtils.getCurrentExtension() + let extension = imports.misc.extensionUtils.getCurrentExtension(); - let interfaces_dir = extension.dir.get_child("interfaces-xml") + let interfaces_dir = extension.dir.get_child('interfaces-xml'); - let file = interfaces_dir.get_child(filename) + let file = interfaces_dir.get_child(filename); - let [ result, contents ] = imports.gi.GLib.file_get_contents(file.get_path()) + let [result, contents] = imports.gi.GLib.file_get_contents(file.get_path()); if (result) { - //HACK: The "" + trick is important as hell because file_get_contents returns + // HACK: The "" + trick is important as hell because file_get_contents returns // an object (WTF?) but Gio.makeProxyWrapper requires `typeof() == "string"` // Otherwise, it will try to check `instanceof XML` and fail miserably because there // is no `XML` on very recent SpiderMonkey releases (or, if SpiderMonkey is old enough, // will spit out a TypeError soon). if (contents instanceof Uint8Array) - contents = imports.byteArray.toString(contents); - return "" + contents + "" + contents = imports.byteArray.toString(contents); + return `${contents}`; } else { - throw new Error("AppIndicatorSupport: Could not load file: "+filename) + throw new Error(`AppIndicatorSupport: Could not load file: ${filename}`); } } diff --git a/lint/eslintrc-gjs.yml b/lint/eslintrc-gjs.yml new file mode 100644 index 0000000..58324e3 --- /dev/null +++ b/lint/eslintrc-gjs.yml @@ -0,0 +1,229 @@ +--- +env: + es6: true +extends: 'eslint:recommended' +rules: + array-bracket-newline: + - error + - consistent + array-bracket-spacing: + - error + - never + array-callback-return: error + arrow-parens: + - error + - as-needed + arrow-spacing: error + block-scoped-var: error + block-spacing: error + brace-style: error + # Waiting for this to have matured a bit in eslint + # camelcase: + # - error + # - properties: never + # allow: [^vfunc_, ^on_, _instance_init] + comma-dangle: + - error + - always-multiline + comma-spacing: + - error + - before: false + after: true + comma-style: + - error + - last + computed-property-spacing: error + curly: + - error + - multi-or-nest + - consistent + dot-location: + - error + - property + eol-last: error + eqeqeq: error + func-call-spacing: error + func-name-matching: error + func-style: + - error + - declaration + - allowArrowFunctions: true + indent: + - error + - 4 + - ignoredNodes: + # Allow not indenting the body of GObject.registerClass, since in the + # future it's intended to be a decorator + - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child' + # Allow dedenting chained member expressions + MemberExpression: 'off' + key-spacing: + - error + - beforeColon: false + afterColon: true + keyword-spacing: + - error + - before: true + after: true + linebreak-style: + - error + - unix + lines-between-class-members: error + max-nested-callbacks: error + max-statements-per-line: error + new-parens: error + no-array-constructor: error + no-await-in-loop: error + no-caller: error + no-constant-condition: + - error + - checkLoops: false + no-div-regex: error + no-empty: + - error + - allowEmptyCatch: true + no-extra-bind: error + no-extra-parens: + - error + - all + - conditionalAssign: false + nestedBinaryExpressions: false + returnAssign: false + no-implicit-coercion: + - error + - allow: + - '!!' + no-invalid-this: error + no-iterator: error + no-label-var: error + no-lonely-if: error + no-loop-func: error + no-nested-ternary: error + no-new-object: error + no-new-wrappers: error + no-octal-escape: error + no-proto: error + no-prototype-builtins: 'off' + no-restricted-properties: + - error + - object: Lang + property: bind + message: Use arrow notation or Function.prototype.bind() + - object: Lang + property: Class + message: Use ES6 classes + - object: imports + property: mainloop + message: Use GLib main loops and timeouts + no-restricted-syntax: + - error + - selector: >- + MethodDefinition[key.name="_init"] > + FunctionExpression[params.length=1] > + BlockStatement[body.length=1] + CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] > + Identifier:first-child + message: _init() that only calls super._init() is unnecessary + - selector: >- + MethodDefinition[key.name="_init"] > + FunctionExpression[params.length=0] > + BlockStatement[body.length=1] + CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"] + message: _init() that only calls super._init() is unnecessary + no-return-assign: error + no-return-await: error + no-self-compare: error + no-shadow: error + no-shadow-restricted-names: error + no-spaced-func: error + no-tabs: error + no-template-curly-in-string: error + no-throw-literal: error + no-trailing-spaces: error + no-undef-init: error + no-unneeded-ternary: error + no-unused-expressions: error + no-unused-vars: + - error + # Vars use a suffix _ instead of a prefix because of file-scope private vars + - varsIgnorePattern: (^unused|_$) + argsIgnorePattern: ^(unused|_) + no-useless-call: error + no-useless-computed-key: error + no-useless-concat: error + no-useless-constructor: error + no-useless-rename: error + no-useless-return: error + no-whitespace-before-property: error + no-with: error + nonblock-statement-body-position: + - error + - below + object-curly-newline: + - error + - consistent: true + object-curly-spacing: error + object-shorthand: error + operator-assignment: error + operator-linebreak: error + # These may be a bit controversial, we can try them out and enable them later + # prefer-const: error + # prefer-destructuring: error + prefer-numeric-literals: error + prefer-promise-reject-errors: error + prefer-rest-params: error + prefer-spread: error + prefer-template: error + quotes: + - error + - single + - avoidEscape: true + require-await: error + rest-spread-spacing: error + semi: + - error + - always + semi-spacing: + - error + - before: false + after: true + semi-style: error + space-before-blocks: error + space-before-function-paren: + - error + - named: never + # for `function ()` and `async () =>`, preserve space around keywords + anonymous: always + asyncArrow: always + space-in-parens: error + space-infix-ops: + - error + - int32Hint: false + space-unary-ops: error + spaced-comment: error + switch-colon-spacing: error + symbol-description: error + template-curly-spacing: error + template-tag-spacing: error + unicode-bom: error + valid-jsdoc: + - error + - requireReturn: false + wrap-iife: + - error + - inside + yield-star-spacing: error + yoda: error +globals: + ARGV: readonly + Debugger: readonly + GIRepositoryGType: readonly + globalThis: readonly + imports: readonly + Intl: readonly + log: readonly + logError: readonly + print: readonly + printerr: readonly +parserOptions: + ecmaVersion: 2020 diff --git a/lint/eslintrc-legacy.yml b/lint/eslintrc-legacy.yml new file mode 100644 index 0000000..55e9a2b --- /dev/null +++ b/lint/eslintrc-legacy.yml @@ -0,0 +1,14 @@ +rules: + eqeqeq: off + indent: + - error + - 4 + - ignoredNodes: + - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child' + CallExpression: + arguments: first + ArrayExpression: first + ObjectExpression: first + MemberExpression: off + prefer-template: off + quotes: off diff --git a/lint/eslintrc-shell.yml b/lint/eslintrc-shell.yml new file mode 100644 index 0000000..bb0636f --- /dev/null +++ b/lint/eslintrc-shell.yml @@ -0,0 +1,31 @@ +rules: + camelcase: + - error + - properties: never + allow: [^vfunc_, ^on_] + consistent-return: error + key-spacing: + - error + - mode: minimum + beforeColon: false + afterColon: true + object-curly-spacing: + - error + - always + prefer-arrow-callback: error + +overrides: + - files: js/** + excludedFiles: + - js/portalHelper/* + globals: + global: readonly + _: readonly + C_: readonly + N_: readonly + ngettext: readonly + - files: subprojects/extensions-app/js/** + globals: + _: readonly + C_: readonly + N_: readonly diff --git a/promiseUtils.js b/promiseUtils.js index f9f13d1..95c7e17 100644 --- a/promiseUtils.js +++ b/promiseUtils.js @@ -298,7 +298,7 @@ var _promisify = Gio._promisify; if (imports.system.version < 16501) { /* This is backported from upstream gjs, so that all the features are available */ - _promisify = function(proto, asyncFunc, finishFunc) { + _promisify = function (proto, asyncFunc, finishFunc) { if (proto[`_original_${asyncFunc}`] !== undefined) return; proto[`_original_${asyncFunc}`] = proto[asyncFunc]; @@ -307,7 +307,7 @@ return this[`_original_${asyncFunc}`](...args); return new Promise((resolve, reject) => { const callStack = new Error().stack.split('\n').filter(line => !line.match(/promisify/)).join('\n'); - this[`_original_${asyncFunc}`](...args, function (source, res) { + this[`_original_${asyncFunc}`](...args, (source, res) => { try { const result = source !== null && source[finishFunc] !== undefined ? source[finishFunc](res) @@ -325,7 +325,7 @@ }); }); }; - } + }; } if (!Promise.allSettled) { diff --git a/statusNotifierWatcher.js b/statusNotifierWatcher.js index 723f9bb..317064e 100644 --- a/statusNotifierWatcher.js +++ b/statusNotifierWatcher.js @@ -14,22 +14,22 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -const Gio = imports.gi.Gio -const GLib = imports.gi.GLib - -const Extension = imports.misc.extensionUtils.getCurrentExtension() - -const AppIndicator = Extension.imports.appIndicator -const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon -const Interfaces = Extension.imports.interfaces +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; + +const Extension = imports.misc.extensionUtils.getCurrentExtension(); + +const AppIndicator = Extension.imports.appIndicator; +const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon; +const Interfaces = Extension.imports.interfaces; const PromiseUtils = Extension.imports.promiseUtils; -const Util = Extension.imports.util +const Util = Extension.imports.util; // TODO: replace with org.freedesktop and /org/freedesktop when approved const KDE_PREFIX = 'org.kde'; -var WATCHER_BUS_NAME = KDE_PREFIX + '.StatusNotifierWatcher'; +var WATCHER_BUS_NAME = `${KDE_PREFIX}.StatusNotifierWatcher`; const WATCHER_OBJECT = '/StatusNotifierWatcher'; const DEFAULT_ITEM_OBJECT_PATH = '/StatusNotifierItem'; @@ -43,12 +43,12 @@ this._watchDog = watchDog; this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this); this._dbusImpl.export(Gio.DBus.session, WATCHER_OBJECT); - this._cancellable = new Gio.Cancellable; + this._cancellable = new Gio.Cancellable(); this._everAcquiredName = false; this._ownName = Gio.DBus.session.own_name(WATCHER_BUS_NAME, - Gio.BusNameOwnerFlags.NONE, - this._acquiredName.bind(this), - this._lostName.bind(this)); + Gio.BusNameOwnerFlags.NONE, + this._acquiredName.bind(this), + this._lostName.bind(this)); this._items = new Map(); this._seekStatusNotifierItems(); @@ -60,9 +60,9 @@ _lostName() { if (this._everAcquiredName) - Util.Logger.debug('Lost name' + WATCHER_BUS_NAME); + Util.Logger.debug(`Lost name${WATCHER_BUS_NAME}`); else - Util.Logger.warn('Failed to acquire ' + WATCHER_BUS_NAME); + Util.Logger.warn(`Failed to acquire ${WATCHER_BUS_NAME}`); this._watchDog.nameAcquired = false; } @@ -92,7 +92,7 @@ GLib.PRIORITY_DEFAULT, this._cancellable); if (!indicator.hasNameOwner) this._itemVanished(id); - }; + } }); // if the desktop is not ready delay the icon creation and signal emissions @@ -116,13 +116,13 @@ let item = this._items.get(id); if (item) { - //delete the old one and add the new indicator + // delete the old one and add the new indicator Util.Logger.debug(`Attempting to re-register ${id}; resetting instead`); item.reset(); return; } - this._registerItem(service, bus_name, obj_path) + this._registerItem(service, bus_name, obj_path); } async _seekStatusNotifierItems() { @@ -171,12 +171,12 @@ } if (!bus_name || !obj_path) { - let error = "Impossible to register an indicator for parameters '"+ - service.toString()+"'"; + let error = `Impossible to register an indicator for parameters '${ + service.toString()}'`; Util.Logger.warn(error); invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError', - error); + error); return; } @@ -187,9 +187,9 @@ _itemVanished(id) { // FIXME: this is useless if the path name disappears while the bus stays alive (not unheard of) - if (this._items.has(id)) { + if (this._items.has(id)) this._remove(id); - } + } _remove(id) { diff --git a/util.js b/util.js index d5db6cb..a43606f 100644 --- a/util.js +++ b/util.js @@ -18,19 +18,19 @@ introspectBusObject, dbusNodeImplementsInterfaces, waitForStartupCompletion, BUS_ADDRESS_REGEX */ -const Gio = imports.gi.Gio -const GLib = imports.gi.GLib +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; const Gdk = imports.gi.Gdk; const Main = imports.ui.main; -const GObject = imports.gi.GObject +const GObject = imports.gi.GObject; const Extension = imports.misc.extensionUtils.getCurrentExtension(); const Params = imports.misc.params; const PromiseUtils = Extension.imports.promiseUtils; -const Signals = imports.signals - -var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/ +const Signals = imports.signals; + +var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/; PromiseUtils._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish'); @@ -44,13 +44,13 @@ let cancellable = cancelRefreshPropertyOnProxy(proxy, { propertyName, - addNew: true + addNew: true, }); try { const [valueVariant] = (await proxy.g_connection.call(proxy.g_name, proxy.g_object_path, 'org.freedesktop.DBus.Properties', 'Get', - GLib.Variant.new('(ss)', [ proxy.g_interface_name, propertyName ]), + GLib.Variant.new('(ss)', [proxy.g_interface_name, propertyName]), GLib.VariantType.new('(v)'), Gio.DBusCallFlags.NONE, -1, cancellable)).deep_unpack(); @@ -60,7 +60,7 @@ proxy.get_cached_property(propertyName).equal(valueVariant)) return; - proxy.set_cached_property(propertyName, valueVariant) + proxy.set_cached_property(propertyName, valueVariant); // synthesize a batched property changed event if (!proxy._proxyChangedProperties) @@ -85,7 +85,7 @@ } } -var cancelRefreshPropertyOnProxy = function(proxy, params) { +var cancelRefreshPropertyOnProxy = function (proxy, params) { if (!proxy._proxyCancellables) return; @@ -113,7 +113,7 @@ delete proxy._proxyChangedProperties; delete proxy._proxyCancellables; } -} +}; async function getUniqueBusName(bus, name, cancellable) { if (name[0] == ':') @@ -155,7 +155,7 @@ async function introspectBusObject(bus, name, cancellable, path = undefined) { if (!path) - path = "/"; + path = '/'; const [introspection] = (await bus.call(name, path, 'org.freedesktop.DBus.Introspectable', 'Introspect', null, new GLib.VariantType('(s)'), Gio.DBusCallFlags.NONE, @@ -183,7 +183,7 @@ return nodes; } -var dbusNodeImplementsInterfaces = function(node_info, interfaces) { +var dbusNodeImplementsInterfaces = function (node_info, interfaces) { if (!(node_info instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces)) return false; @@ -193,13 +193,13 @@ } return false; -} +}; var NameWatcher = class AppIndicatorsNameWatcher { constructor(name) { this._watcherId = Gio.DBus.session.watch_name(name, Gio.BusNameWatcherFlags.NONE, () => { - this._nameOnBus = true + this._nameOnBus = true; Logger.debug(`Name ${name} appeared`); this.emit('changed'); this.emit('appeared'); @@ -224,36 +224,38 @@ }; Signals.addSignalMethods(NameWatcher.prototype); -const connectSmart3A = function(src, signal, handler) { - let id = src.connect(signal, handler) +const connectSmart3A = function (src, signal, handler) { + let id = src.connect(signal, handler); if (src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src))) { let destroy_id = src.connect('destroy', () => { - src.disconnect(id) - src.disconnect(destroy_id) - }) - } -} - -const connectSmart4A = function(src, signal, target, method) { + src.disconnect(id); + src.disconnect(destroy_id); + }); + } +}; + +const connectSmart4A = function (src, signal, target, method) { if (typeof method === 'string') - method = target[method].bind(target) + method = target[method].bind(target); if (typeof method === 'function') - method = method.bind(target) - - let signal_id = src.connect(signal, method) + method = method.bind(target); + + let signal_id = src.connect(signal, method); // GObject classes might or might not have a destroy signal // JS Classes will not complain when connecting to non-existent signals - let src_destroy_id = src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src)) ? src.connect('destroy', on_destroy) : 0 - let tgt_destroy_id = target.connect && (!(target instanceof GObject.Object) || GObject.signal_lookup('destroy', target)) ? target.connect('destroy', on_destroy) : 0 + let src_destroy_id = src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src)) ? src.connect('destroy', on_destroy) : 0; + let tgt_destroy_id = target.connect && (!(target instanceof GObject.Object) || GObject.signal_lookup('destroy', target)) ? target.connect('destroy', on_destroy) : 0; function on_destroy() { - src.disconnect(signal_id) - if (src_destroy_id) src.disconnect(src_destroy_id) - if (tgt_destroy_id) target.disconnect(tgt_destroy_id) - } -} + src.disconnect(signal_id); + if (src_destroy_id) + src.disconnect(src_destroy_id); + if (tgt_destroy_id) + target.disconnect(tgt_destroy_id); + } +}; /** * Connect signals to slots, and remove the connection when either source or @@ -264,12 +266,12 @@ * or * Util.connectSmart(srcOb, 'signal', () => { ... }) */ -var connectSmart = function() { +var connectSmart = function () { if (arguments.length == 4) - return connectSmart4A.apply(null, arguments) + return connectSmart4A.apply(null, arguments); else - return connectSmart3A.apply(null, arguments) -} + return connectSmart3A.apply(null, arguments); +}; // eslint-disable-next-line valid-jsdoc /**