cleanup: Cleanup code to match eslint rules
Marco Trevisan (TreviƱo)
3 years ago
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | 15 | |
16 | const Clutter = imports.gi.Clutter; | |
17 | const Cogl = imports.gi.Cogl; | |
16 | /* exported AppIndicator, IconActor */ | |
17 | ||
18 | 18 | const GdkPixbuf = imports.gi.GdkPixbuf; |
19 | 19 | const Gio = imports.gi.Gio; |
20 | 20 | const GLib = imports.gi.GLib; |
21 | 21 | const GObject = imports.gi.GObject; |
22 | 22 | const Gtk = imports.gi.Gtk; |
23 | 23 | const St = imports.gi.St; |
24 | const Shell = imports.gi.Shell; | |
25 | 24 | |
26 | 25 | const Extension = imports.misc.extensionUtils.getCurrentExtension(); |
27 | 26 | const Signals = imports.signals; |
28 | 27 | |
29 | const DBusMenu = Extension.imports.dbusMenu; | |
30 | 28 | const IconCache = Extension.imports.iconCache; |
31 | 29 | const Util = Extension.imports.util; |
32 | 30 | const Interfaces = Extension.imports.interfaces; |
40 | 38 | |
41 | 39 | const MAX_UPDATE_FREQUENCY = 100; // In ms |
42 | 40 | |
41 | // eslint-disable-next-line no-unused-vars | |
43 | 42 | const SNICategory = { |
44 | 43 | APPLICATION: 'ApplicationStatus', |
45 | 44 | COMMUNICATIONS: 'Communications', |
63 | 62 | * the AppIndicator class serves as a generic container for indicator information and functions common |
64 | 63 | * for every displaying implementation (IndicatorMessageSource and IndicatorStatusIcon) |
65 | 64 | */ |
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; | |
71 | 70 | this._accumulatedSignals = new Set(); |
72 | 71 | |
73 | let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem); | |
72 | const interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem); | |
74 | 73 | |
75 | 74 | // HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need |
76 | 75 | // to specify G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES |
77 | 76 | this._cancellable = new Gio.Cancellable(); |
78 | 77 | 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, | |
82 | 81 | g_object_path: object, |
83 | 82 | g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES }); |
84 | 83 | |
87 | 86 | Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal); |
88 | 87 | Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged'); |
89 | 88 | |
90 | if (service !== bus_name && service.match(Util.BUS_ADDRESS_REGEX)) { | |
89 | if (service !== busName && service.match(Util.BUS_ADDRESS_REGEX)) { | |
91 | 90 | this._uniqueId = service; |
92 | 91 | this._nameWatcher = new Util.NameWatcher(service); |
93 | 92 | Util.connectSmart(this._nameWatcher, 'changed', () => this._nameOwnerChanged()); |
101 | 100 | this._checkMenuReady(); |
102 | 101 | } catch (e) { |
103 | 102 | 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}`); | |
105 | 104 | } |
106 | 105 | } |
107 | 106 | |
136 | 135 | for (let checks = 0; checks < 3 && !this.isReady; ++checks) { |
137 | 136 | this._delayCheck = new PromiseUtils.TimeoutSecondsPromise(1, |
138 | 137 | GLib.PRIORITY_DEFAULT_IDLE, cancellable); |
138 | // eslint-disable-next-line no-await-in-loop | |
139 | 139 | await this._delayCheck; |
140 | 140 | Util.refreshPropertyOnProxy(this._proxy, 'Menu'); |
141 | 141 | } |
174 | 174 | let interfaceProps = this._proxy.g_interface_info.properties; |
175 | 175 | this._proxyPropertyList = |
176 | 176 | (this._proxy.get_cached_property_names() || []).filter(p => |
177 | interfaceProps.some(propinfo => propinfo.name == p)); | |
177 | interfaceProps.some(propinfo => propinfo.name === p)); | |
178 | 178 | |
179 | 179 | if (this._proxyPropertyList.length) { |
180 | 180 | this._addExtraProperty('XAyatanaLabel'); |
242 | 242 | } |
243 | 243 | |
244 | 244 | get menuPath() { |
245 | if (this._proxy.Menu == '/NO_DBUSMENU') | |
245 | if (this._proxy.Menu === '/NO_DBUSMENU') | |
246 | 246 | return null; |
247 | 247 | |
248 | 248 | return this._proxy.Menu || '/MenuBar'; |
281 | 281 | return this._cancellable; |
282 | 282 | } |
283 | 283 | |
284 | _onPropertiesChanged(proxy, changed, invalidated) { | |
284 | _onPropertiesChanged(_proxy, changed, _invalidated) { | |
285 | 285 | let props = Object.keys(changed.unpack()); |
286 | 286 | let signalsToEmit = new Set(); |
287 | 287 | |
290 | 290 | // a few need to be passed down to the displaying code |
291 | 291 | |
292 | 292 | // all these can mean that the icon has to be changed |
293 | if (property == 'Status' || | |
293 | if (property === 'Status' || | |
294 | 294 | property.startsWith('Icon') || |
295 | 295 | property.startsWith('AttentionIcon')) |
296 | 296 | signalsToEmit.add('icon'); |
301 | 301 | signalsToEmit.add('overlay-icon'); |
302 | 302 | |
303 | 303 | // this may make all of our icons invalid |
304 | if (property == 'IconThemePath') { | |
304 | if (property === 'IconThemePath') { | |
305 | 305 | signalsToEmit.add('icon'); |
306 | 306 | signalsToEmit.add('overlay-icon'); |
307 | 307 | } |
308 | 308 | |
309 | 309 | // the label will be handled elsewhere |
310 | if (property == 'XAyatanaLabel') | |
310 | if (property === 'XAyatanaLabel') | |
311 | 311 | signalsToEmit.add('label'); |
312 | 312 | |
313 | if (property == 'Menu') { | |
313 | if (property === 'Menu') { | |
314 | 314 | if (!this._checkIfReady() && this.isReady) |
315 | 315 | signalsToEmit.add('menu'); |
316 | 316 | } |
317 | 317 | |
318 | 318 | // status updates may cause the indicator to be hidden |
319 | if (property == 'Status') | |
319 | if (property === 'Status') | |
320 | 320 | signalsToEmit.add('status'); |
321 | 321 | }); |
322 | 322 | |
332 | 332 | |
333 | 333 | this.disconnectAll(); |
334 | 334 | this._cancellable.cancel(); |
335 | this._nameWatcher && this._nameWatcher.destroy(); | |
336 | 335 | Util.cancelRefreshPropertyOnProxy(this._proxy); |
336 | if (this._nameWatcher) | |
337 | this._nameWatcher.destroy(); | |
337 | 338 | delete this._cancellable; |
338 | 339 | delete this._proxy; |
340 | delete this._nameWatcher; | |
339 | 341 | } |
340 | 342 | |
341 | 343 | open() { |
351 | 353 | } |
352 | 354 | |
353 | 355 | scroll(dx, dy) { |
354 | if (dx != 0) | |
356 | if (dx !== 0) | |
355 | 357 | this._proxy.ScrollRemote(Math.floor(dx), 'horizontal'); |
356 | 358 | |
357 | if (dy != 0) | |
359 | if (dy !== 0) | |
358 | 360 | this._proxy.ScrollRemote(Math.floor(dy), 'vertical'); |
359 | 361 | } |
360 | 362 | }; |
361 | 363 | Signals.addSignalMethods(AppIndicator.prototype); |
362 | 364 | |
363 | 365 | 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) { | |
367 | 369 | super._init({ |
368 | 370 | reactive: true, |
369 | 371 | style_class: 'system-status-icon', |
374 | 376 | this.add_style_class_name('appindicator-icon'); |
375 | 377 | this.set_style('padding:0'); |
376 | 378 | |
379 | // eslint-disable-next-line no-undef | |
377 | 380 | let themeContext = St.ThemeContext.get_for_stage(global.stage); |
378 | this.height = icon_size * themeContext.scale_factor; | |
381 | this.height = iconSize * themeContext.scale_factor; | |
379 | 382 | |
380 | 383 | this._indicator = indicator; |
381 | this._iconSize = icon_size; | |
384 | this._iconSize = iconSize; | |
382 | 385 | this._iconCache = new IconCache.IconCache(); |
383 | 386 | this._cancellable = new Gio.Cancellable(); |
384 | 387 | this._loadingIcons = new Set(); |
388 | 391 | Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon'); |
389 | 392 | |
390 | 393 | Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => { |
391 | this.height = icon_size * tc.scale_factor; | |
394 | this.height = iconSize * tc.scale_factor; | |
392 | 395 | this._invalidateIcon(); |
393 | 396 | }); |
394 | 397 | |
424 | 427 | // Will look the icon up in the cache, if it's found |
425 | 428 | // it will return it. Otherwise, it will create it and cache it. |
426 | 429 | 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 || ''}`; | |
429 | 433 | let gicon = this._iconCache.get(id); |
430 | 434 | |
431 | 435 | if (gicon) |
440 | 444 | } |
441 | 445 | |
442 | 446 | this._loadingIcons.add(id); |
443 | let path = this._getIconInfo(iconName, themePath, iconSize, scale_factor); | |
447 | let path = this._getIconInfo(iconName, themePath, iconSize, scaleFactor); | |
444 | 448 | gicon = await this._createIconByName(path); |
445 | 449 | this._loadingIcons.delete(id); |
446 | 450 | if (gicon) |
514 | 518 | |
515 | 519 | _getIconInfo(name, themePath, size, scale) { |
516 | 520 | let path = null; |
517 | if (name && name[0] == '/') { | |
521 | if (name && name[0] === '/') { | |
518 | 522 | // HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it. |
519 | 523 | path = name; |
520 | 524 | } else if (name) { |
528 | 532 | let iconInfo = null; |
529 | 533 | |
530 | 534 | // 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; | |
532 | 536 | 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()); | |
539 | 542 | } else { |
540 | icon_theme = Gtk.IconTheme.get_default(); | |
541 | } | |
542 | if (icon_theme) { | |
543 | iconTheme = Gtk.IconTheme.get_default(); | |
544 | } | |
545 | if (iconTheme) { | |
543 | 546 | // 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, | |
545 | 548 | Gtk.IconLookupFlags.GENERIC_FALLBACK); |
546 | 549 | // no icon? that's bad! |
547 | 550 | if (iconInfo === null) { |
588 | 591 | } |
589 | 592 | |
590 | 593 | 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; | |
593 | 597 | // the pixmap actually is an array of pixmaps with different sizes |
594 | 598 | // we use the one that is smaller or equal the iconSize |
595 | 599 | |
647 | 651 | // So when it's not need anymore we make sure to check the .inUse property |
648 | 652 | // and set it to false so that it can be picked up by the garbage collector. |
649 | 653 | _setGicon(iconType, gicon) { |
650 | if (iconType != SNIconType.OVERLAY) { | |
654 | if (iconType !== SNIconType.OVERLAY) { | |
651 | 655 | if (gicon) { |
652 | 656 | this.gicon = new Gio.EmblemedIcon({ gicon }); |
653 | 657 | |
654 | 658 | if (!(gicon instanceof GdkPixbuf.Pixbuf)) |
655 | gicon.inUse = this.gicon.get_icon() == gicon; | |
659 | gicon.inUse = this.gicon.get_icon() === gicon; | |
656 | 660 | } else { |
657 | 661 | this.gicon = null; |
658 | 662 | Util.Logger.critical(`unable to update icon for ${this._indicator.id}`); |
720 | 724 | } |
721 | 725 | |
722 | 726 | // 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 | |
724 | 728 | ? SNIconType.ATTENTION : SNIconType.NORMAL; |
725 | 729 | |
726 | 730 | this._updateIconByType(iconType, this._iconSize); |
12 | 12 | // You should have received a copy of the GNU General Public License |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | const Atk = imports.gi.Atk; | |
16 | const Clutter = imports.gi.Clutter; | |
17 | 15 | const Gio = imports.gi.Gio; |
18 | 16 | const GLib = imports.gi.GLib; |
19 | 17 | const GdkPixbuf = imports.gi.GdkPixbuf; |
35 | 33 | /** |
36 | 34 | * Saves menu property values and handles type checking and defaults |
37 | 35 | */ |
38 | var PropertyStore = class AppIndicators_PropertyStore { | |
39 | ||
40 | constructor(initial_properties) { | |
36 | var PropertyStore = class AppIndicatorsPropertyStore { | |
37 | ||
38 | constructor(initialProperties) { | |
41 | 39 | this._props = new Map(); |
42 | 40 | |
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]); | |
46 | 44 | |
47 | 45 | } |
48 | 46 | } |
91 | 89 | /** |
92 | 90 | * Represents a single menu item |
93 | 91 | */ |
94 | var DbusMenuItem = class AppIndicators_DbusMenuItem { | |
92 | var DbusMenuItem = class AppIndicatorsDbusMenuItem { | |
95 | 93 | |
96 | 94 | // will steal the properties object |
97 | constructor(client, id, properties, children_ids) { | |
95 | constructor(client, id, properties, childrenIds) { | |
98 | 96 | this._client = client; |
99 | 97 | this._id = id; |
100 | 98 | 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); | |
106 | 104 | return prop ? prop.get_string()[0] : null; |
107 | 105 | } |
108 | 106 | |
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); | |
115 | 113 | return prop ? prop.get_boolean() : false; |
116 | 114 | } |
117 | 115 | |
118 | property_get_int(prop_name) { | |
119 | let prop = this.property_get_variant(prop_name); | |
116 | propertyGetInt(propName) { | |
117 | let prop = this.propertyGetVariant(propName); | |
120 | 118 | return prop ? prop.get_int32() : 0; |
121 | 119 | } |
122 | 120 | |
123 | property_set(prop, value) { | |
121 | propertySet(prop, value) { | |
124 | 122 | this._propStore.set(prop, value); |
125 | 123 | |
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() { | |
130 | 128 | return this._children_ids.concat(); // clone it! |
131 | 129 | } |
132 | 130 | |
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) { | |
139 | 137 | // find it |
140 | 138 | let pos = -1; |
141 | 139 | 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) { | |
143 | 141 | pos = i; |
144 | 142 | break; |
145 | 143 | } |
149 | 147 | Util.Logger.critical("Trying to remove child which doesn't exist"); |
150 | 148 | } else { |
151 | 149 | 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) { | |
157 | 155 | // find the old position |
158 | let oldpos = -1; | |
156 | let oldPos = -1; | |
159 | 157 | 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; | |
162 | 160 | break; |
163 | 161 | } |
164 | 162 | } |
165 | 163 | |
166 | if (oldpos < 0) { | |
164 | if (oldPos < 0) { | |
167 | 165 | Util.Logger.critical("tried to move child which wasn't in the list"); |
168 | 166 | return; |
169 | 167 | } |
170 | 168 | |
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) { | |
183 | 181 | if (!data) |
184 | 182 | data = GLib.Variant.new_int32(0); |
185 | 183 | |
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() { | |
190 | 188 | return this._id; |
191 | 189 | } |
192 | 190 | |
193 | send_about_to_show() { | |
194 | this._client.send_about_to_show(this._id); | |
191 | sendAboutToShow() { | |
192 | this._client.sendAboutToShow(this._id); | |
195 | 193 | } |
196 | 194 | }; |
197 | 195 | Signals.addSignalMethods(DbusMenuItem.prototype); |
202 | 200 | /** |
203 | 201 | * The client does the heavy lifting of actually reading layouts and distributing events |
204 | 202 | */ |
205 | var DBusClient = class AppIndicators_DBusClient { | |
203 | var DBusClient = class AppIndicatorsDBusClient { | |
206 | 204 | |
207 | 205 | constructor(busName, busPath) { |
208 | 206 | this._cancellable = new Gio.Cancellable(); |
238 | 236 | return !!this._proxy.g_name_owner; |
239 | 237 | } |
240 | 238 | |
241 | get_root() { | |
239 | getRoot() { | |
242 | 240 | return this._items.get(0); |
243 | 241 | } |
244 | 242 | |
286 | 284 | return; |
287 | 285 | |
288 | 286 | for (let prop in properties) |
289 | item.property_set(prop, properties[prop]); | |
287 | item.propertySet(prop, properties[prop]); | |
290 | 288 | }); |
291 | 289 | } |
292 | 290 | |
297 | 295 | |
298 | 296 | let toTraverse = [0]; |
299 | 297 | while (toTraverse.length > 0) { |
300 | let item = this.get_item(toTraverse.shift()); | |
298 | let item = this.getItem(toTraverse.shift()); | |
301 | 299 | item._dbusClientGcTag = tag; |
302 | Array.prototype.push.apply(toTraverse, item.get_children_ids()); | |
300 | Array.prototype.push.apply(toTraverse, item.getChildrenIds()); | |
303 | 301 | } |
304 | 302 | |
305 | 303 | this._items.forEach((i, id) => { |
306 | if (i._dbusClientGcTag != tag) | |
304 | if (i._dbusClientGcTag !== tag) | |
307 | 305 | this._items.delete(id); |
308 | 306 | }); |
309 | 307 | } |
329 | 327 | return; |
330 | 328 | } |
331 | 329 | |
332 | let [revision, root] = result; | |
330 | let [revision_, root] = result; | |
333 | 331 | this._doLayoutUpdate(root); |
334 | 332 | this._gcItems(); |
335 | 333 | |
353 | 351 | if (menuItem) { |
354 | 352 | // we do, update our properties if necessary |
355 | 353 | for (let prop in properties) |
356 | menuItem.property_set(prop, properties[prop]); | |
354 | menuItem.propertySet(prop, properties[prop]); | |
357 | 355 | |
358 | 356 | |
359 | 357 | // make sure our children are all at the right place, and exist |
360 | let oldChildrenIds = menuItem.get_children_ids(); | |
358 | let oldChildrenIds = menuItem.getChildrenIds(); | |
361 | 359 | for (let i = 0; i < childrenIds.length; ++i) { |
362 | 360 | // try to recycle an old child |
363 | 361 | let oldChild = -1; |
364 | 362 | for (let j = 0; j < oldChildrenIds.length; ++j) { |
365 | if (oldChildrenIds[j] == childrenIds[i]) { | |
363 | if (oldChildrenIds[j] === childrenIds[i]) { | |
366 | 364 | oldChild = oldChildrenIds.splice(j, 1)[0]; |
367 | 365 | break; |
368 | 366 | } |
370 | 368 | |
371 | 369 | if (oldChild < 0) { |
372 | 370 | // no old child found, so create a new one! |
373 | menuItem.add_child(i, childrenIds[i]); | |
371 | menuItem.addChild(i, childrenIds[i]); | |
374 | 372 | } else { |
375 | 373 | // old child found, reuse it! |
376 | menuItem.move_child(childrenIds[i], i); | |
374 | menuItem.moveChild(childrenIds[i], i); | |
377 | 375 | } |
378 | 376 | } |
379 | 377 | |
380 | 378 | // 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)); | |
382 | 380 | } else { |
383 | 381 | // we don't, so let's create us |
384 | 382 | this._items.set(id, new DbusMenuItem(this, id, properties, childrenIds)); |
402 | 400 | this._proxy.connectSignal('ItemsPropertiesUpdated', this._onPropertiesUpdated.bind(this)); |
403 | 401 | } |
404 | 402 | |
405 | get_item(id) { | |
403 | getItem(id) { | |
406 | 404 | let item = this._items.get(id); |
407 | 405 | if (!item) |
408 | 406 | Util.Logger.warn(`trying to retrieve item for non-existing id ${id} !?`); |
410 | 408 | } |
411 | 409 | |
412 | 410 | // 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) { | |
414 | 412 | /* Some indicators (you, dropbox!) don't use the right signature |
415 | 413 | * and don't return a boolean, so we need to support both cases */ |
416 | 414 | let connection = this._proxy.get_connection(); |
431 | 429 | }); |
432 | 430 | } |
433 | 431 | |
434 | send_event(id, event, params, timestamp) { | |
432 | sendEvent(id, event, params, timestamp) { | |
435 | 433 | if (!this._proxy) |
436 | 434 | return; |
437 | 435 | |
450 | 448 | return; |
451 | 449 | |
452 | 450 | for (let prop in props) |
453 | item.property_set(prop, props[prop]); | |
451 | item.propertySet(prop, props[prop]); | |
454 | 452 | }); |
455 | 453 | removed.forEach(([id, propNames]) => { |
456 | 454 | let item = this._items.get(id); |
457 | 455 | if (!item) |
458 | 456 | return; |
459 | 457 | |
460 | propNames.forEach(propName => item.property_set(propName, null)); | |
458 | propNames.forEach(propName => item.propertySet(propName, null)); | |
461 | 459 | }); |
462 | 460 | } |
463 | 461 | |
492 | 490 | const MenuItemFactory = { |
493 | 491 | createItem(client, dbusItem) { |
494 | 492 | // 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(''); | |
499 | 498 | else |
500 | var shellItem = new PopupMenu.PopupMenuItem('FIXME'); | |
499 | shellItem = new PopupMenu.PopupMenuItem('FIXME'); | |
501 | 500 | |
502 | 501 | shellItem._dbusItem = dbusItem; |
503 | 502 | shellItem._dbusClient = client; |
517 | 516 | |
518 | 517 | // initially create children |
519 | 518 | if (shellItem instanceof PopupMenu.PopupSubMenuMenuItem) { |
520 | let children = dbusItem.get_children(); | |
519 | let children = dbusItem.getChildren(); | |
521 | 520 | for (let i = 0; i < children.length; ++i) |
522 | 521 | shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i])); |
523 | 522 | |
550 | 549 | menu._parent._openedSubMenu = menu; |
551 | 550 | } |
552 | 551 | |
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(); | |
555 | 554 | } else { |
556 | 555 | if (NEED_NESTED_SUBMENU_FIX) { |
557 | 556 | // close our own submenus |
559 | 558 | menu._openedSubMenu.close(false); |
560 | 559 | } |
561 | 560 | |
562 | this._dbusItem.handle_event('closed', null, 0); | |
561 | this._dbusItem.handleEvent('closed', null, 0); | |
563 | 562 | } |
564 | 563 | }, |
565 | 564 | |
566 | 565 | _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') | |
572 | 571 | MenuItemFactory._updateOrnament.call(this); |
573 | else if (prop == 'label') | |
572 | else if (prop === 'label') | |
574 | 573 | MenuItemFactory._updateLabel.call(this); |
575 | else if (prop == 'enabled') | |
574 | else if (prop === 'enabled') | |
576 | 575 | MenuItemFactory._updateSensitive.call(this); |
577 | else if (prop == 'visible') | |
576 | else if (prop === 'visible') | |
578 | 577 | MenuItemFactory._updateVisible.call(this); |
579 | else if (prop == 'icon-name' || prop == 'icon-data') | |
578 | else if (prop === 'icon-name' || prop === 'icon-data') | |
580 | 579 | MenuItemFactory._updateImage.call(this); |
581 | else if (prop == 'type' || prop == 'children-display') | |
580 | else if (prop === 'type' || prop === 'children-display') | |
582 | 581 | MenuItemFactory._replaceSelf.call(this); |
583 | // else | |
584 | // Util.Logger.debug("Unhandled property change: "+prop) | |
582 | else | |
583 | Util.Logger.debug(`Unhandled property change: ${prop}`); | |
585 | 584 | }, |
586 | 585 | |
587 | 586 | _onChildAdded(dbusItem, child, position) { |
600 | 599 | } else { |
601 | 600 | // find it! |
602 | 601 | this.menu._getMenuItems().forEach(item => { |
603 | if (item._dbusItem == child) | |
602 | if (item._dbusItem === child) | |
604 | 603 | item.destroy(); |
605 | 604 | }); |
606 | 605 | } |
616 | 615 | }, |
617 | 616 | |
618 | 617 | _updateLabel() { |
619 | let label = this._dbusItem.property_get('label').replace(/_([^_])/, '$1'); | |
618 | let label = this._dbusItem.propertyGet('label').replace(/_([^_])/, '$1'); | |
620 | 619 | |
621 | 620 | if (this.label) // especially on GS3.8, the separator item might not even have a hidden label |
622 | 621 | this.label.set_text(label); |
626 | 625 | if (!this.setOrnament) |
627 | 626 | return; // separators and alike might not have gotten the polyfill |
628 | 627 | |
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')) | |
630 | 629 | 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')) | |
632 | 631 | this.setOrnament(PopupMenu.Ornament.DOT); |
633 | 632 | else |
634 | 633 | this.setOrnament(PopupMenu.Ornament.NONE); |
638 | 637 | if (!this._icon) |
639 | 638 | return; // might be missing on submenus / separators |
640 | 639 | |
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'); | |
643 | 642 | if (iconName) |
644 | 643 | this._icon.icon_name = iconName; |
645 | 644 | else if (iconData) |
647 | 646 | }, |
648 | 647 | |
649 | 648 | _updateVisible() { |
650 | this.visible = this._dbusItem.property_get_bool('visible'); | |
649 | this.visible = this._dbusItem.propertyGetBool('visible'); | |
651 | 650 | }, |
652 | 651 | |
653 | 652 | _updateSensitive() { |
654 | this.setSensitive(this._dbusItem.property_get_bool('enabled')); | |
653 | this.setSensitive(this._dbusItem.propertyGetBool('enabled')); | |
655 | 654 | }, |
656 | 655 | |
657 | 656 | _replaceSelf(newSelf) { |
689 | 688 | // First, find our wrapper. Children tend to lie. We do not trust the old positioning. |
690 | 689 | let family = menu._getMenuItems(); |
691 | 690 | for (let i = 0; i < family.length; ++i) { |
692 | if (family[i]._dbusItem == dbusItem) { | |
691 | if (family[i]._dbusItem === dbusItem) { | |
693 | 692 | // now, remove it |
694 | 693 | menu.box.remove_child(family[i]); |
695 | 694 | |
696 | 695 | // and add it again somewhere else |
697 | if (newpos < family.length && family[newpos] != family[i]) | |
696 | if (newpos < family.length && family[newpos] !== family[i]) | |
698 | 697 | menu.box.insert_child_below(family[i], family[newpos]); |
699 | 698 | else |
700 | 699 | menu.box.add(family[i]); |
712 | 711 | * |
713 | 712 | * Something like a mini-god-object |
714 | 713 | */ |
715 | var Client = class AppIndicators_Client { | |
714 | var Client = class AppIndicatorsClient { | |
716 | 715 | |
717 | 716 | constructor(busName, path) { |
718 | 717 | this._busName = busName; |
730 | 729 | // it will also connect the client to be automatically destroyed when the menu dies. |
731 | 730 | attachToMenu(menu) { |
732 | 731 | this._rootMenu = menu; |
733 | this._rootItem = this._client.get_root(); | |
732 | this._rootItem = this._client.getRoot(); | |
734 | 733 | |
735 | 734 | // cleanup: remove existing children (just in case) |
736 | 735 | this._rootMenu.removeAll(); |
747 | 746 | Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved'); |
748 | 747 | |
749 | 748 | // Dropbox requires us to call AboutToShow(0) first |
750 | this._rootItem.send_about_to_show(); | |
749 | this._rootItem.sendAboutToShow(); | |
751 | 750 | |
752 | 751 | // fill the menu for the first time |
753 | this._rootItem.get_children().forEach(child => | |
752 | this._rootItem.getChildren().forEach(child => | |
754 | 753 | this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)), |
755 | 754 | ); |
756 | 755 | } |
759 | 758 | if (!submenu) |
760 | 759 | return; |
761 | 760 | |
762 | if (submenu._parent != this._rootMenu) | |
761 | if (submenu._parent !== this._rootMenu) | |
763 | 762 | return; |
764 | 763 | |
765 | 764 | if (submenu === this._openedSubMenu) |
779 | 778 | // children like to play hide and seek |
780 | 779 | // but we know how to find it for sure! |
781 | 780 | this._rootMenu._getMenuItems().forEach(item => { |
782 | if (item._dbusItem == child) | |
781 | if (item._dbusItem === child) | |
783 | 782 | item.destroy(); |
784 | 783 | }); |
785 | 784 | } |
796 | 795 | if (this._openedSubMenu && this._openedSubMenu.isOpen) |
797 | 796 | this._openedSubMenu.close(); |
798 | 797 | |
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(); | |
801 | 800 | } else { |
802 | this._rootItem.handle_event('closed', null, 0); | |
801 | this._rootItem.handleEvent('closed', null, 0); | |
803 | 802 | } |
804 | 803 | } |
805 | 804 |
12 | 12 | // You should have received a copy of the GNU General Public License |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // 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 | ||
16 | 18 | const Extension = imports.misc.extensionUtils.getCurrentExtension(); |
17 | 19 | |
18 | 20 | const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher; |
24 | 26 | |
25 | 27 | function init() { |
26 | 28 | watchDog = new Util.NameWatcher(StatusNotifierWatcher.WATCHER_BUS_NAME); |
27 | watchDog.connect('vanished', () => maybe_enable_after_name_available()); | |
29 | watchDog.connect('vanished', () => maybeEnableAfterNameAvailable()); | |
28 | 30 | |
29 | 31 | // HACK: we want to leave the watchdog alive when disabling the extension, |
30 | 32 | // but if we are being reloaded, we destroy it since it could be considered |
31 | 33 | // a leak and spams our log, too. |
34 | /* eslint-disable no-undef */ | |
32 | 35 | if (typeof global['--appindicator-extension-on-reload'] === 'function') |
33 | 36 | global['--appindicator-extension-on-reload'](); |
34 | 37 | |
36 | 39 | Util.Logger.debug('Reload detected, destroying old watchdog'); |
37 | 40 | watchDog.destroy(); |
38 | 41 | }; |
42 | /* eslint-enable no-undef */ | |
39 | 43 | } |
40 | 44 | |
41 | 45 | // FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly. |
42 | 46 | // This will create very bad side effects in case we were not done unowning the name while trying |
43 | 47 | // to own it again. Since g_bus_unown_name doesn't fire any callback when it's done, we need to |
44 | 48 | // 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() { | |
46 | 50 | // by the time we get called whe might not be enabled |
47 | 51 | if (isEnabled && (!watchDog.nameAcquired || !watchDog.nameOnBus) && statusNotifierWatcher === null) |
48 | 52 | statusNotifierWatcher = new StatusNotifierWatcher.StatusNotifierWatcher(watchDog); |
50 | 54 | |
51 | 55 | function enable() { |
52 | 56 | isEnabled = true; |
53 | maybe_enable_after_name_available(); | |
57 | maybeEnableAfterNameAvailable(); | |
54 | 58 | } |
55 | 59 | |
56 | 60 | function disable() { |
12 | 12 | // You should have received a copy of the GNU General Public License |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | ||
16 | /* exported IconCache */ | |
15 | 17 | |
16 | 18 | const GLib = imports.gi.GLib; |
17 | 19 | const Gio = imports.gi.Gio; |
30 | 32 | const LIFETIME_TIMESPAN = 10; // seconds |
31 | 33 | |
32 | 34 | // how to use: see IconCache.add, IconCache.get |
33 | var IconCache = class AppIndicators_IconCache { | |
35 | var IconCache = class AppIndicatorsIconCache { | |
34 | 36 | constructor() { |
35 | 37 | this._cache = new Map(); |
36 | 38 | this._lifetime = new Map(); // we don't want to attach lifetime to the object |
82 | 84 | |
83 | 85 | // marks all the icons as removable, if something doesn't claim them before |
84 | 86 | weakClear() { |
85 | this._cache.forEach(icon => icon.inUse = false); | |
87 | this._cache.forEach(icon => (icon.inUse = false)); | |
86 | 88 | this._checkGC(); |
87 | 89 | } |
88 | 90 | |
105 | 107 | } |
106 | 108 | |
107 | 109 | async _checkGC() { |
108 | let cacheIsEmpty = this._cache.size == 0; | |
110 | let cacheIsEmpty = this._cache.size === 0; | |
109 | 111 | |
110 | 112 | if (!cacheIsEmpty && !this._gcTimeout) { |
111 | 113 | Util.Logger.debug('IconCache: garbage collector started'); |
128 | 130 | this._remove(id); |
129 | 131 | else |
130 | 132 | Util.Logger.debug(`IconCache: ${id} survived this round.`); |
131 | ||
132 | 133 | }); |
133 | 134 | |
134 | 135 | return true; |
170 | 170 | item.connect('activate', it => { |
171 | 171 | if (it.get_active()) { |
172 | 172 | indicator.set_label(`${new Date().getTime()}`, 'Blub'); |
173 | it.connect('activate', () => indicator.set_icon(getRandomIcon())); | |
173 | item.connect('activate', () => indicator.set_icon(getRandomIcon())); | |
174 | 174 | } else { |
175 | 175 | indicator.set_label('', ''); |
176 | 176 | indicator.set_icon(DEFAULT_ICON); |
200 | 200 | item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates'); |
201 | 201 | item.connect('activate', it => { |
202 | 202 | if (it.get_active()) { |
203 | it._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => { | |
203 | item._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => { | |
204 | 204 | setRandomIconPath(); |
205 | 205 | indicator.set_label(`${new Date().getSeconds()}`, ''); |
206 | 206 | return GLib.SOURCE_CONTINUE; |
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> |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | 15 | |
16 | /* exported StatusNotifierItem, StatusNotifierWatcher, DBusMenu */ | |
17 | ||
16 | 18 | var StatusNotifierItem = loadInterfaceXml('StatusNotifierItem.xml'); |
17 | const Properties = loadInterfaceXml('Properties.xml'); | |
18 | 19 | var StatusNotifierWatcher = loadInterfaceXml('StatusNotifierWatcher.xml'); |
19 | 20 | var DBusMenu = loadInterfaceXml('DBusMenu.xml'); |
20 | 21 | |
21 | 22 | // loads a xml file into an in-memory string |
22 | 23 | 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); | |
29 | 27 | let [result, contents] = imports.gi.GLib.file_get_contents(file.get_path()); |
30 | 28 | |
31 | 29 | if (result) { |
32 | 30 | // 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"` | |
34 | 32 | // Otherwise, it will try to check `instanceof XML` and fail miserably because there |
35 | 33 | // is no `XML` on very recent SpiderMonkey releases (or, if SpiderMonkey is old enough, |
36 | 34 | // will spit out a TypeError soon). |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | 15 | |
16 | /* exported StatusNotifierWatcher */ | |
17 | ||
16 | 18 | const Gio = imports.gi.Gio; |
17 | 19 | const GLib = imports.gi.GLib; |
18 | 20 | |
36 | 38 | /* |
37 | 39 | * The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object |
38 | 40 | */ |
39 | var StatusNotifierWatcher = class AppIndicators_StatusNotifierWatcher { | |
41 | var StatusNotifierWatcher = class AppIndicatorsStatusNotifierWatcher { | |
40 | 42 | |
41 | 43 | constructor(watchDog) { |
42 | 44 | this._watchDog = watchDog; |
67 | 69 | |
68 | 70 | |
69 | 71 | // 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); | |
76 | 78 | |
77 | 79 | if (this._items.has(id)) { |
78 | 80 | Util.Logger.warn(`Item ${id} is already registered`); |
82 | 84 | Util.Logger.debug(`Registering StatusNotifierItem ${id}`); |
83 | 85 | |
84 | 86 | try { |
85 | const indicator = new AppIndicator.AppIndicator(service, bus_name, obj_path); | |
87 | const indicator = new AppIndicator.AppIndicator(service, busName, objPath); | |
86 | 88 | this._items.set(id, indicator); |
87 | 89 | |
88 | 90 | indicator.connect('name-owner-changed', async () => { |
110 | 112 | } |
111 | 113 | } |
112 | 114 | |
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); | |
115 | 117 | let item = this._items.get(id); |
116 | 118 | |
117 | 119 | if (item) { |
121 | 123 | return; |
122 | 124 | } |
123 | 125 | |
124 | this._registerItem(service, bus_name, obj_path); | |
126 | this._registerItem(service, busName, objPath); | |
125 | 127 | } |
126 | 128 | |
127 | 129 | async _seekStatusNotifierItems() { |
154 | 156 | // instead, ayatana patched gnome apps to send a path |
155 | 157 | // while kde apps send a bus name |
156 | 158 | 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; | |
162 | 164 | } else if (service.match(Util.BUS_ADDRESS_REGEX)) { |
163 | 165 | try { |
164 | bus_name = await Util.getUniqueBusName(invocation.get_connection(), | |
166 | busName = await Util.getUniqueBusName(invocation.get_connection(), | |
165 | 167 | service, this._cancellable); |
166 | 168 | } catch (e) { |
167 | 169 | logError(e); |
168 | 170 | } |
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) { | |
173 | 175 | let error = `Impossible to register an indicator for parameters '${ |
174 | 176 | service.toString()}'`; |
175 | 177 | Util.Logger.warn(error); |
179 | 181 | return; |
180 | 182 | } |
181 | 183 | |
182 | this._ensureItemRegistered(service, bus_name, obj_path); | |
184 | this._ensureItemRegistered(service, busName, objPath); | |
183 | 185 | |
184 | 186 | invocation.return_value(null); |
185 | 187 | } |
15 | 15 | |
16 | 16 | /* exported refreshPropertyOnProxy, getUniqueBusName, getBusNames, |
17 | 17 | introspectBusObject, dbusNodeImplementsInterfaces, waitForStartupCompletion, |
18 | BUS_ADDRESS_REGEX */ | |
18 | connectSmart, BUS_ADDRESS_REGEX */ | |
19 | 19 | |
20 | 20 | const Gio = imports.gi.Gio; |
21 | 21 | const GLib = imports.gi.GLib; |
26 | 26 | const Extension = imports.misc.extensionUtils.getCurrentExtension(); |
27 | 27 | const Params = imports.misc.params; |
28 | 28 | const PromiseUtils = Extension.imports.promiseUtils; |
29 | ||
30 | 29 | const Signals = imports.signals; |
31 | 30 | |
32 | 31 | var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/; |
84 | 83 | } |
85 | 84 | } |
86 | 85 | |
87 | var cancelRefreshPropertyOnProxy = function (proxy, params) { | |
86 | function cancelRefreshPropertyOnProxy(proxy, params) { | |
88 | 87 | if (!proxy._proxyCancellables) |
89 | return; | |
88 | return null; | |
90 | 89 | |
91 | 90 | params = Params.parse(params, { |
92 | 91 | propertyName: undefined, |
112 | 111 | delete proxy._proxyChangedProperties; |
113 | 112 | delete proxy._proxyCancellables; |
114 | 113 | } |
115 | }; | |
114 | ||
115 | return null; | |
116 | } | |
116 | 117 | |
117 | 118 | async function getUniqueBusName(bus, name, cancellable) { |
118 | if (name[0] == ':') | |
119 | if (name[0] === ':') | |
119 | 120 | return name; |
120 | 121 | |
121 | 122 | if (!bus) |
182 | 183 | return nodes; |
183 | 184 | } |
184 | 185 | |
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)) | |
187 | 188 | return false; |
188 | 189 | |
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 | } | |
196 | 192 | |
197 | 193 | var NameWatcher = class AppIndicatorsNameWatcher { |
198 | 194 | constructor(name) { |
223 | 219 | }; |
224 | 220 | Signals.addSignalMethods(NameWatcher.prototype); |
225 | 221 | |
226 | const connectSmart3A = function (src, signal, handler) { | |
222 | function connectSmart3A(src, signal, handler) { | |
227 | 223 | let id = src.connect(signal, handler); |
228 | 224 | |
229 | 225 | 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', () => { | |
231 | 227 | src.disconnect(id); |
232 | src.disconnect(destroy_id); | |
228 | src.disconnect(destroyId); | |
233 | 229 | }); |
234 | 230 | } |
235 | }; | |
236 | ||
237 | const connectSmart4A = function (src, signal, target, method) { | |
231 | } | |
232 | ||
233 | function connectSmart4A(src, signal, target, method) { | |
238 | 234 | if (typeof method === 'string') |
239 | 235 | method = target[method].bind(target); |
240 | 236 | if (typeof method === 'function') |
241 | 237 | method = method.bind(target); |
242 | 238 | |
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 | }; | |
244 | 247 | |
245 | 248 | // GObject classes might or might not have a destroy signal |
246 | 249 | // 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 | |
259 | 257 | /** |
260 | 258 | * Connect signals to slots, and remove the connection when either source or |
261 | 259 | * target are destroyed |
265 | 263 | * or |
266 | 264 | * Util.connectSmart(srcOb, 'signal', () => { ... }) |
267 | 265 | */ |
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); | |
271 | 269 | else |
272 | return connectSmart3A.apply(null, arguments); | |
273 | }; | |
270 | return connectSmart3A(...args); | |
271 | } | |
274 | 272 | |
275 | 273 | // eslint-disable-next-line valid-jsdoc |
276 | 274 | /** |
288 | 286 | /** |
289 | 287 | * Helper class for logging stuff |
290 | 288 | */ |
291 | var Logger = class AppIndicators_Logger { | |
289 | var Logger = class AppIndicatorsLogger { | |
292 | 290 | static _logStructured(logLevel, message, extraFields = {}) { |
293 | 291 | if (!Object.values(GLib.LogLevelFlags).includes(logLevel)) { |
294 | 292 | Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING, |