Codebase list gnome-shell-extension-appindicator / f5fe477
cleanup: Cleanup code to match eslint rules Marco Trevisan (TreviƱo) 3 years ago
9 changed file(s) with 258 addition(s) and 268 deletion(s). Raw diff Collapse all Expand all
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1515
16 const Clutter = imports.gi.Clutter;
17 const Cogl = imports.gi.Cogl;
16 /* exported AppIndicator, IconActor */
17
1818 const GdkPixbuf = imports.gi.GdkPixbuf;
1919 const Gio = imports.gi.Gio;
2020 const GLib = imports.gi.GLib;
2121 const GObject = imports.gi.GObject;
2222 const Gtk = imports.gi.Gtk;
2323 const St = imports.gi.St;
24 const Shell = imports.gi.Shell;
2524
2625 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2726 const Signals = imports.signals;
2827
29 const DBusMenu = Extension.imports.dbusMenu;
3028 const IconCache = Extension.imports.iconCache;
3129 const Util = Extension.imports.util;
3230 const Interfaces = Extension.imports.interfaces;
4038
4139 const MAX_UPDATE_FREQUENCY = 100; // In ms
4240
41 // eslint-disable-next-line no-unused-vars
4342 const SNICategory = {
4443 APPLICATION: 'ApplicationStatus',
4544 COMMUNICATIONS: 'Communications',
6362 * the AppIndicator class serves as a generic container for indicator information and functions common
6463 * for every displaying implementation (IndicatorMessageSource and IndicatorStatusIcon)
6564 */
66 var AppIndicator = class AppIndicators_AppIndicator {
67
68 constructor(service, bus_name, object) {
69 this.busName = bus_name;
70 this._uniqueId = bus_name + object;
65 var AppIndicator = class AppIndicatorsAppIndicator {
66
67 constructor(service, busName, object) {
68 this.busName = busName;
69 this._uniqueId = busName + object;
7170 this._accumulatedSignals = new Set();
7271
73 let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem);
72 const interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem);
7473
7574 // HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need
7675 // to specify G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES
7776 this._cancellable = new Gio.Cancellable();
7877 this._proxy = new Gio.DBusProxy({ g_connection: Gio.DBus.session,
79 g_interface_name: interface_info.name,
80 g_interface_info: interface_info,
81 g_name: bus_name,
78 g_interface_name: interfaceInfo.name,
79 g_interface_info: interfaceInfo,
80 g_name: busName,
8281 g_object_path: object,
8382 g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES });
8483
8786 Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal);
8887 Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged');
8988
90 if (service !== bus_name && service.match(Util.BUS_ADDRESS_REGEX)) {
89 if (service !== busName && service.match(Util.BUS_ADDRESS_REGEX)) {
9190 this._uniqueId = service;
9291 this._nameWatcher = new Util.NameWatcher(service);
9392 Util.connectSmart(this._nameWatcher, 'changed', () => this._nameOwnerChanged());
101100 this._checkMenuReady();
102101 } catch (e) {
103102 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
104 Util.Logger.warn(`While initalizing proxy for ${bus_name} ${object}: ${e}`);
103 Util.Logger.warn(`While initalizing proxy for ${this._uniqueId}: ${e}`);
105104 }
106105 }
107106
136135 for (let checks = 0; checks < 3 && !this.isReady; ++checks) {
137136 this._delayCheck = new PromiseUtils.TimeoutSecondsPromise(1,
138137 GLib.PRIORITY_DEFAULT_IDLE, cancellable);
138 // eslint-disable-next-line no-await-in-loop
139139 await this._delayCheck;
140140 Util.refreshPropertyOnProxy(this._proxy, 'Menu');
141141 }
174174 let interfaceProps = this._proxy.g_interface_info.properties;
175175 this._proxyPropertyList =
176176 (this._proxy.get_cached_property_names() || []).filter(p =>
177 interfaceProps.some(propinfo => propinfo.name == p));
177 interfaceProps.some(propinfo => propinfo.name === p));
178178
179179 if (this._proxyPropertyList.length) {
180180 this._addExtraProperty('XAyatanaLabel');
242242 }
243243
244244 get menuPath() {
245 if (this._proxy.Menu == '/NO_DBUSMENU')
245 if (this._proxy.Menu === '/NO_DBUSMENU')
246246 return null;
247247
248248 return this._proxy.Menu || '/MenuBar';
281281 return this._cancellable;
282282 }
283283
284 _onPropertiesChanged(proxy, changed, invalidated) {
284 _onPropertiesChanged(_proxy, changed, _invalidated) {
285285 let props = Object.keys(changed.unpack());
286286 let signalsToEmit = new Set();
287287
290290 // a few need to be passed down to the displaying code
291291
292292 // all these can mean that the icon has to be changed
293 if (property == 'Status' ||
293 if (property === 'Status' ||
294294 property.startsWith('Icon') ||
295295 property.startsWith('AttentionIcon'))
296296 signalsToEmit.add('icon');
301301 signalsToEmit.add('overlay-icon');
302302
303303 // this may make all of our icons invalid
304 if (property == 'IconThemePath') {
304 if (property === 'IconThemePath') {
305305 signalsToEmit.add('icon');
306306 signalsToEmit.add('overlay-icon');
307307 }
308308
309309 // the label will be handled elsewhere
310 if (property == 'XAyatanaLabel')
310 if (property === 'XAyatanaLabel')
311311 signalsToEmit.add('label');
312312
313 if (property == 'Menu') {
313 if (property === 'Menu') {
314314 if (!this._checkIfReady() && this.isReady)
315315 signalsToEmit.add('menu');
316316 }
317317
318318 // status updates may cause the indicator to be hidden
319 if (property == 'Status')
319 if (property === 'Status')
320320 signalsToEmit.add('status');
321321 });
322322
332332
333333 this.disconnectAll();
334334 this._cancellable.cancel();
335 this._nameWatcher && this._nameWatcher.destroy();
336335 Util.cancelRefreshPropertyOnProxy(this._proxy);
336 if (this._nameWatcher)
337 this._nameWatcher.destroy();
337338 delete this._cancellable;
338339 delete this._proxy;
340 delete this._nameWatcher;
339341 }
340342
341343 open() {
351353 }
352354
353355 scroll(dx, dy) {
354 if (dx != 0)
356 if (dx !== 0)
355357 this._proxy.ScrollRemote(Math.floor(dx), 'horizontal');
356358
357 if (dy != 0)
359 if (dy !== 0)
358360 this._proxy.ScrollRemote(Math.floor(dy), 'vertical');
359361 }
360362 };
361363 Signals.addSignalMethods(AppIndicator.prototype);
362364
363365 var IconActor = GObject.registerClass(
364 class AppIndicators_IconActor extends St.Icon {
365
366 _init(indicator, icon_size) {
366 class AppIndicatorsIconActor extends St.Icon {
367
368 _init(indicator, iconSize) {
367369 super._init({
368370 reactive: true,
369371 style_class: 'system-status-icon',
374376 this.add_style_class_name('appindicator-icon');
375377 this.set_style('padding:0');
376378
379 // eslint-disable-next-line no-undef
377380 let themeContext = St.ThemeContext.get_for_stage(global.stage);
378 this.height = icon_size * themeContext.scale_factor;
381 this.height = iconSize * themeContext.scale_factor;
379382
380383 this._indicator = indicator;
381 this._iconSize = icon_size;
384 this._iconSize = iconSize;
382385 this._iconCache = new IconCache.IconCache();
383386 this._cancellable = new Gio.Cancellable();
384387 this._loadingIcons = new Set();
388391 Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon');
389392
390393 Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => {
391 this.height = icon_size * tc.scale_factor;
394 this.height = iconSize * tc.scale_factor;
392395 this._invalidateIcon();
393396 });
394397
424427 // Will look the icon up in the cache, if it's found
425428 // it will return it. Otherwise, it will create it and cache it.
426429 async _cacheOrCreateIconByName(iconSize, iconName, themePath) {
427 let { scale_factor } = St.ThemeContext.get_for_stage(global.stage);
428 let id = `${iconName}@${iconSize * scale_factor}${themePath || ''}`;
430 // eslint-disable-next-line no-undef
431 let { scale_factor: scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
432 let id = `${iconName}@${iconSize * scaleFactor}${themePath || ''}`;
429433 let gicon = this._iconCache.get(id);
430434
431435 if (gicon)
440444 }
441445
442446 this._loadingIcons.add(id);
443 let path = this._getIconInfo(iconName, themePath, iconSize, scale_factor);
447 let path = this._getIconInfo(iconName, themePath, iconSize, scaleFactor);
444448 gicon = await this._createIconByName(path);
445449 this._loadingIcons.delete(id);
446450 if (gicon)
514518
515519 _getIconInfo(name, themePath, size, scale) {
516520 let path = null;
517 if (name && name[0] == '/') {
521 if (name && name[0] === '/') {
518522 // HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it.
519523 path = name;
520524 } else if (name) {
528532 let iconInfo = null;
529533
530534 // we try to avoid messing with the default icon theme, so we'll create a new one if needed
531 let icon_theme = null;
535 let iconTheme = null;
532536 if (themePath) {
533 icon_theme = new Gtk.IconTheme();
534 Gtk.IconTheme.get_default().get_search_path().forEach(path => {
535 icon_theme.append_search_path(path);
536 });
537 icon_theme.append_search_path(themePath);
538 icon_theme.set_screen(imports.gi.Gdk.Screen.get_default());
537 iconTheme = new Gtk.IconTheme();
538 Gtk.IconTheme.get_default().get_search_path().forEach(p =>
539 iconTheme.append_search_path(p));
540 iconTheme.append_search_path(themePath);
541 iconTheme.set_screen(imports.gi.Gdk.Screen.get_default());
539542 } else {
540 icon_theme = Gtk.IconTheme.get_default();
541 }
542 if (icon_theme) {
543 iconTheme = Gtk.IconTheme.get_default();
544 }
545 if (iconTheme) {
543546 // try to look up the icon in the icon theme
544 iconInfo = icon_theme.lookup_icon_for_scale(name, size, scale,
547 iconInfo = iconTheme.lookup_icon_for_scale(name, size, scale,
545548 Gtk.IconLookupFlags.GENERIC_FALLBACK);
546549 // no icon? that's bad!
547550 if (iconInfo === null) {
588591 }
589592
590593 async _createIconFromPixmap(iconSize, iconPixmapArray) {
591 let { scale_factor } = St.ThemeContext.get_for_stage(global.stage);
592 iconSize *= scale_factor;
594 // eslint-disable-next-line no-undef
595 const { scale_factor: scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
596 iconSize *= scaleFactor;
593597 // the pixmap actually is an array of pixmaps with different sizes
594598 // we use the one that is smaller or equal the iconSize
595599
647651 // So when it's not need anymore we make sure to check the .inUse property
648652 // and set it to false so that it can be picked up by the garbage collector.
649653 _setGicon(iconType, gicon) {
650 if (iconType != SNIconType.OVERLAY) {
654 if (iconType !== SNIconType.OVERLAY) {
651655 if (gicon) {
652656 this.gicon = new Gio.EmblemedIcon({ gicon });
653657
654658 if (!(gicon instanceof GdkPixbuf.Pixbuf))
655 gicon.inUse = this.gicon.get_icon() == gicon;
659 gicon.inUse = this.gicon.get_icon() === gicon;
656660 } else {
657661 this.gicon = null;
658662 Util.Logger.critical(`unable to update icon for ${this._indicator.id}`);
720724 }
721725
722726 // we might need to use the AttentionIcon*, which have precedence over the normal icons
723 let iconType = this._indicator.status == SNIStatus.NEEDS_ATTENTION
727 let iconType = this._indicator.status === SNIStatus.NEEDS_ATTENTION
724728 ? SNIconType.ATTENTION : SNIconType.NORMAL;
725729
726730 this._updateIconByType(iconType, this._iconSize);
1212 // You should have received a copy of the GNU General Public License
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 const Atk = imports.gi.Atk;
16 const Clutter = imports.gi.Clutter;
1715 const Gio = imports.gi.Gio;
1816 const GLib = imports.gi.GLib;
1917 const GdkPixbuf = imports.gi.GdkPixbuf;
3533 /**
3634 * Saves menu property values and handles type checking and defaults
3735 */
38 var PropertyStore = class AppIndicators_PropertyStore {
39
40 constructor(initial_properties) {
36 var PropertyStore = class AppIndicatorsPropertyStore {
37
38 constructor(initialProperties) {
4139 this._props = new Map();
4240
43 if (initial_properties) {
44 for (let i in initial_properties)
45 this.set(i, initial_properties[i]);
41 if (initialProperties) {
42 for (let i in initialProperties)
43 this.set(i, initialProperties[i]);
4644
4745 }
4846 }
9189 /**
9290 * Represents a single menu item
9391 */
94 var DbusMenuItem = class AppIndicators_DbusMenuItem {
92 var DbusMenuItem = class AppIndicatorsDbusMenuItem {
9593
9694 // will steal the properties object
97 constructor(client, id, properties, children_ids) {
95 constructor(client, id, properties, childrenIds) {
9896 this._client = client;
9997 this._id = id;
10098 this._propStore = new PropertyStore(properties);
101 this._children_ids = children_ids;
102 }
103
104 property_get(prop_name) {
105 let prop = this.property_get_variant(prop_name);
99 this._children_ids = childrenIds;
100 }
101
102 propertyGet(propName) {
103 let prop = this.propertyGetVariant(propName);
106104 return prop ? prop.get_string()[0] : null;
107105 }
108106
109 property_get_variant(prop_name) {
110 return this._propStore.get(prop_name);
111 }
112
113 property_get_bool(prop_name) {
114 let prop = this.property_get_variant(prop_name);
107 propertyGetVariant(propName) {
108 return this._propStore.get(propName);
109 }
110
111 propertyGetBool(propName) {
112 let prop = this.propertyGetVariant(propName);
115113 return prop ? prop.get_boolean() : false;
116114 }
117115
118 property_get_int(prop_name) {
119 let prop = this.property_get_variant(prop_name);
116 propertyGetInt(propName) {
117 let prop = this.propertyGetVariant(propName);
120118 return prop ? prop.get_int32() : 0;
121119 }
122120
123 property_set(prop, value) {
121 propertySet(prop, value) {
124122 this._propStore.set(prop, value);
125123
126 this.emit('property-changed', prop, this.property_get_variant(prop));
127 }
128
129 get_children_ids() {
124 this.emit('property-changed', prop, this.propertyGetVariant(prop));
125 }
126
127 getChildrenIds() {
130128 return this._children_ids.concat(); // clone it!
131129 }
132130
133 add_child(pos, child_id) {
134 this._children_ids.splice(pos, 0, child_id);
135 this.emit('child-added', this._client.get_item(child_id), pos);
136 }
137
138 remove_child(child_id) {
131 addChild(pos, childId) {
132 this._children_ids.splice(pos, 0, childId);
133 this.emit('child-added', this._client.getItem(childId), pos);
134 }
135
136 removeChild(childId) {
139137 // find it
140138 let pos = -1;
141139 for (let i = 0; i < this._children_ids.length; ++i) {
142 if (this._children_ids[i] == child_id) {
140 if (this._children_ids[i] === childId) {
143141 pos = i;
144142 break;
145143 }
149147 Util.Logger.critical("Trying to remove child which doesn't exist");
150148 } else {
151149 this._children_ids.splice(pos, 1);
152 this.emit('child-removed', this._client.get_item(child_id));
153 }
154 }
155
156 move_child(child_id, newpos) {
150 this.emit('child-removed', this._client.getItem(childId));
151 }
152 }
153
154 moveChild(childId, newPos) {
157155 // find the old position
158 let oldpos = -1;
156 let oldPos = -1;
159157 for (let i = 0; i < this._children_ids.length; ++i) {
160 if (this._children_ids[i] == child_id) {
161 oldpos = i;
158 if (this._children_ids[i] === childId) {
159 oldPos = i;
162160 break;
163161 }
164162 }
165163
166 if (oldpos < 0) {
164 if (oldPos < 0) {
167165 Util.Logger.critical("tried to move child which wasn't in the list");
168166 return;
169167 }
170168
171 if (oldpos != newpos) {
172 this._children_ids.splice(oldpos, 1);
173 this._children_ids.splice(newpos, 0, child_id);
174 this.emit('child-moved', oldpos, newpos, this._client.get_item(child_id));
175 }
176 }
177
178 get_children() {
179 return this._children_ids.map(el => this._client.get_item(el));
180 }
181
182 handle_event(event, data, timestamp) {
169 if (oldPos !== newPos) {
170 this._children_ids.splice(oldPos, 1);
171 this._children_ids.splice(newPos, 0, childId);
172 this.emit('child-moved', oldPos, newPos, this._client.getItem(childId));
173 }
174 }
175
176 getChildren() {
177 return this._children_ids.map(el => this._client.getItem(el));
178 }
179
180 handleEvent(event, data, timestamp) {
183181 if (!data)
184182 data = GLib.Variant.new_int32(0);
185183
186 this._client.send_event(this._id, event, data, timestamp);
187 }
188
189 get_id() {
184 this._client.sendEvent(this._id, event, data, timestamp);
185 }
186
187 getId() {
190188 return this._id;
191189 }
192190
193 send_about_to_show() {
194 this._client.send_about_to_show(this._id);
191 sendAboutToShow() {
192 this._client.sendAboutToShow(this._id);
195193 }
196194 };
197195 Signals.addSignalMethods(DbusMenuItem.prototype);
202200 /**
203201 * The client does the heavy lifting of actually reading layouts and distributing events
204202 */
205 var DBusClient = class AppIndicators_DBusClient {
203 var DBusClient = class AppIndicatorsDBusClient {
206204
207205 constructor(busName, busPath) {
208206 this._cancellable = new Gio.Cancellable();
238236 return !!this._proxy.g_name_owner;
239237 }
240238
241 get_root() {
239 getRoot() {
242240 return this._items.get(0);
243241 }
244242
286284 return;
287285
288286 for (let prop in properties)
289 item.property_set(prop, properties[prop]);
287 item.propertySet(prop, properties[prop]);
290288 });
291289 }
292290
297295
298296 let toTraverse = [0];
299297 while (toTraverse.length > 0) {
300 let item = this.get_item(toTraverse.shift());
298 let item = this.getItem(toTraverse.shift());
301299 item._dbusClientGcTag = tag;
302 Array.prototype.push.apply(toTraverse, item.get_children_ids());
300 Array.prototype.push.apply(toTraverse, item.getChildrenIds());
303301 }
304302
305303 this._items.forEach((i, id) => {
306 if (i._dbusClientGcTag != tag)
304 if (i._dbusClientGcTag !== tag)
307305 this._items.delete(id);
308306 });
309307 }
329327 return;
330328 }
331329
332 let [revision, root] = result;
330 let [revision_, root] = result;
333331 this._doLayoutUpdate(root);
334332 this._gcItems();
335333
353351 if (menuItem) {
354352 // we do, update our properties if necessary
355353 for (let prop in properties)
356 menuItem.property_set(prop, properties[prop]);
354 menuItem.propertySet(prop, properties[prop]);
357355
358356
359357 // make sure our children are all at the right place, and exist
360 let oldChildrenIds = menuItem.get_children_ids();
358 let oldChildrenIds = menuItem.getChildrenIds();
361359 for (let i = 0; i < childrenIds.length; ++i) {
362360 // try to recycle an old child
363361 let oldChild = -1;
364362 for (let j = 0; j < oldChildrenIds.length; ++j) {
365 if (oldChildrenIds[j] == childrenIds[i]) {
363 if (oldChildrenIds[j] === childrenIds[i]) {
366364 oldChild = oldChildrenIds.splice(j, 1)[0];
367365 break;
368366 }
370368
371369 if (oldChild < 0) {
372370 // no old child found, so create a new one!
373 menuItem.add_child(i, childrenIds[i]);
371 menuItem.addChild(i, childrenIds[i]);
374372 } else {
375373 // old child found, reuse it!
376 menuItem.move_child(childrenIds[i], i);
374 menuItem.moveChild(childrenIds[i], i);
377375 }
378376 }
379377
380378 // remove any old children that weren't reused
381 oldChildrenIds.forEach(child_id => menuItem.remove_child(child_id));
379 oldChildrenIds.forEach(c => menuItem.removeChild(c));
382380 } else {
383381 // we don't, so let's create us
384382 this._items.set(id, new DbusMenuItem(this, id, properties, childrenIds));
402400 this._proxy.connectSignal('ItemsPropertiesUpdated', this._onPropertiesUpdated.bind(this));
403401 }
404402
405 get_item(id) {
403 getItem(id) {
406404 let item = this._items.get(id);
407405 if (!item)
408406 Util.Logger.warn(`trying to retrieve item for non-existing id ${id} !?`);
410408 }
411409
412410 // we don't need to cache and burst-send that since it will not happen that frequently
413 send_about_to_show(id) {
411 sendAboutToShow(id) {
414412 /* Some indicators (you, dropbox!) don't use the right signature
415413 * and don't return a boolean, so we need to support both cases */
416414 let connection = this._proxy.get_connection();
431429 });
432430 }
433431
434 send_event(id, event, params, timestamp) {
432 sendEvent(id, event, params, timestamp) {
435433 if (!this._proxy)
436434 return;
437435
450448 return;
451449
452450 for (let prop in props)
453 item.property_set(prop, props[prop]);
451 item.propertySet(prop, props[prop]);
454452 });
455453 removed.forEach(([id, propNames]) => {
456454 let item = this._items.get(id);
457455 if (!item)
458456 return;
459457
460 propNames.forEach(propName => item.property_set(propName, null));
458 propNames.forEach(propName => item.propertySet(propName, null));
461459 });
462460 }
463461
492490 const MenuItemFactory = {
493491 createItem(client, dbusItem) {
494492 // first, decide whether it's a submenu or not
495 if (dbusItem.property_get('children-display') == 'submenu')
496 var shellItem = new PopupMenu.PopupSubMenuMenuItem('FIXME');
497 else if (dbusItem.property_get('type') == 'separator')
498 var shellItem = new PopupMenu.PopupSeparatorMenuItem('');
493 let shellItem;
494 if (dbusItem.propertyGet('children-display') === 'submenu')
495 shellItem = new PopupMenu.PopupSubMenuMenuItem('FIXME');
496 else if (dbusItem.propertyGet('type') === 'separator')
497 shellItem = new PopupMenu.PopupSeparatorMenuItem('');
499498 else
500 var shellItem = new PopupMenu.PopupMenuItem('FIXME');
499 shellItem = new PopupMenu.PopupMenuItem('FIXME');
501500
502501 shellItem._dbusItem = dbusItem;
503502 shellItem._dbusClient = client;
517516
518517 // initially create children
519518 if (shellItem instanceof PopupMenu.PopupSubMenuMenuItem) {
520 let children = dbusItem.get_children();
519 let children = dbusItem.getChildren();
521520 for (let i = 0; i < children.length; ++i)
522521 shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i]));
523522
550549 menu._parent._openedSubMenu = menu;
551550 }
552551
553 this._dbusItem.handle_event('opened', null, 0);
554 this._dbusItem.send_about_to_show();
552 this._dbusItem.handleEvent('opened', null, 0);
553 this._dbusItem.sendAboutToShow();
555554 } else {
556555 if (NEED_NESTED_SUBMENU_FIX) {
557556 // close our own submenus
559558 menu._openedSubMenu.close(false);
560559 }
561560
562 this._dbusItem.handle_event('closed', null, 0);
561 this._dbusItem.handleEvent('closed', null, 0);
563562 }
564563 },
565564
566565 _onActivate() {
567 this._dbusItem.handle_event('clicked', GLib.Variant.new('i', 0), 0);
568 },
569
570 _onPropertyChanged(dbusItem, prop, value) {
571 if (prop == 'toggle-type' || prop == 'toggle-state')
566 this._dbusItem.handleEvent('clicked', GLib.Variant.new('i', 0), 0);
567 },
568
569 _onPropertyChanged(dbusItem, prop, _value) {
570 if (prop === 'toggle-type' || prop === 'toggle-state')
572571 MenuItemFactory._updateOrnament.call(this);
573 else if (prop == 'label')
572 else if (prop === 'label')
574573 MenuItemFactory._updateLabel.call(this);
575 else if (prop == 'enabled')
574 else if (prop === 'enabled')
576575 MenuItemFactory._updateSensitive.call(this);
577 else if (prop == 'visible')
576 else if (prop === 'visible')
578577 MenuItemFactory._updateVisible.call(this);
579 else if (prop == 'icon-name' || prop == 'icon-data')
578 else if (prop === 'icon-name' || prop === 'icon-data')
580579 MenuItemFactory._updateImage.call(this);
581 else if (prop == 'type' || prop == 'children-display')
580 else if (prop === 'type' || prop === 'children-display')
582581 MenuItemFactory._replaceSelf.call(this);
583 // else
584 // Util.Logger.debug("Unhandled property change: "+prop)
582 else
583 Util.Logger.debug(`Unhandled property change: ${prop}`);
585584 },
586585
587586 _onChildAdded(dbusItem, child, position) {
600599 } else {
601600 // find it!
602601 this.menu._getMenuItems().forEach(item => {
603 if (item._dbusItem == child)
602 if (item._dbusItem === child)
604603 item.destroy();
605604 });
606605 }
616615 },
617616
618617 _updateLabel() {
619 let label = this._dbusItem.property_get('label').replace(/_([^_])/, '$1');
618 let label = this._dbusItem.propertyGet('label').replace(/_([^_])/, '$1');
620619
621620 if (this.label) // especially on GS3.8, the separator item might not even have a hidden label
622621 this.label.set_text(label);
626625 if (!this.setOrnament)
627626 return; // separators and alike might not have gotten the polyfill
628627
629 if (this._dbusItem.property_get('toggle-type') == 'checkmark' && this._dbusItem.property_get_int('toggle-state'))
628 if (this._dbusItem.propertyGet('toggle-type') === 'checkmark' && this._dbusItem.propertyGetInt('toggle-state'))
630629 this.setOrnament(PopupMenu.Ornament.CHECK);
631 else if (this._dbusItem.property_get('toggle-type') == 'radio' && this._dbusItem.property_get_int('toggle-state'))
630 else if (this._dbusItem.propertyGet('toggle-type') === 'radio' && this._dbusItem.propertyGetInt('toggle-state'))
632631 this.setOrnament(PopupMenu.Ornament.DOT);
633632 else
634633 this.setOrnament(PopupMenu.Ornament.NONE);
638637 if (!this._icon)
639638 return; // might be missing on submenus / separators
640639
641 let iconName = this._dbusItem.property_get('icon-name');
642 let iconData = this._dbusItem.property_get_variant('icon-data');
640 let iconName = this._dbusItem.propertyGet('icon-name');
641 let iconData = this._dbusItem.propertyGetVariant('icon-data');
643642 if (iconName)
644643 this._icon.icon_name = iconName;
645644 else if (iconData)
647646 },
648647
649648 _updateVisible() {
650 this.visible = this._dbusItem.property_get_bool('visible');
649 this.visible = this._dbusItem.propertyGetBool('visible');
651650 },
652651
653652 _updateSensitive() {
654 this.setSensitive(this._dbusItem.property_get_bool('enabled'));
653 this.setSensitive(this._dbusItem.propertyGetBool('enabled'));
655654 },
656655
657656 _replaceSelf(newSelf) {
689688 // First, find our wrapper. Children tend to lie. We do not trust the old positioning.
690689 let family = menu._getMenuItems();
691690 for (let i = 0; i < family.length; ++i) {
692 if (family[i]._dbusItem == dbusItem) {
691 if (family[i]._dbusItem === dbusItem) {
693692 // now, remove it
694693 menu.box.remove_child(family[i]);
695694
696695 // and add it again somewhere else
697 if (newpos < family.length && family[newpos] != family[i])
696 if (newpos < family.length && family[newpos] !== family[i])
698697 menu.box.insert_child_below(family[i], family[newpos]);
699698 else
700699 menu.box.add(family[i]);
712711 *
713712 * Something like a mini-god-object
714713 */
715 var Client = class AppIndicators_Client {
714 var Client = class AppIndicatorsClient {
716715
717716 constructor(busName, path) {
718717 this._busName = busName;
730729 // it will also connect the client to be automatically destroyed when the menu dies.
731730 attachToMenu(menu) {
732731 this._rootMenu = menu;
733 this._rootItem = this._client.get_root();
732 this._rootItem = this._client.getRoot();
734733
735734 // cleanup: remove existing children (just in case)
736735 this._rootMenu.removeAll();
747746 Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved');
748747
749748 // Dropbox requires us to call AboutToShow(0) first
750 this._rootItem.send_about_to_show();
749 this._rootItem.sendAboutToShow();
751750
752751 // fill the menu for the first time
753 this._rootItem.get_children().forEach(child =>
752 this._rootItem.getChildren().forEach(child =>
754753 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)),
755754 );
756755 }
759758 if (!submenu)
760759 return;
761760
762 if (submenu._parent != this._rootMenu)
761 if (submenu._parent !== this._rootMenu)
763762 return;
764763
765764 if (submenu === this._openedSubMenu)
779778 // children like to play hide and seek
780779 // but we know how to find it for sure!
781780 this._rootMenu._getMenuItems().forEach(item => {
782 if (item._dbusItem == child)
781 if (item._dbusItem === child)
783782 item.destroy();
784783 });
785784 }
796795 if (this._openedSubMenu && this._openedSubMenu.isOpen)
797796 this._openedSubMenu.close();
798797
799 this._rootItem.handle_event('opened', null, 0);
800 this._rootItem.send_about_to_show();
798 this._rootItem.handleEvent('opened', null, 0);
799 this._rootItem.sendAboutToShow();
801800 } else {
802 this._rootItem.handle_event('closed', null, 0);
801 this._rootItem.handleEvent('closed', null, 0);
803802 }
804803 }
805804
1212 // You should have received a copy of the GNU General Public License
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 const Gio = imports.gi.Gio;
15
16 /* exported init, enable, disable */
17
1618 const Extension = imports.misc.extensionUtils.getCurrentExtension();
1719
1820 const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher;
2426
2527 function init() {
2628 watchDog = new Util.NameWatcher(StatusNotifierWatcher.WATCHER_BUS_NAME);
27 watchDog.connect('vanished', () => maybe_enable_after_name_available());
29 watchDog.connect('vanished', () => maybeEnableAfterNameAvailable());
2830
2931 // HACK: we want to leave the watchdog alive when disabling the extension,
3032 // but if we are being reloaded, we destroy it since it could be considered
3133 // a leak and spams our log, too.
34 /* eslint-disable no-undef */
3235 if (typeof global['--appindicator-extension-on-reload'] === 'function')
3336 global['--appindicator-extension-on-reload']();
3437
3639 Util.Logger.debug('Reload detected, destroying old watchdog');
3740 watchDog.destroy();
3841 };
42 /* eslint-enable no-undef */
3943 }
4044
4145 // FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly.
4246 // This will create very bad side effects in case we were not done unowning the name while trying
4347 // to own it again. Since g_bus_unown_name doesn't fire any callback when it's done, we need to
4448 // monitor the bus manually to find out when the name vanished so we can reclaim it again.
45 function maybe_enable_after_name_available() {
49 function maybeEnableAfterNameAvailable() {
4650 // by the time we get called whe might not be enabled
4751 if (isEnabled && (!watchDog.nameAcquired || !watchDog.nameOnBus) && statusNotifierWatcher === null)
4852 statusNotifierWatcher = new StatusNotifierWatcher.StatusNotifierWatcher(watchDog);
5054
5155 function enable() {
5256 isEnabled = true;
53 maybe_enable_after_name_available();
57 maybeEnableAfterNameAvailable();
5458 }
5559
5660 function disable() {
1212 // You should have received a copy of the GNU General Public License
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15
16 /* exported IconCache */
1517
1618 const GLib = imports.gi.GLib;
1719 const Gio = imports.gi.Gio;
3032 const LIFETIME_TIMESPAN = 10; // seconds
3133
3234 // how to use: see IconCache.add, IconCache.get
33 var IconCache = class AppIndicators_IconCache {
35 var IconCache = class AppIndicatorsIconCache {
3436 constructor() {
3537 this._cache = new Map();
3638 this._lifetime = new Map(); // we don't want to attach lifetime to the object
8284
8385 // marks all the icons as removable, if something doesn't claim them before
8486 weakClear() {
85 this._cache.forEach(icon => icon.inUse = false);
87 this._cache.forEach(icon => (icon.inUse = false));
8688 this._checkGC();
8789 }
8890
105107 }
106108
107109 async _checkGC() {
108 let cacheIsEmpty = this._cache.size == 0;
110 let cacheIsEmpty = this._cache.size === 0;
109111
110112 if (!cacheIsEmpty && !this._gcTimeout) {
111113 Util.Logger.debug('IconCache: garbage collector started');
128130 this._remove(id);
129131 else
130132 Util.Logger.debug(`IconCache: ${id} survived this round.`);
131
132133 });
133134
134135 return true;
170170 item.connect('activate', it => {
171171 if (it.get_active()) {
172172 indicator.set_label(`${new Date().getTime()}`, 'Blub');
173 it.connect('activate', () => indicator.set_icon(getRandomIcon()));
173 item.connect('activate', () => indicator.set_icon(getRandomIcon()));
174174 } else {
175175 indicator.set_label('', '');
176176 indicator.set_icon(DEFAULT_ICON);
200200 item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates');
201201 item.connect('activate', it => {
202202 if (it.get_active()) {
203 it._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => {
203 item._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => {
204204 setRandomIconPath();
205205 indicator.set_label(`${new Date().getSeconds()}`, '');
206206 return GLib.SOURCE_CONTINUE;
+0
-16
interfaces-xml/Properties.xml less more
0 <interface name="org.freedesktop.DBus.Properties">
1 <method name="Get">
2 <arg type="s" direction="in" />
3 <arg type="s" direction="in" />
4 <arg type="v" direction="out" />
5 </method>
6 <method name="GetAll">
7 <arg type="s" direction="in" />
8 <arg type="a{sv}" direction="out" />
9 </method>
10 <signal name="PropertiesChanged">
11 <arg type="s" direction="out" />
12 <arg type="a{sv}" direction="out" />
13 <arg type="as" direction="out" />
14 </signal>
15 </interface>
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1515
16 /* exported StatusNotifierItem, StatusNotifierWatcher, DBusMenu */
17
1618 var StatusNotifierItem = loadInterfaceXml('StatusNotifierItem.xml');
17 const Properties = loadInterfaceXml('Properties.xml');
1819 var StatusNotifierWatcher = loadInterfaceXml('StatusNotifierWatcher.xml');
1920 var DBusMenu = loadInterfaceXml('DBusMenu.xml');
2021
2122 // loads a xml file into an in-memory string
2223 function loadInterfaceXml(filename) {
23 let extension = imports.misc.extensionUtils.getCurrentExtension();
24
25 let interfaces_dir = extension.dir.get_child('interfaces-xml');
26
27 let file = interfaces_dir.get_child(filename);
28
24 const extension = imports.misc.extensionUtils.getCurrentExtension();
25 const interfacesDir = extension.dir.get_child('interfaces-xml');
26 const file = interfacesDir.get_child(filename);
2927 let [result, contents] = imports.gi.GLib.file_get_contents(file.get_path());
3028
3129 if (result) {
3230 // HACK: The "" + trick is important as hell because file_get_contents returns
33 // an object (WTF?) but Gio.makeProxyWrapper requires `typeof() == "string"`
31 // an object (WTF?) but Gio.makeProxyWrapper requires `typeof() === "string"`
3432 // Otherwise, it will try to check `instanceof XML` and fail miserably because there
3533 // is no `XML` on very recent SpiderMonkey releases (or, if SpiderMonkey is old enough,
3634 // will spit out a TypeError soon).
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1515
16 /* exported StatusNotifierWatcher */
17
1618 const Gio = imports.gi.Gio;
1719 const GLib = imports.gi.GLib;
1820
3638 /*
3739 * The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object
3840 */
39 var StatusNotifierWatcher = class AppIndicators_StatusNotifierWatcher {
41 var StatusNotifierWatcher = class AppIndicatorsStatusNotifierWatcher {
4042
4143 constructor(watchDog) {
4244 this._watchDog = watchDog;
6769
6870
6971 // create a unique index for the _items dictionary
70 _getItemId(bus_name, obj_path) {
71 return bus_name + obj_path;
72 }
73
74 async _registerItem(service, bus_name, obj_path) {
75 let id = this._getItemId(bus_name, obj_path);
72 _getItemId(busName, objPath) {
73 return busName + objPath;
74 }
75
76 async _registerItem(service, busName, objPath) {
77 let id = this._getItemId(busName, objPath);
7678
7779 if (this._items.has(id)) {
7880 Util.Logger.warn(`Item ${id} is already registered`);
8284 Util.Logger.debug(`Registering StatusNotifierItem ${id}`);
8385
8486 try {
85 const indicator = new AppIndicator.AppIndicator(service, bus_name, obj_path);
87 const indicator = new AppIndicator.AppIndicator(service, busName, objPath);
8688 this._items.set(id, indicator);
8789
8890 indicator.connect('name-owner-changed', async () => {
110112 }
111113 }
112114
113 _ensureItemRegistered(service, bus_name, obj_path) {
114 let id = this._getItemId(bus_name, obj_path);
115 _ensureItemRegistered(service, busName, objPath) {
116 let id = this._getItemId(busName, objPath);
115117 let item = this._items.get(id);
116118
117119 if (item) {
121123 return;
122124 }
123125
124 this._registerItem(service, bus_name, obj_path);
126 this._registerItem(service, busName, objPath);
125127 }
126128
127129 async _seekStatusNotifierItems() {
154156 // instead, ayatana patched gnome apps to send a path
155157 // while kde apps send a bus name
156158 let [service] = params;
157 let bus_name = null, obj_path = null;
158
159 if (service.charAt(0) == '/') { // looks like a path
160 bus_name = invocation.get_sender();
161 obj_path = service;
159 let busName, objPath;
160
161 if (service.charAt(0) === '/') { // looks like a path
162 busName = invocation.get_sender();
163 objPath = service;
162164 } else if (service.match(Util.BUS_ADDRESS_REGEX)) {
163165 try {
164 bus_name = await Util.getUniqueBusName(invocation.get_connection(),
166 busName = await Util.getUniqueBusName(invocation.get_connection(),
165167 service, this._cancellable);
166168 } catch (e) {
167169 logError(e);
168170 }
169 obj_path = DEFAULT_ITEM_OBJECT_PATH;
170 }
171
172 if (!bus_name || !obj_path) {
171 objPath = DEFAULT_ITEM_OBJECT_PATH;
172 }
173
174 if (!busName || !objPath) {
173175 let error = `Impossible to register an indicator for parameters '${
174176 service.toString()}'`;
175177 Util.Logger.warn(error);
179181 return;
180182 }
181183
182 this._ensureItemRegistered(service, bus_name, obj_path);
184 this._ensureItemRegistered(service, busName, objPath);
183185
184186 invocation.return_value(null);
185187 }
1515
1616 /* exported refreshPropertyOnProxy, getUniqueBusName, getBusNames,
1717 introspectBusObject, dbusNodeImplementsInterfaces, waitForStartupCompletion,
18 BUS_ADDRESS_REGEX */
18 connectSmart, BUS_ADDRESS_REGEX */
1919
2020 const Gio = imports.gi.Gio;
2121 const GLib = imports.gi.GLib;
2626 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2727 const Params = imports.misc.params;
2828 const PromiseUtils = Extension.imports.promiseUtils;
29
3029 const Signals = imports.signals;
3130
3231 var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/;
8483 }
8584 }
8685
87 var cancelRefreshPropertyOnProxy = function (proxy, params) {
86 function cancelRefreshPropertyOnProxy(proxy, params) {
8887 if (!proxy._proxyCancellables)
89 return;
88 return null;
9089
9190 params = Params.parse(params, {
9291 propertyName: undefined,
112111 delete proxy._proxyChangedProperties;
113112 delete proxy._proxyCancellables;
114113 }
115 };
114
115 return null;
116 }
116117
117118 async function getUniqueBusName(bus, name, cancellable) {
118 if (name[0] == ':')
119 if (name[0] === ':')
119120 return name;
120121
121122 if (!bus)
182183 return nodes;
183184 }
184185
185 var dbusNodeImplementsInterfaces = function (node_info, interfaces) {
186 if (!(node_info instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces))
186 function dbusNodeImplementsInterfaces(nodeInfo, interfaces) {
187 if (!(nodeInfo instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces))
187188 return false;
188189
189 for (let iface of interfaces) {
190 if (node_info.lookup_interface(iface) !== null)
191 return true;
192 }
193
194 return false;
195 };
190 return interfaces.some(iface => nodeInfo.lookup_interface(iface));
191 }
196192
197193 var NameWatcher = class AppIndicatorsNameWatcher {
198194 constructor(name) {
223219 };
224220 Signals.addSignalMethods(NameWatcher.prototype);
225221
226 const connectSmart3A = function (src, signal, handler) {
222 function connectSmart3A(src, signal, handler) {
227223 let id = src.connect(signal, handler);
228224
229225 if (src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src))) {
230 let destroy_id = src.connect('destroy', () => {
226 let destroyId = src.connect('destroy', () => {
231227 src.disconnect(id);
232 src.disconnect(destroy_id);
228 src.disconnect(destroyId);
233229 });
234230 }
235 };
236
237 const connectSmart4A = function (src, signal, target, method) {
231 }
232
233 function connectSmart4A(src, signal, target, method) {
238234 if (typeof method === 'string')
239235 method = target[method].bind(target);
240236 if (typeof method === 'function')
241237 method = method.bind(target);
242238
243 let signal_id = src.connect(signal, method);
239 const signalId = src.connect(signal, method);
240 const onDestroy = () => {
241 src.disconnect(signalId);
242 if (srcDestroyId)
243 src.disconnect(srcDestroyId);
244 if (tgtDestroyId)
245 target.disconnect(tgtDestroyId);
246 };
244247
245248 // GObject classes might or might not have a destroy signal
246249 // JS Classes will not complain when connecting to non-existent signals
247 let src_destroy_id = src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src)) ? src.connect('destroy', on_destroy) : 0;
248 let tgt_destroy_id = target.connect && (!(target instanceof GObject.Object) || GObject.signal_lookup('destroy', target)) ? target.connect('destroy', on_destroy) : 0;
249
250 function on_destroy() {
251 src.disconnect(signal_id);
252 if (src_destroy_id)
253 src.disconnect(src_destroy_id);
254 if (tgt_destroy_id)
255 target.disconnect(tgt_destroy_id);
256 }
257 };
258
250 const srcDestroyId = src.connect && (!(src instanceof GObject.Object) ||
251 GObject.signal_lookup('destroy', src)) ? src.connect('destroy', onDestroy) : 0;
252 const tgtDestroyId = target.connect && (!(target instanceof GObject.Object) ||
253 GObject.signal_lookup('destroy', target)) ? target.connect('destroy', onDestroy) : 0;
254 }
255
256 // eslint-disable-next-line valid-jsdoc
259257 /**
260258 * Connect signals to slots, and remove the connection when either source or
261259 * target are destroyed
265263 * or
266264 * Util.connectSmart(srcOb, 'signal', () => { ... })
267265 */
268 var connectSmart = function () {
269 if (arguments.length == 4)
270 return connectSmart4A.apply(null, arguments);
266 function connectSmart(...args) {
267 if (arguments.length === 4)
268 return connectSmart4A(...args);
271269 else
272 return connectSmart3A.apply(null, arguments);
273 };
270 return connectSmart3A(...args);
271 }
274272
275273 // eslint-disable-next-line valid-jsdoc
276274 /**
288286 /**
289287 * Helper class for logging stuff
290288 */
291 var Logger = class AppIndicators_Logger {
289 var Logger = class AppIndicatorsLogger {
292290 static _logStructured(logLevel, message, extraFields = {}) {
293291 if (!Object.values(GLib.LogLevelFlags).includes(logLevel)) {
294292 Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING,