Codebase list gnome-shell-extension-appindicator / 5248b0b
eslint: Add linter rules based on upstream gnome-shell and gjs and use auto-fix Cleanup the code a bit with auto-fix feature of eslint Marco Trevisan (TreviƱo) 3 years ago
14 changed file(s) with 1062 addition(s) and 776 deletion(s). Raw diff Collapse all Expand all
0 extends:
1 - ./lint/eslintrc-gjs.yml
2 - ./lint/eslintrc-shell.yml
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
18 const GdkPixbuf = imports.gi.GdkPixbuf
19 const Gio = imports.gi.Gio
20 const GLib = imports.gi.GLib
21 const GObject = imports.gi.GObject
22 const Gtk = imports.gi.Gtk
23 const St = imports.gi.St
24 const Shell = imports.gi.Shell
16 const Clutter = imports.gi.Clutter;
17 const Cogl = imports.gi.Cogl;
18 const GdkPixbuf = imports.gi.GdkPixbuf;
19 const Gio = imports.gi.Gio;
20 const GLib = imports.gi.GLib;
21 const GObject = imports.gi.GObject;
22 const Gtk = imports.gi.Gtk;
23 const St = imports.gi.St;
24 const Shell = imports.gi.Shell;
2525
2626 const Extension = imports.misc.extensionUtils.getCurrentExtension();
27 const Signals = imports.signals
27 const Signals = imports.signals;
2828
2929 const DBusMenu = Extension.imports.dbusMenu;
3030 const IconCache = Extension.imports.iconCache;
4444 APPLICATION: 'ApplicationStatus',
4545 COMMUNICATIONS: 'Communications',
4646 SYSTEM: 'SystemServices',
47 HARDWARE: 'Hardware'
47 HARDWARE: 'Hardware',
4848 };
4949
5050 var SNIStatus = {
5151 PASSIVE: 'Passive',
5252 ACTIVE: 'Active',
53 NEEDS_ATTENTION: 'NeedsAttention'
53 NEEDS_ATTENTION: 'NeedsAttention',
5454 };
5555
5656 const SNIconType = {
6666 var AppIndicator = class AppIndicators_AppIndicator {
6767
6868 constructor(service, bus_name, object) {
69 this.busName = bus_name
70 this._uniqueId = bus_name + object
69 this.busName = bus_name;
70 this._uniqueId = bus_name + object;
7171 this._accumulatedSignals = new Set();
7272
73 let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem)
74
75 //HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need
73 let interface_info = Gio.DBusInterfaceInfo.new_for_xml(Interfaces.StatusNotifierItem);
74
75 // HACK: we cannot use Gio.DBusProxy.makeProxyWrapper because we need
7676 // to specify G_DBUS_PROXY_FLAGS_GET_INVALIDATED_PROPERTIES
7777 this._cancellable = new Gio.Cancellable();
7878 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,
82 g_object_path: object,
83 g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES })
79 g_interface_name: interface_info.name,
80 g_interface_info: interface_info,
81 g_name: bus_name,
82 g_object_path: object,
83 g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES });
8484
8585 this._setupProxy();
86 Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged')
87 Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal)
88 Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged')
86 Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged');
87 Util.connectSmart(this._proxy, 'g-signal', this, this._onProxySignal);
88 Util.connectSmart(this._proxy, 'notify::g-name-owner', this, '_nameOwnerChanged');
8989
9090 if (service !== bus_name && service.match(Util.BUS_ADDRESS_REGEX)) {
9191 this._uniqueId = service;
163163 get: () => {
164164 const v = this._proxy.get_cached_property(name);
165165 return v ? v.deep_unpack() : null;
166 }
166 },
167167 });
168168 }
169169
188188 let prop = null;
189189
190190 if (signal.startsWith('New'))
191 prop = signal.substr(3)
191 prop = signal.substr(3);
192192 else if (signal.startsWith('XAyatanaNew'))
193 prop = 'XAyatana' + signal.substr(11)
193 prop = `XAyatana${signal.substr(11)}`;
194194
195195 if (!prop)
196196 return;
197197
198198 [prop, `${prop}Name`, `${prop}Pixmap`].filter(p =>
199199 this._proxyPropertyList.includes(p)).forEach(p =>
200 Util.refreshPropertyOnProxy(this._proxy, p, {
201 skipEqualityCheck: p.endsWith('Pixmap'),
202 })
203 );
200 Util.refreshPropertyOnProxy(this._proxy, p, {
201 skipEqualityCheck: p.endsWith('Pixmap'),
202 }),
203 );
204204 }
205205
206206 async _onProxySignal(_proxy, _sender, signal, _params) {
213213 GLib.PRIORITY_DEFAULT_IDLE, MAX_UPDATE_FREQUENCY, this._cancellable);
214214 try {
215215 await this._signalsAccumulator;
216 this._accumulatedSignals.forEach((s) => this._translateNewSignals(s));
216 this._accumulatedSignals.forEach(s => this._translateNewSignals(s));
217217 this._accumulatedSignals.clear();
218218 } finally {
219219 delete this._signalsAccumulator;
220220 }
221221 }
222222
223 //public property getters
223 // public property getters
224224 get title() {
225225 return this._proxy.Title;
226226 }
227
227228 get id() {
228229 return this._proxy.Id;
229230 }
231
230232 get uniqueId() {
231233 return this._uniqueId;
232234 }
235
233236 get status() {
234237 return this._proxy.Status;
235238 }
239
236240 get label() {
237241 return this._proxy.XAyatanaLabel;
238242 }
243
239244 get menuPath() {
240245 if (this._proxy.Menu == '/NO_DBUSMENU')
241246 return null;
247252 return [
248253 this._proxy.AttentionIconName,
249254 this._proxy.AttentionIconPixmap,
250 this._proxy.IconThemePath
251 ]
255 this._proxy.IconThemePath,
256 ];
252257 }
253258
254259 get icon() {
255260 return [
256261 this._proxy.IconName,
257262 this._proxy.IconPixmap,
258 this._proxy.IconThemePath
259 ]
263 this._proxy.IconThemePath,
264 ];
260265 }
261266
262267 get overlayIcon() {
263268 return [
264269 this._proxy.OverlayIconName,
265270 this._proxy.OverlayIconPixmap,
266 this._proxy.IconThemePath
267 ]
271 this._proxy.IconThemePath,
272 ];
268273 }
269274
270275 get hasNameOwner() {
280285 let props = Object.keys(changed.unpack());
281286 let signalsToEmit = new Set();
282287
283 props.forEach((property) => {
288 props.forEach(property => {
284289 // some property changes require updates on our part,
285290 // a few need to be passed down to the displaying code
286291
287292 // all these can mean that the icon has to be changed
288293 if (property == 'Status' ||
289294 property.startsWith('Icon') ||
290 property.startsWith('AttentionIcon')) {
291 signalsToEmit.add('icon')
292 }
295 property.startsWith('AttentionIcon'))
296 signalsToEmit.add('icon');
297
293298
294299 // same for overlays
295300 if (property.startsWith('OverlayIcon'))
296 signalsToEmit.add('overlay-icon')
301 signalsToEmit.add('overlay-icon');
297302
298303 // this may make all of our icons invalid
299304 if (property == 'IconThemePath') {
300 signalsToEmit.add('icon')
301 signalsToEmit.add('overlay-icon')
305 signalsToEmit.add('icon');
306 signalsToEmit.add('overlay-icon');
302307 }
303308
304309 // the label will be handled elsewhere
305310 if (property == 'XAyatanaLabel')
306 signalsToEmit.add('label')
311 signalsToEmit.add('label');
307312
308313 if (property == 'Menu') {
309314 if (!this._checkIfReady() && this.isReady)
310 signalsToEmit.add('menu')
315 signalsToEmit.add('menu');
311316 }
312317
313318 // status updates may cause the indicator to be hidden
314319 if (property == 'Status')
315 signalsToEmit.add('status')
320 signalsToEmit.add('status');
316321 });
317322
318323 signalsToEmit.forEach(s => this.emit(s));
323328 }
324329
325330 destroy() {
326 this.emit('destroy')
327
328 this.disconnectAll()
331 this.emit('destroy');
332
333 this.disconnectAll();
329334 this._cancellable.cancel();
330335 this._nameWatcher && this._nameWatcher.destroy();
331336 Util.cancelRefreshPropertyOnProxy(this._proxy);
338343 // nor can we call any X11 functions. Luckily, the Activate method usually works fine.
339344 // parameters are "an hint to the item where to show eventual windows" [sic]
340345 // ... and don't seem to have any effect.
341 this._proxy.ActivateRemote(0, 0)
346 this._proxy.ActivateRemote(0, 0);
342347 }
343348
344349 secondaryActivate() {
345 this._proxy.SecondaryActivateRemote(0, 0)
350 this._proxy.SecondaryActivateRemote(0, 0);
346351 }
347352
348353 scroll(dx, dy) {
349354 if (dx != 0)
350 this._proxy.ScrollRemote(Math.floor(dx), 'horizontal')
355 this._proxy.ScrollRemote(Math.floor(dx), 'horizontal');
351356
352357 if (dy != 0)
353 this._proxy.ScrollRemote(Math.floor(dy), 'vertical')
358 this._proxy.ScrollRemote(Math.floor(dy), 'vertical');
354359 }
355360 };
356361 Signals.addSignalMethods(AppIndicator.prototype);
372377 let themeContext = St.ThemeContext.get_for_stage(global.stage);
373378 this.height = icon_size * themeContext.scale_factor;
374379
375 this._indicator = indicator
376 this._iconSize = icon_size
377 this._iconCache = new IconCache.IconCache()
380 this._indicator = indicator;
381 this._iconSize = icon_size;
382 this._iconCache = new IconCache.IconCache();
378383 this._cancellable = new Gio.Cancellable();
379384 this._loadingIcons = new Set();
380385
381 Util.connectSmart(this._indicator, 'icon', this, '_updateIcon')
382 Util.connectSmart(this._indicator, 'overlay-icon', this, '_updateOverlayIcon')
383 Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon')
384
385 Util.connectSmart(themeContext, 'notify::scale-factor', this, (tc) => {
386 Util.connectSmart(this._indicator, 'icon', this, '_updateIcon');
387 Util.connectSmart(this._indicator, 'overlay-icon', this, '_updateOverlayIcon');
388 Util.connectSmart(this._indicator, 'reset', this, '_invalidateIcon');
389
390 Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => {
386391 this.height = icon_size * tc.scale_factor;
387392 this._invalidateIcon();
388393 });
390395 Util.connectSmart(this._indicator, 'ready', this, () => {
391396 this._updateIconClass();
392397 this._invalidateIcon();
393 })
394
395 Util.connectSmart(Gtk.IconTheme.get_default(), 'changed', this, '_invalidateIcon')
398 });
399
400 Util.connectSmart(Gtk.IconTheme.get_default(), 'changed', this, '_invalidateIcon');
396401
397402 if (indicator.isReady)
398 this._invalidateIcon()
403 this._invalidateIcon();
399404
400405 this.connect('destroy', () => {
401406 this._iconCache.destroy();
419424 // Will look the icon up in the cache, if it's found
420425 // it will return it. Otherwise, it will create it and cache it.
421426 async _cacheOrCreateIconByName(iconSize, iconName, themePath) {
422 let {scale_factor} = St.ThemeContext.get_for_stage(global.stage);
427 let { scale_factor } = St.ThemeContext.get_for_stage(global.stage);
423428 let id = `${iconName}@${iconSize * scale_factor}${themePath || ''}`;
424429 let gicon = this._iconCache.get(id);
425430
497502 } else {
498503 this.icon_size = this._iconSize;
499504 return new Gio.FileIcon({
500 file: Gio.File.new_for_path(path)
505 file: Gio.File.new_for_path(path),
501506 });
502507 }
503508 } catch (e) {
509514
510515 _getIconInfo(name, themePath, size, scale) {
511516 let path = null;
512 if (name && name[0] == "/") {
513 //HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it.
517 if (name && name[0] == '/') {
518 // HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it.
514519 path = name;
515520 } else if (name) {
516521 // we manually look up the icon instead of letting st.icon do it for us
517522 // this allows us to sneak in an indicator provided search path and to avoid ugly upscaled icons
518523
519524 // indicator-application looks up a special "panel" variant, we just replicate that here
520 name = name + "-panel";
525 name += '-panel';
521526
522527 // icon info as returned by the lookup
523528 let iconInfo = null;
526531 let icon_theme = null;
527532 if (themePath) {
528533 icon_theme = new Gtk.IconTheme();
529 Gtk.IconTheme.get_default().get_search_path().forEach((path) => {
534 Gtk.IconTheme.get_default().get_search_path().forEach(path => {
530535 icon_theme.append_search_path(path);
531536 });
532537 icon_theme.append_search_path(themePath);
565570 await new PromiseUtils.IdlePromise(GLib.PRIORITY_LOW, cancellable);
566571
567572 for (let j = start; j < end; j += 4) {
568 let srcAlpha = src[j]
573 let srcAlpha = src[j];
569574
570575 dest[j] = src[j + 1]; /* red */
571576 dest[j + 1] = src[j + 2]; /* green */
583588 }
584589
585590 async _createIconFromPixmap(iconSize, iconPixmapArray) {
586 let {scale_factor} = St.ThemeContext.get_for_stage(global.stage);
587 iconSize = iconSize * scale_factor
591 let { scale_factor } = St.ThemeContext.get_for_stage(global.stage);
592 iconSize *= scale_factor;
588593 // the pixmap actually is an array of pixmaps with different sizes
589594 // we use the one that is smaller or equal the iconSize
590595
594599
595600 const sortedIconPixmapArray = iconPixmapArray.sort((pixmapA, pixmapB) => {
596601 // we sort smallest to biggest
597 const areaA = pixmapA[0] * pixmapA[1]
598 const areaB = pixmapB[0] * pixmapB[1]
599
600 return areaA - areaB
601 })
602 const areaA = pixmapA[0] * pixmapA[1];
603 const areaB = pixmapB[0] * pixmapB[1];
604
605 return areaA - areaB;
606 });
602607
603608 const qualifiedIconPixmapArray = sortedIconPixmapArray.filter(pixmap =>
604609 // we prefer any pixmap that is equal or bigger than our requested size
605 pixmap[0] >= iconSize && pixmap[1] >= iconSize)
606
607 const iconPixmap = qualifiedIconPixmapArray.length > 0 ?
608 qualifiedIconPixmapArray[0] : sortedIconPixmapArray.pop()
609
610 const [ width, height, bytes ] = iconPixmap
611 const rowStride = width * 4 // hopefully this is correct
610 pixmap[0] >= iconSize && pixmap[1] >= iconSize);
611
612 const iconPixmap = qualifiedIconPixmapArray.length > 0
613 ? qualifiedIconPixmapArray[0] : sortedIconPixmapArray.pop();
614
615 const [width, height, bytes] = iconPixmap;
616 const rowStride = width * 4; // hopefully this is correct
612617
613618 const id = `__PIXMAP_ICON_${width}x${height}`;
614619 if (this._loadingIcons.has(id)) {
647652 this.gicon = new Gio.EmblemedIcon({ gicon });
648653
649654 if (!(gicon instanceof GdkPixbuf.Pixbuf))
650 gicon.inUse = (this.gicon.get_icon() == gicon);
655 gicon.inUse = this.gicon.get_icon() == gicon;
651656 } else {
652657 this.gicon = null;
653658 Util.Logger.critical(`unable to update icon for ${this._indicator.id}`);
654659 }
660 } else if (gicon) {
661 this._emblem = new Gio.Emblem({ icon: gicon });
662
663 if (!(gicon instanceof GdkPixbuf.Pixbuf))
664 gicon.inUse = true;
655665 } else {
656 if (gicon) {
657 this._emblem = new Gio.Emblem({ icon: gicon });
658
659 if (!(gicon instanceof GdkPixbuf.Pixbuf))
660 gicon.inUse = true;
661 } else {
662 this._emblem = null;
663 Util.Logger.debug(`unable to update icon emblem for ${this._indicator.id}`);
664 }
666 this._emblem = null;
667 Util.Logger.debug(`unable to update icon emblem for ${this._indicator.id}`);
665668 }
666669
667670 if (this.gicon) {
676679 async _updateIconByType(iconType, iconSize) {
677680 let icon;
678681 switch (iconType) {
679 case SNIconType.ATTENTION:
680 icon = this._indicator.attentionIcon;
681 break;
682 case SNIconType.NORMAL:
683 icon = this._indicator.icon;
684 break;
685 case SNIconType.OVERLAY:
686 icon = this._indicator.overlayIcon;
687 break;
682 case SNIconType.ATTENTION:
683 icon = this._indicator.attentionIcon;
684 break;
685 case SNIconType.NORMAL:
686 icon = this._indicator.icon;
687 break;
688 case SNIconType.OVERLAY:
689 icon = this._indicator.overlayIcon;
690 break;
688691 }
689692
690693 const [name, pixmap, theme] = icon;
713716 let { gicon } = this.gicon;
714717
715718 if (gicon.inUse)
716 gicon.inUse = false
719 gicon.inUse = false;
717720 }
718721
719722 // we might need to use the AttentionIcon*, which have precedence over the normal icons
720 let iconType = this._indicator.status == SNIStatus.NEEDS_ATTENTION ?
721 SNIconType.ATTENTION : SNIconType.NORMAL;
723 let iconType = this._indicator.status == SNIStatus.NEEDS_ATTENTION
724 ? SNIconType.ATTENTION : SNIconType.NORMAL;
722725
723726 this._updateIconByType(iconType, this._iconSize);
724727 }
734737 // KDE hardcodes the overlay icon size to 10px (normal icon size 16px)
735738 // we approximate that ratio for other sizes, too.
736739 // our algorithms will always pick a smaller one instead of stretching it.
737 let iconSize = Math.floor(this._iconSize / 1.6)
740 let iconSize = Math.floor(this._iconSize / 1.6);
738741
739742 this._updateIconByType(SNIconType.OVERLAY, iconSize);
740743 }
741744
742745 // called when the icon theme changes
743746 _invalidateIcon() {
744 this._iconCache.clear()
747 this._iconCache.clear();
745748 this._cancelLoading();
746749
747 this._updateIcon()
748 this._updateOverlayIcon()
750 this._updateIcon();
751 this._updateOverlayIcon();
749752 }
750753 });
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
17 const Gio = imports.gi.Gio
18 const GLib = imports.gi.GLib
19 const GdkPixbuf = imports.gi.GdkPixbuf
20 const PopupMenu = imports.ui.popupMenu
21 const Signals = imports.signals
22 const St = imports.gi.St
23
24 const Extension = imports.misc.extensionUtils.getCurrentExtension()
25
26 const DBusInterfaces = Extension.imports.interfaces
15 const Atk = imports.gi.Atk;
16 const Clutter = imports.gi.Clutter;
17 const Gio = imports.gi.Gio;
18 const GLib = imports.gi.GLib;
19 const GdkPixbuf = imports.gi.GdkPixbuf;
20 const PopupMenu = imports.ui.popupMenu;
21 const Signals = imports.signals;
22 const St = imports.gi.St;
23
24 const Extension = imports.misc.extensionUtils.getCurrentExtension();
25
26 const DBusInterfaces = Extension.imports.interfaces;
2727 const PromiseUtils = Extension.imports.promiseUtils;
28 const Util = Extension.imports.util
29
30 //////////////////////////////////////////////////////////////////////////
28 const Util = Extension.imports.util;
29
30 // ////////////////////////////////////////////////////////////////////////
3131 // PART ONE: "ViewModel" backend implementation.
3232 // Both code and design are inspired by libdbusmenu
33 //////////////////////////////////////////////////////////////////////////
33 // ////////////////////////////////////////////////////////////////////////
3434
3535 /**
3636 * Saves menu property values and handles type checking and defaults
4141 this._props = new Map();
4242
4343 if (initial_properties) {
44 for (let i in initial_properties) {
45 this.set(i, initial_properties[i])
46 }
44 for (let i in initial_properties)
45 this.set(i, initial_properties[i]);
46
4747 }
4848 }
4949
5050 set(name, value) {
5151 if (name in PropertyStore.MandatedTypes && value && !value.is_of_type(PropertyStore.MandatedTypes[name]))
52 Util.Logger.warn("Cannot set property "+name+": type mismatch!")
52 Util.Logger.warn(`Cannot set property ${name}: type mismatch!`);
5353 else if (value)
5454 this._props.set(name, value);
5555 else
6161 if (prop)
6262 return prop;
6363 else if (name in PropertyStore.DefaultValues)
64 return PropertyStore.DefaultValues[name]
64 return PropertyStore.DefaultValues[name];
6565 else
66 return null
66 return null;
6767 }
6868 };
6969
7070 // we list all the properties we know and use here, so we won' have to deal with unexpected type mismatches
7171 PropertyStore.MandatedTypes = {
72 'visible' : GLib.VariantType.new("b"),
73 'enabled' : GLib.VariantType.new("b"),
74 'label' : GLib.VariantType.new("s"),
75 'type' : GLib.VariantType.new("s"),
76 'children-display' : GLib.VariantType.new("s"),
77 'icon-name' : GLib.VariantType.new("s"),
78 'icon-data' : GLib.VariantType.new("ay"),
79 'toggle-type' : GLib.VariantType.new("s"),
80 'toggle-state' : GLib.VariantType.new("i")
81 }
72 'visible': GLib.VariantType.new('b'),
73 'enabled': GLib.VariantType.new('b'),
74 'label': GLib.VariantType.new('s'),
75 'type': GLib.VariantType.new('s'),
76 'children-display': GLib.VariantType.new('s'),
77 'icon-name': GLib.VariantType.new('s'),
78 'icon-data': GLib.VariantType.new('ay'),
79 'toggle-type': GLib.VariantType.new('s'),
80 'toggle-state': GLib.VariantType.new('i'),
81 };
8282
8383 PropertyStore.DefaultValues = {
8484 'visible': GLib.Variant.new_boolean(true),
8585 'enabled': GLib.Variant.new_boolean(true),
86 'label' : GLib.Variant.new_string(''),
87 'type' : GLib.Variant.new_string("standard")
86 'label': GLib.Variant.new_string(''),
87 'type': GLib.Variant.new_string('standard'),
8888 // elements not in here must return null
89 }
89 };
9090
9191 /**
9292 * Represents a single menu item
9595
9696 // will steal the properties object
9797 constructor(client, id, properties, children_ids) {
98 this._client = client
99 this._id = id
100 this._propStore = new PropertyStore(properties)
101 this._children_ids = children_ids
98 this._client = client;
99 this._id = id;
100 this._propStore = new PropertyStore(properties);
101 this._children_ids = children_ids;
102102 }
103103
104104 property_get(prop_name) {
105 let prop = this.property_get_variant(prop_name)
106 return prop ? prop.get_string()[0] : null
105 let prop = this.property_get_variant(prop_name);
106 return prop ? prop.get_string()[0] : null;
107107 }
108108
109109 property_get_variant(prop_name) {
110 return this._propStore.get(prop_name)
110 return this._propStore.get(prop_name);
111111 }
112112
113113 property_get_bool(prop_name) {
114 let prop = this.property_get_variant(prop_name)
115 return prop ? prop.get_boolean() : false
114 let prop = this.property_get_variant(prop_name);
115 return prop ? prop.get_boolean() : false;
116116 }
117117
118118 property_get_int(prop_name) {
119 let prop = this.property_get_variant(prop_name)
120 return prop ? prop.get_int32() : 0
119 let prop = this.property_get_variant(prop_name);
120 return prop ? prop.get_int32() : 0;
121121 }
122122
123123 property_set(prop, value) {
124 this._propStore.set(prop, value)
125
126 this.emit('property-changed', prop, this.property_get_variant(prop))
124 this._propStore.set(prop, value);
125
126 this.emit('property-changed', prop, this.property_get_variant(prop));
127127 }
128128
129129 get_children_ids() {
130 return this._children_ids.concat() // clone it!
130 return this._children_ids.concat(); // clone it!
131131 }
132132
133133 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)
134 this._children_ids.splice(pos, 0, child_id);
135 this.emit('child-added', this._client.get_item(child_id), pos);
136136 }
137137
138138 remove_child(child_id) {
139139 // find it
140 let pos = -1
140 let pos = -1;
141141 for (let i = 0; i < this._children_ids.length; ++i) {
142142 if (this._children_ids[i] == child_id) {
143 pos = i
144 break
143 pos = i;
144 break;
145145 }
146146 }
147147
148148 if (pos < 0) {
149 Util.Logger.critical("Trying to remove child which doesn't exist")
149 Util.Logger.critical("Trying to remove child which doesn't exist");
150150 } else {
151 this._children_ids.splice(pos, 1)
152 this.emit('child-removed', this._client.get_item(child_id))
151 this._children_ids.splice(pos, 1);
152 this.emit('child-removed', this._client.get_item(child_id));
153153 }
154154 }
155155
156156 move_child(child_id, newpos) {
157157 // find the old position
158 let oldpos = -1
158 let oldpos = -1;
159159 for (let i = 0; i < this._children_ids.length; ++i) {
160160 if (this._children_ids[i] == child_id) {
161 oldpos = i
162 break
161 oldpos = i;
162 break;
163163 }
164164 }
165165
166166 if (oldpos < 0) {
167 Util.Logger.critical("tried to move child which wasn't in the list")
168 return
167 Util.Logger.critical("tried to move child which wasn't in the list");
168 return;
169169 }
170170
171171 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))
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));
175175 }
176176 }
177177
181181
182182 handle_event(event, data, timestamp) {
183183 if (!data)
184 data = GLib.Variant.new_int32(0)
185
186 this._client.send_event(this._id, event, data, timestamp)
184 data = GLib.Variant.new_int32(0);
185
186 this._client.send_event(this._id, event, data, timestamp);
187187 }
188188
189189 get_id() {
190 return this._id
190 return this._id;
191191 }
192192
193193 send_about_to_show() {
194 this._client.send_about_to_show(this._id)
195 }
196 }
197 Signals.addSignalMethods(DbusMenuItem.prototype)
194 this._client.send_about_to_show(this._id);
195 }
196 };
197 Signals.addSignalMethods(DbusMenuItem.prototype);
198198
199199
200200 const BusClientProxy = Gio.DBusProxy.makeProxyWrapper(DBusInterfaces.DBusMenu);
210210 busName,
211211 busPath,
212212 this._clientReady.bind(this),
213 this._cancellable)
213 this._cancellable);
214214 this._items = new Map([
215215 [
216216 0,
217217 new DbusMenuItem(this, 0, {
218218 'children-display': GLib.Variant.new_string('submenu'),
219219 }, []),
220 ]
220 ],
221221 ]);
222222
223223 // will be set to true if a layout update is requested while one is already in progress
224224 // then the handler that completes the layout update will request another update
225 this._flagLayoutUpdateRequired = false
226 this._flagLayoutUpdateInProgress = false
225 this._flagLayoutUpdateRequired = false;
226 this._flagLayoutUpdateInProgress = false;
227227
228228 // property requests are queued
229229 this._propertiesRequestedFor = new Set(/* ids */);
244244
245245 _requestLayoutUpdate() {
246246 if (this._flagLayoutUpdateInProgress)
247 this._flagLayoutUpdateRequired = true
247 this._flagLayoutUpdateRequired = true;
248248 else
249 this._beginLayoutUpdate()
249 this._beginLayoutUpdate();
250250 }
251251
252252 async _requestProperties(id) {
263263
264264 _beginRequestProperties() {
265265 this._proxy.GetGroupPropertiesRemote(
266 Array.from(this._propertiesRequestedFor),
267 [],
268 this._cancellable,
269 this._endRequestProperties.bind(this))
266 Array.from(this._propertiesRequestedFor),
267 [],
268 this._cancellable,
269 this._endRequestProperties.bind(this));
270270
271271 this._propertiesRequestedFor.clear();
272 return false
272 return false;
273273 }
274274
275275 _endRequestProperties(result, error) {
276276 if (error) {
277277 if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
278278 Util.Logger.warn(`Could not retrieve properties: ${error}`);
279 return
279 return;
280280 }
281281
282282 // for some funny reason, the result array is hidden in an array
283283 result[0].forEach(([id, properties]) => {
284284 let item = this._items.get(id);
285285 if (!item)
286 return
286 return;
287287
288288 for (let prop in properties)
289 item.property_set(prop, properties[prop])
289 item.property_set(prop, properties[prop]);
290290 });
291291 }
292292
293293 // Traverses the list of cached menu items and removes everyone that is not in the list
294294 // so we don't keep alive unused items
295295 _gcItems() {
296 let tag = new Date().getTime()
297
298 let toTraverse = [ 0 ]
296 let tag = new Date().getTime();
297
298 let toTraverse = [0];
299299 while (toTraverse.length > 0) {
300 let item = this.get_item(toTraverse.shift())
301 item._dbusClientGcTag = tag
302 Array.prototype.push.apply(toTraverse, item.get_children_ids())
300 let item = this.get_item(toTraverse.shift());
301 item._dbusClientGcTag = tag;
302 Array.prototype.push.apply(toTraverse, item.get_children_ids());
303303 }
304304
305305 this._items.forEach((i, id) => {
314314 // we only read the type property, because if the type changes after reading all properties,
315315 // the view would have to replace the item completely which we try to avoid
316316 this._proxy.GetLayoutRemote(0, -1,
317 [ 'type', 'children-display' ],
317 ['type', 'children-display'],
318318 this._cancellable,
319 this._endLayoutUpdate.bind(this))
320
321 this._flagLayoutUpdateRequired = false
322 this._flagLayoutUpdateInProgress = true
319 this._endLayoutUpdate.bind(this));
320
321 this._flagLayoutUpdateRequired = false;
322 this._flagLayoutUpdateInProgress = true;
323323 }
324324
325325 _endLayoutUpdate(result, error) {
326326 if (error) {
327327 if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
328328 Util.Logger.warn(`While reading menu layout on proxy ${this._proxy.g_name_owner}: ${error}`);
329 return
330 }
331
332 let [ revision, root ] = result
333 this._doLayoutUpdate(root)
334 this._gcItems()
329 return;
330 }
331
332 let [revision, root] = result;
333 this._doLayoutUpdate(root);
334 this._gcItems();
335335
336336 if (this._flagLayoutUpdateRequired)
337 this._beginLayoutUpdate()
337 this._beginLayoutUpdate();
338338 else
339 this._flagLayoutUpdateInProgress = false
339 this._flagLayoutUpdateInProgress = false;
340340 }
341341
342342 _doLayoutUpdate(item) {
343 let [ id, properties, children ] = item
344
345 let childrenUnpacked = children.map(c => c.deep_unpack())
346 let childrenIds = childrenUnpacked.map(c => c[0])
343 let [id, properties, children] = item;
344
345 let childrenUnpacked = children.map(c => c.deep_unpack());
346 let childrenIds = childrenUnpacked.map(c => c[0]);
347347
348348 // make sure all our children exist
349349 childrenUnpacked.forEach(c => this._doLayoutUpdate(c));
352352 const menuItem = this._items.get(id);
353353 if (menuItem) {
354354 // we do, update our properties if necessary
355 for (let prop in properties) {
356 menuItem.property_set(prop, properties[prop])
357 }
355 for (let prop in properties)
356 menuItem.property_set(prop, properties[prop]);
357
358358
359359 // make sure our children are all at the right place, and exist
360 let oldChildrenIds = menuItem.get_children_ids()
360 let oldChildrenIds = menuItem.get_children_ids();
361361 for (let i = 0; i < childrenIds.length; ++i) {
362362 // try to recycle an old child
363 let oldChild = -1
363 let oldChild = -1;
364364 for (let j = 0; j < oldChildrenIds.length; ++j) {
365365 if (oldChildrenIds[j] == childrenIds[i]) {
366 oldChild = oldChildrenIds.splice(j, 1)[0]
367 break
366 oldChild = oldChildrenIds.splice(j, 1)[0];
367 break;
368368 }
369369 }
370370
371371 if (oldChild < 0) {
372372 // no old child found, so create a new one!
373 menuItem.add_child(i, childrenIds[i])
373 menuItem.add_child(i, childrenIds[i]);
374374 } else {
375375 // old child found, reuse it!
376 menuItem.move_child(childrenIds[i], i)
376 menuItem.move_child(childrenIds[i], i);
377377 }
378378 }
379379
382382 } else {
383383 // we don't, so let's create us
384384 this._items.set(id, new DbusMenuItem(this, id, properties, childrenIds));
385 this._requestProperties(id)
386 }
387
388 return id
385 this._requestProperties(id);
386 }
387
388 return id;
389389 }
390390
391391 _clientReady(result, error) {
395395 return;
396396 }
397397
398 this._requestLayoutUpdate()
398 this._requestLayoutUpdate();
399399
400400 // listen for updated layouts and properties
401 this._proxy.connectSignal("LayoutUpdated", this._onLayoutUpdated.bind(this))
402 this._proxy.connectSignal("ItemsPropertiesUpdated", this._onPropertiesUpdated.bind(this))
401 this._proxy.connectSignal('LayoutUpdated', this._onLayoutUpdated.bind(this));
402 this._proxy.connectSignal('ItemsPropertiesUpdated', this._onPropertiesUpdated.bind(this));
403403 }
404404
405405 get_item(id) {
415415 * and don't return a boolean, so we need to support both cases */
416416 let connection = this._proxy.get_connection();
417417 connection.call(this._proxy.get_name(), this._proxy.get_object_path(),
418 this._proxy.get_interface_name(), 'AboutToShow',
419 new GLib.Variant("(i)", [id]), null,
420 Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => {
421 try {
422 let ret = proxy.call_finish(res);
423 if ((ret.is_of_type(new GLib.VariantType('(b)')) &&
418 this._proxy.get_interface_name(), 'AboutToShow',
419 new GLib.Variant('(i)', [id]), null,
420 Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => {
421 try {
422 let ret = proxy.call_finish(res);
423 if ((ret.is_of_type(new GLib.VariantType('(b)')) &&
424424 ret.get_child_value(0).get_boolean()) ||
425 ret.is_of_type(new GLib.VariantType('()'))) {
426 this._requestLayoutUpdate();
425 ret.is_of_type(new GLib.VariantType('()')))
426 this._requestLayoutUpdate();
427
428 } catch (e) {
429 Util.Logger.warn(`Impossible to send about-to-show to menu: ${e}`);
427430 }
428 } catch (e) {
429 Util.Logger.warn("Impossible to send about-to-show to menu: " + e);
430 }
431 });
431 });
432432 }
433433
434434 send_event(id, event, params, timestamp) {
435435 if (!this._proxy)
436 return
436 return;
437437
438438 this._proxy.EventRemote(id, event, params, timestamp, this._cancellable,
439 () => { /* we don't care */ })
439 () => { /* we don't care */ });
440440 }
441441
442442 _onLayoutUpdated() {
443 this._requestLayoutUpdate()
443 this._requestLayoutUpdate();
444444 }
445445
446446 _onPropertiesUpdated(proxy, name, [changed, removed]) {
447447 changed.forEach(([id, props]) => {
448448 let item = this._items.get(id);
449449 if (!item)
450 return
450 return;
451451
452452 for (let prop in props)
453 item.property_set(prop, props[prop])
453 item.property_set(prop, props[prop]);
454454 });
455455 removed.forEach(([id, propNames]) => {
456456 let item = this._items.get(id);
457457 if (!item)
458 return
458 return;
459459
460460 propNames.forEach(propName => item.property_set(propName, null));
461461 });
462462 }
463463
464464 destroy() {
465 this.emit('destroy')
465 this.emit('destroy');
466466
467467 this._cancellable.cancel();
468 Signals._disconnectAll.apply(this._proxy)
469
470 this._proxy = null
471 }
472 }
473 Signals.addSignalMethods(DBusClient.prototype)
474
475 //////////////////////////////////////////////////////////////////////////
468 Signals._disconnectAll.apply(this._proxy);
469
470 this._proxy = null;
471 }
472 };
473 Signals.addSignalMethods(DBusClient.prototype);
474
475 // ////////////////////////////////////////////////////////////////////////
476476 // PART TWO: "View" frontend implementation.
477 //////////////////////////////////////////////////////////////////////////
477 // ////////////////////////////////////////////////////////////////////////
478478
479479 // https://bugzilla.gnome.org/show_bug.cgi?id=731514
480480 // GNOME 3.10 and 3.12 can't open a nested submenu.
481481 // Patches have been written, but it's not clear when (if?) they will be applied.
482482 // We also don't know whether they will be backported to 3.10, so we will work around
483483 // it in the meantime. Offending versions can be clearly identified:
484 const NEED_NESTED_SUBMENU_FIX = '_setOpenedSubMenu' in PopupMenu.PopupMenu.prototype
484 const NEED_NESTED_SUBMENU_FIX = '_setOpenedSubMenu' in PopupMenu.PopupMenu.prototype;
485485
486486 /**
487487 * Creates new wrapper menu items and injects methods for managing them at runtime.
490490 * handlers, so any `this` will refer to a menu item create in createItem
491491 */
492492 const MenuItemFactory = {
493 createItem: function(client, dbusItem) {
493 createItem(client, dbusItem) {
494494 // 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('')
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('');
499499 else
500 var shellItem = new PopupMenu.PopupMenuItem("FIXME")
501
502 shellItem._dbusItem = dbusItem
503 shellItem._dbusClient = client
500 var shellItem = new PopupMenu.PopupMenuItem('FIXME');
501
502 shellItem._dbusItem = dbusItem;
503 shellItem._dbusClient = client;
504504
505505 if (shellItem instanceof PopupMenu.PopupMenuItem) {
506 shellItem._icon = new St.Icon({ style_class: 'popup-menu-icon', x_align: St.Align.END })
506 shellItem._icon = new St.Icon({ style_class: 'popup-menu-icon', x_align: St.Align.END });
507507 shellItem.add_child(shellItem._icon);
508508 shellItem.label.x_expand = true;
509509 }
510510
511511 // initialize our state
512 MenuItemFactory._updateLabel.call(shellItem)
513 MenuItemFactory._updateOrnament.call(shellItem)
514 MenuItemFactory._updateImage.call(shellItem)
515 MenuItemFactory._updateVisible.call(shellItem)
516 MenuItemFactory._updateSensitive.call(shellItem)
512 MenuItemFactory._updateLabel.call(shellItem);
513 MenuItemFactory._updateOrnament.call(shellItem);
514 MenuItemFactory._updateImage.call(shellItem);
515 MenuItemFactory._updateVisible.call(shellItem);
516 MenuItemFactory._updateSensitive.call(shellItem);
517517
518518 // initially create children
519519 if (shellItem instanceof PopupMenu.PopupSubMenuMenuItem) {
520 let children = dbusItem.get_children()
521 for (let i = 0; i < children.length; ++i) {
522 shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i]))
523 }
520 let children = dbusItem.get_children();
521 for (let i = 0; i < children.length; ++i)
522 shellItem.menu.addMenuItem(MenuItemFactory.createItem(client, children[i]));
523
524524 }
525525
526526 // now, connect various events
527 Util.connectSmart(dbusItem, 'property-changed', shellItem, MenuItemFactory._onPropertyChanged)
528 Util.connectSmart(dbusItem, 'child-added', shellItem, MenuItemFactory._onChildAdded)
529 Util.connectSmart(dbusItem, 'child-removed', shellItem, MenuItemFactory._onChildRemoved)
530 Util.connectSmart(dbusItem, 'child-moved', shellItem, MenuItemFactory._onChildMoved)
531 Util.connectSmart(shellItem, 'activate', shellItem, MenuItemFactory._onActivate)
527 Util.connectSmart(dbusItem, 'property-changed', shellItem, MenuItemFactory._onPropertyChanged);
528 Util.connectSmart(dbusItem, 'child-added', shellItem, MenuItemFactory._onChildAdded);
529 Util.connectSmart(dbusItem, 'child-removed', shellItem, MenuItemFactory._onChildRemoved);
530 Util.connectSmart(dbusItem, 'child-moved', shellItem, MenuItemFactory._onChildMoved);
531 Util.connectSmart(shellItem, 'activate', shellItem, MenuItemFactory._onActivate);
532532
533533 if (shellItem.menu)
534 Util.connectSmart(shellItem.menu, "open-state-changed", shellItem, MenuItemFactory._onOpenStateChanged)
535
536 return shellItem
534 Util.connectSmart(shellItem.menu, 'open-state-changed', shellItem, MenuItemFactory._onOpenStateChanged);
535
536 return shellItem;
537537 },
538538
539539 _onOpenStateChanged(menu, open) {
541541 if (NEED_NESTED_SUBMENU_FIX) {
542542 // close our own submenus
543543 if (menu._openedSubMenu)
544 menu._openedSubMenu.close(false)
544 menu._openedSubMenu.close(false);
545545
546546 // register ourselves and close sibling submenus
547547 if (menu._parent._openedSubMenu && menu._parent._openedSubMenu !== menu)
548 menu._parent._openedSubMenu.close(true)
549
550 menu._parent._openedSubMenu = menu
548 menu._parent._openedSubMenu.close(true);
549
550 menu._parent._openedSubMenu = menu;
551551 }
552552
553 this._dbusItem.handle_event("opened", null, 0)
554 this._dbusItem.send_about_to_show()
553 this._dbusItem.handle_event('opened', null, 0);
554 this._dbusItem.send_about_to_show();
555555 } else {
556556 if (NEED_NESTED_SUBMENU_FIX) {
557557 // close our own submenus
558558 if (menu._openedSubMenu)
559 menu._openedSubMenu.close(false)
559 menu._openedSubMenu.close(false);
560560 }
561561
562 this._dbusItem.handle_event("closed", null, 0)
562 this._dbusItem.handle_event('closed', null, 0);
563563 }
564564 },
565565
566566 _onActivate() {
567 this._dbusItem.handle_event("clicked", GLib.Variant.new("i", 0), 0)
567 this._dbusItem.handle_event('clicked', GLib.Variant.new('i', 0), 0);
568568 },
569569
570570 _onPropertyChanged(dbusItem, prop, value) {
571 if (prop == "toggle-type" || prop == "toggle-state")
572 MenuItemFactory._updateOrnament.call(this)
573 else if (prop == "label")
574 MenuItemFactory._updateLabel.call(this)
575 else if (prop == "enabled")
576 MenuItemFactory._updateSensitive.call(this)
577 else if (prop == "visible")
578 MenuItemFactory._updateVisible.call(this)
579 else if (prop == "icon-name" || prop == "icon-data")
580 MenuItemFactory._updateImage.call(this)
581 else if (prop == "type" || prop == "children-display")
582 MenuItemFactory._replaceSelf.call(this)
583 //else
571 if (prop == 'toggle-type' || prop == 'toggle-state')
572 MenuItemFactory._updateOrnament.call(this);
573 else if (prop == 'label')
574 MenuItemFactory._updateLabel.call(this);
575 else if (prop == 'enabled')
576 MenuItemFactory._updateSensitive.call(this);
577 else if (prop == 'visible')
578 MenuItemFactory._updateVisible.call(this);
579 else if (prop == 'icon-name' || prop == 'icon-data')
580 MenuItemFactory._updateImage.call(this);
581 else if (prop == 'type' || prop == 'children-display')
582 MenuItemFactory._replaceSelf.call(this);
583 // else
584584 // Util.Logger.debug("Unhandled property change: "+prop)
585585 },
586586
587587 _onChildAdded(dbusItem, child, position) {
588588 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
589 Util.Logger.warn("Tried to add a child to non-submenu item. Better recreate it as whole")
590 MenuItemFactory._replaceSelf.call(this)
589 Util.Logger.warn('Tried to add a child to non-submenu item. Better recreate it as whole');
590 MenuItemFactory._replaceSelf.call(this);
591591 } else {
592 this.menu.addMenuItem(MenuItemFactory.createItem(this._dbusClient, child), position)
592 this.menu.addMenuItem(MenuItemFactory.createItem(this._dbusClient, child), position);
593593 }
594594 },
595595
596596 _onChildRemoved(dbusItem, child) {
597597 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
598 Util.Logger.warn("Tried to remove a child from non-submenu item. Better recreate it as whole")
599 MenuItemFactory._replaceSelf.call(this)
598 Util.Logger.warn('Tried to remove a child from non-submenu item. Better recreate it as whole');
599 MenuItemFactory._replaceSelf.call(this);
600600 } else {
601601 // find it!
602 this.menu._getMenuItems().forEach((item) => {
602 this.menu._getMenuItems().forEach(item => {
603603 if (item._dbusItem == child)
604 item.destroy()
605 })
604 item.destroy();
605 });
606606 }
607607 },
608608
609609 _onChildMoved(dbusItem, child, oldpos, newpos) {
610610 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
611 Util.Logger.warn("Tried to move a child in non-submenu item. Better recreate it as whole")
612 MenuItemFactory._replaceSelf.call(this)
611 Util.Logger.warn('Tried to move a child in non-submenu item. Better recreate it as whole');
612 MenuItemFactory._replaceSelf.call(this);
613613 } else {
614 MenuUtils.moveItemInMenu(this.menu, child, newpos)
614 MenuUtils.moveItemInMenu(this.menu, child, newpos);
615615 }
616616 },
617617
618618 _updateLabel() {
619 let label = this._dbusItem.property_get("label").replace(/_([^_])/, "$1")
619 let label = this._dbusItem.property_get('label').replace(/_([^_])/, '$1');
620620
621621 if (this.label) // especially on GS3.8, the separator item might not even have a hidden label
622 this.label.set_text(label)
622 this.label.set_text(label);
623623 },
624624
625625 _updateOrnament() {
626 if (!this.setOrnament) return // separators and alike might not have gotten the polyfill
627
628 if (this._dbusItem.property_get("toggle-type") == "checkmark" && this._dbusItem.property_get_int("toggle-state"))
629 this.setOrnament(PopupMenu.Ornament.CHECK)
630 else if (this._dbusItem.property_get("toggle-type") == "radio" && this._dbusItem.property_get_int("toggle-state"))
631 this.setOrnament(PopupMenu.Ornament.DOT)
626 if (!this.setOrnament)
627 return; // separators and alike might not have gotten the polyfill
628
629 if (this._dbusItem.property_get('toggle-type') == 'checkmark' && this._dbusItem.property_get_int('toggle-state'))
630 this.setOrnament(PopupMenu.Ornament.CHECK);
631 else if (this._dbusItem.property_get('toggle-type') == 'radio' && this._dbusItem.property_get_int('toggle-state'))
632 this.setOrnament(PopupMenu.Ornament.DOT);
632633 else
633 this.setOrnament(PopupMenu.Ornament.NONE)
634 this.setOrnament(PopupMenu.Ornament.NONE);
634635 },
635636
636637 _updateImage() {
637 if (!this._icon) return // might be missing on submenus / separators
638
639 let iconName = this._dbusItem.property_get("icon-name")
640 let iconData = this._dbusItem.property_get_variant("icon-data")
638 if (!this._icon)
639 return; // might be missing on submenus / separators
640
641 let iconName = this._dbusItem.property_get('icon-name');
642 let iconData = this._dbusItem.property_get_variant('icon-data');
641643 if (iconName)
642 this._icon.icon_name = iconName
644 this._icon.icon_name = iconName;
643645 else if (iconData)
644 this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null)
646 this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null);
645647 },
646648
647649 _updateVisible() {
648 this.visible = this._dbusItem.property_get_bool("visible")
650 this.visible = this._dbusItem.property_get_bool('visible');
649651 },
650652
651653 _updateSensitive() {
652 this.setSensitive(this._dbusItem.property_get_bool("enabled"))
654 this.setSensitive(this._dbusItem.property_get_bool('enabled'));
653655 },
654656
655657 _replaceSelf(newSelf) {
656658 // create our new self if needed
657659 if (!newSelf)
658 newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem)
660 newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem);
659661
660662 // first, we need to find our old position
661 let pos = -1
662 let family = this._parent._getMenuItems()
663 let pos = -1;
664 let family = this._parent._getMenuItems();
663665 for (let i = 0; i < family.length; ++i) {
664666 if (family[i] === this)
665 pos = i
667 pos = i;
666668 }
667669
668670 if (pos < 0)
669 throw new Error("DBusMenu: can't replace non existing menu item")
671 throw new Error("DBusMenu: can't replace non existing menu item");
670672
671673
672674 // add our new self while we're still alive
673 this._parent.addMenuItem(newSelf, pos)
675 this._parent.addMenuItem(newSelf, pos);
674676
675677 // now destroy our old self
676 this.destroy()
677 }
678 }
678 this.destroy();
679 },
680 };
679681
680682 /**
681683 * Utility functions not necessarily belonging into the item factory
682684 */
683685 const MenuUtils = {
684686 moveItemInMenu(menu, dbusItem, newpos) {
685 //HACK: we're really getting into the internals of the PopupMenu implementation
687 // HACK: we're really getting into the internals of the PopupMenu implementation
686688
687689 // First, find our wrapper. Children tend to lie. We do not trust the old positioning.
688 let family = menu._getMenuItems()
690 let family = menu._getMenuItems();
689691 for (let i = 0; i < family.length; ++i) {
690692 if (family[i]._dbusItem == dbusItem) {
691693 // now, remove it
692 menu.box.remove_child(family[i])
694 menu.box.remove_child(family[i]);
693695
694696 // and add it again somewhere else
695697 if (newpos < family.length && family[newpos] != family[i])
696 menu.box.insert_child_below(family[i], family[newpos])
698 menu.box.insert_child_below(family[i], family[newpos]);
697699 else
698 menu.box.add(family[i])
700 menu.box.add(family[i]);
699701
700702 // skip the rest
701 return
703 return;
702704 }
703705 }
704 }
705 }
706 },
707 };
706708
707709
708710 /**
713715 var Client = class AppIndicators_Client {
714716
715717 constructor(busName, path) {
716 this._busName = busName
717 this._busPath = path
718 this._client = new DBusClient(busName, path)
719 this._rootMenu = null // the shell menu
720 this._rootItem = null // the DbusMenuItem for the root
718 this._busName = busName;
719 this._busPath = path;
720 this._client = new DBusClient(busName, path);
721 this._rootMenu = null; // the shell menu
722 this._rootItem = null; // the DbusMenuItem for the root
721723 }
722724
723725 get isReady() {
727729 // this will attach the client to an already existing menu that will be used as the root menu.
728730 // it will also connect the client to be automatically destroyed when the menu dies.
729731 attachToMenu(menu) {
730 this._rootMenu = menu
731 this._rootItem = this._client.get_root()
732 this._rootMenu = menu;
733 this._rootItem = this._client.get_root();
732734
733735 // cleanup: remove existing children (just in case)
734 this._rootMenu.removeAll()
736 this._rootMenu.removeAll();
735737
736738 if (NEED_NESTED_SUBMENU_FIX)
737 menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this)
739 menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this);
738740
739741 // connect handlers
740 Util.connectSmart(menu, 'open-state-changed', this, '_onMenuOpened')
741 Util.connectSmart(menu, 'destroy', this, 'destroy')
742
743 Util.connectSmart(this._rootItem, 'child-added', this, '_onRootChildAdded')
744 Util.connectSmart(this._rootItem, 'child-removed', this, '_onRootChildRemoved')
745 Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved')
742 Util.connectSmart(menu, 'open-state-changed', this, '_onMenuOpened');
743 Util.connectSmart(menu, 'destroy', this, 'destroy');
744
745 Util.connectSmart(this._rootItem, 'child-added', this, '_onRootChildAdded');
746 Util.connectSmart(this._rootItem, 'child-removed', this, '_onRootChildRemoved');
747 Util.connectSmart(this._rootItem, 'child-moved', this, '_onRootChildMoved');
746748
747749 // Dropbox requires us to call AboutToShow(0) first
748 this._rootItem.send_about_to_show()
750 this._rootItem.send_about_to_show();
749751
750752 // fill the menu for the first time
751753 this._rootItem.get_children().forEach(child =>
752 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child))
754 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)),
753755 );
754756 }
755757
756758 _setOpenedSubmenu(submenu) {
757759 if (!submenu)
758 return
760 return;
759761
760762 if (submenu._parent != this._rootMenu)
761 return
763 return;
762764
763765 if (submenu === this._openedSubMenu)
764 return
766 return;
765767
766768 if (this._openedSubMenu && this._openedSubMenu.isOpen)
767 this._openedSubMenu.close(true)
768
769 this._openedSubMenu = submenu
769 this._openedSubMenu.close(true);
770
771 this._openedSubMenu = submenu;
770772 }
771773
772774 _onRootChildAdded(dbusItem, child, position) {
773 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position)
775 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position);
774776 }
775777
776778 _onRootChildRemoved(dbusItem, child) {
777779 // children like to play hide and seek
778780 // but we know how to find it for sure!
779 this._rootMenu._getMenuItems().forEach((item) => {
781 this._rootMenu._getMenuItems().forEach(item => {
780782 if (item._dbusItem == child)
781 item.destroy()
782 })
783 item.destroy();
784 });
783785 }
784786
785787 _onRootChildMoved(dbusItem, child, oldpos, newpos) {
786 MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos)
788 MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos);
787789 }
788790
789791 _onMenuOpened(menu, state) {
790 if (!this._rootItem) return
792 if (!this._rootItem)
793 return;
791794
792795 if (state) {
793796 if (this._openedSubMenu && this._openedSubMenu.isOpen)
794 this._openedSubMenu.close()
795
796 this._rootItem.handle_event("opened", null, 0)
797 this._rootItem.send_about_to_show()
797 this._openedSubMenu.close();
798
799 this._rootItem.handle_event('opened', null, 0);
800 this._rootItem.send_about_to_show();
798801 } else {
799 this._rootItem.handle_event("closed", null, 0)
802 this._rootItem.handle_event('closed', null, 0);
800803 }
801804 }
802805
803806 destroy() {
804 this.emit('destroy')
807 this.emit('destroy');
805808
806809 if (this._client)
807 this._client.destroy()
808
809 this._client = null
810 this._rootItem = null
811 this._rootMenu = null
812 }
813 }
814 Signals.addSignalMethods(Client.prototype)
810 this._client.destroy();
811
812 this._client = null;
813 this._rootItem = null;
814 this._rootMenu = null;
815 }
816 };
817 Signals.addSignalMethods(Client.prototype);
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 const Gio = imports.gi.Gio;
16 const Extension = imports.misc.extensionUtils.getCurrentExtension()
16 const Extension = imports.misc.extensionUtils.getCurrentExtension();
1717
18 const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher
19 const Util = Extension.imports.util
18 const StatusNotifierWatcher = Extension.imports.statusNotifierWatcher;
19 const Util = Extension.imports.util;
2020
2121 let statusNotifierWatcher = null;
2222 let isEnabled = false;
2626 watchDog = new Util.NameWatcher(StatusNotifierWatcher.WATCHER_BUS_NAME);
2727 watchDog.connect('vanished', () => maybe_enable_after_name_available());
2828
29 //HACK: we want to leave the watchdog alive when disabling the extension,
29 // HACK: we want to leave the watchdog alive when disabling the extension,
3030 // but if we are being reloaded, we destroy it since it could be considered
3131 // a leak and spams our log, too.
32 if (typeof global['--appindicator-extension-on-reload'] == 'function')
33 global['--appindicator-extension-on-reload']()
32 if (typeof global['--appindicator-extension-on-reload'] === 'function')
33 global['--appindicator-extension-on-reload']();
3434
3535 global['--appindicator-extension-on-reload'] = () => {
36 Util.Logger.debug("Reload detected, destroying old watchdog")
36 Util.Logger.debug('Reload detected, destroying old watchdog');
3737 watchDog.destroy();
38 }
38 };
3939 }
4040
41 //FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly.
41 // FIXME: when entering/leaving the lock screen, the extension might be enabled/disabled rapidly.
4242 // This will create very bad side effects in case we were not done unowning the name while trying
4343 // to own it again. Since g_bus_unown_name doesn't fire any callback when it's done, we need to
4444 // monitor the bus manually to find out when the name vanished so we can reclaim it again.
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 GLib = imports.gi.GLib
17 const Gio = imports.gi.Gio
16 const GLib = imports.gi.GLib;
17 const Gio = imports.gi.Gio;
1818
1919 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2020 const PromiseUtils = Extension.imports.promiseUtils;
3333 var IconCache = class AppIndicators_IconCache {
3434 constructor() {
3535 this._cache = new Map();
36 this._lifetime = new Map(); //we don't want to attach lifetime to the object
36 this._lifetime = new Map(); // we don't want to attach lifetime to the object
3737 }
3838
3939 add(id, icon) {
8282
8383 // marks all the icons as removable, if something doesn't claim them before
8484 weakClear() {
85 this._cache.forEach((icon) => icon.inUse = false);
85 this._cache.forEach(icon => icon.inUse = false);
8686 this._checkGC();
8787 }
8888
108108 let cacheIsEmpty = this._cache.size == 0;
109109
110110 if (!cacheIsEmpty && !this._gcTimeout) {
111 Util.Logger.debug("IconCache: garbage collector started");
111 Util.Logger.debug('IconCache: garbage collector started');
112112 this._gcTimeout = new PromiseUtils.TimeoutSecondsPromise(GC_INTERVAL,
113113 GLib.PRIORITY_LOW);
114114 await this._gcTimeout;
115115 } else if (cacheIsEmpty && this._gcTimeout) {
116 Util.Logger.debug("IconCache: garbage collector stopped");
116 Util.Logger.debug('IconCache: garbage collector stopped');
117117 this._gcTimeout.cancel();
118118 delete this._gcTimeout;
119119 }
122122 _gc() {
123123 let time = new Date().getTime();
124124 this._cache.forEach((icon, id) => {
125 if (icon.inUse) {
125 if (icon.inUse)
126126 Util.Logger.debug(`IconCache: ${id} is in use.`);
127 } else if (this._lifetime.get(id) < time) {
127 else if (this._lifetime.get(id) < time)
128128 this._remove(id);
129 } else {
129 else
130130 Util.Logger.debug(`IconCache: ${id} survived this round.`);
131 }
131
132132 });
133133
134134 return true;
3333
3434 (() => {
3535
36 var app = new Gtk.Application({
37 application_id: null
38 });
39
40 var window = null;
41
42 app.connect("activate", () => {
43 window.present();
44 });
45
46 app.connect("startup", () => {
47 window = new Gtk.ApplicationWindow({
48 title: "test",
49 application: app
36 var app = new Gtk.Application({
37 application_id: null,
5038 });
5139
52 let getRandomIcon = () =>
53 iconsPool[Math.floor(Math.random() * (iconsPool.length - 1))];
54
55 let setRandomIconPath = () => {
56 let iconName = getRandomIcon();
57 let iconInfo = Gtk.IconTheme.get_default().lookup_icon(iconName,
58 16, Gtk.IconLookupFlags.GENERIC_FALLBACK);
59 let iconFile = Gio.File.new_for_path(iconInfo.get_filename());
60 let [, extension] = iconFile.get_basename().split('.');
61 let newName = `${iconName}-${Math.floor(Math.random() * 100)}.${extension}`;
62 let newFile = Gio.File.new_for_path(
63 `${GLib.dir_make_tmp('indicator-test-XXXXXX')}/${newName}`);
64 iconFile.copy(newFile, Gio.FileCopyFlagsOVERWRITE, null, null);
65
66 indicator.set_icon_theme_path(newFile.get_parent().get_path());
67 indicator.set_icon(newFile.get_basename());
68 }
69
70 var menu = new Gtk.Menu();
71
72 var item = Gtk.MenuItem.new_with_label("A standard item");
73 menu.append(item);
74
75 item = Gtk.MenuItem.new_with_label("Foo");
76 menu.append(item);
77
78 item = Gtk.ImageMenuItem.new_with_label("Calculator");
79 item.image = Gtk.Image.new_from_icon_name("gnome-calculator", Gtk.IconSize.MENU);
80 menu.append(item);
81
82 item = Gtk.CheckMenuItem.new_with_label("Check me!");
83 menu.append(item);
84
85 item = Gtk.MenuItem.new_with_label("Blub");
86 let sub = new Gtk.Menu();
87 item.set_submenu(sub);
88 menu.append(item);
89
90 item = Gtk.MenuItem.new_with_label("Blubdablub");
91 sub.append(item);
92
93 item = new Gtk.SeparatorMenuItem();
94 menu.append(item);
95
96 item = Gtk.MenuItem.new_with_label("Foo");
97 menu.append(item);
98
99 let submenu = new Gtk.Menu();
100 item.set_submenu(submenu);
101
102 item = Gtk.MenuItem.new_with_label("Hello");
103 submenu.append(item);
104
105 item = Gtk.MenuItem.new_with_label("Nested");
106 submenu.append(item);
107
108 let submenu1 = new Gtk.Menu();
109 item.set_submenu(submenu1);
110
111 item = Gtk.MenuItem.new_with_label("Another nested");
112 submenu.append(item);
113
114 let submenu2 = new Gtk.Menu();
115 item.set_submenu(submenu2);
116
117 item = Gtk.MenuItem.new_with_label("Some other item");
118 submenu1.append(item);
119
120 item = Gtk.MenuItem.new_with_label("abcdefg");
121 submenu2.append(item);
122
123 item = new Gtk.SeparatorMenuItem();
124 menu.append(item);
125
126 var group = [];
127
128 for (let i = 0; i < 5; ++i) {
129 item = Gtk.RadioMenuItem.new_with_label(group, "Example Radio "+i);
130 group = Gtk.RadioMenuItem.prototype.get_group.apply(item)//.get_group();
131 if (i == 1)
132 item.set_active(true);
133 menu.append(item);
134 }
135
136 item = new Gtk.SeparatorMenuItem();
137 menu.append(item);
138
139 item = Gtk.MenuItem.new_with_label("Set Label");
140 item.connect('activate', () => {
141 indicator.set_label(''+new Date().getTime(), 'Blub');
40 var window = null;
41
42 app.connect('activate', () => {
43 window.present();
14244 });
143 menu.append(item);
144
145 item = Gtk.MenuItem.new_with_label("Unset Label");
146 item.connect('activate', () => {
147 indicator.set_label('', '');
148 })
149 menu.append(item);
150
151 item = Gtk.MenuItem.new_with_label("Autodestroy Label");
152 item.connect('activate', () => {
153 let i = 30;
154 GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
155 indicator.set_label(i > 0 ? `Label timeout ${i--}` : '', '');
156 return (i >= 0);
157 });
158 })
159 menu.append(item);
160
161 item = Gtk.MenuItem.new_with_label('Set Random icon');
162 item.connect('activate', () => indicator.set_icon(getRandomIcon()));
163 menu.append(item);
164
165 item = Gtk.MenuItem.new_with_label('Set Random custom theme icon');
166 item.connect('activate', setRandomIconPath);
167 menu.append(item);
168
169 item = Gtk.CheckMenuItem.new_with_label('Toggle Label and Icon');
170 item.connect('activate', (item) => {
171 if (item.get_active()) {
45
46 app.connect('startup', () => {
47 window = new Gtk.ApplicationWindow({
48 title: 'test',
49 application: app,
50 });
51
52 let getRandomIcon = () =>
53 iconsPool[Math.floor(Math.random() * (iconsPool.length - 1))];
54
55 let setRandomIconPath = () => {
56 let iconName = getRandomIcon();
57 let iconInfo = Gtk.IconTheme.get_default().lookup_icon(iconName,
58 16, Gtk.IconLookupFlags.GENERIC_FALLBACK);
59 let iconFile = Gio.File.new_for_path(iconInfo.get_filename());
60 let [, extension] = iconFile.get_basename().split('.');
61 let newName = `${iconName}-${Math.floor(Math.random() * 100)}.${extension}`;
62 let newFile = Gio.File.new_for_path(
63 `${GLib.dir_make_tmp('indicator-test-XXXXXX')}/${newName}`);
64 iconFile.copy(newFile, Gio.FileCopyFlagsOVERWRITE, null, null);
65
66 indicator.set_icon_theme_path(newFile.get_parent().get_path());
67 indicator.set_icon(newFile.get_basename());
68 };
69
70 var menu = new Gtk.Menu();
71
72 var item = Gtk.MenuItem.new_with_label('A standard item');
73 menu.append(item);
74
75 item = Gtk.MenuItem.new_with_label('Foo');
76 menu.append(item);
77
78 item = Gtk.ImageMenuItem.new_with_label('Calculator');
79 item.image = Gtk.Image.new_from_icon_name('gnome-calculator', Gtk.IconSize.MENU);
80 menu.append(item);
81
82 item = Gtk.CheckMenuItem.new_with_label('Check me!');
83 menu.append(item);
84
85 item = Gtk.MenuItem.new_with_label('Blub');
86 let sub = new Gtk.Menu();
87 item.set_submenu(sub);
88 menu.append(item);
89
90 item = Gtk.MenuItem.new_with_label('Blubdablub');
91 sub.append(item);
92
93 item = new Gtk.SeparatorMenuItem();
94 menu.append(item);
95
96 item = Gtk.MenuItem.new_with_label('Foo');
97 menu.append(item);
98
99 let submenu = new Gtk.Menu();
100 item.set_submenu(submenu);
101
102 item = Gtk.MenuItem.new_with_label('Hello');
103 submenu.append(item);
104
105 item = Gtk.MenuItem.new_with_label('Nested');
106 submenu.append(item);
107
108 let submenu1 = new Gtk.Menu();
109 item.set_submenu(submenu1);
110
111 item = Gtk.MenuItem.new_with_label('Another nested');
112 submenu.append(item);
113
114 let submenu2 = new Gtk.Menu();
115 item.set_submenu(submenu2);
116
117 item = Gtk.MenuItem.new_with_label('Some other item');
118 submenu1.append(item);
119
120 item = Gtk.MenuItem.new_with_label('abcdefg');
121 submenu2.append(item);
122
123 item = new Gtk.SeparatorMenuItem();
124 menu.append(item);
125
126 var group = [];
127
128 for (let i = 0; i < 5; ++i) {
129 item = Gtk.RadioMenuItem.new_with_label(group, `Example Radio ${i}`);
130 group = Gtk.RadioMenuItem.prototype.get_group.apply(item);// .get_group();
131 if (i === 1)
132 item.set_active(true);
133 menu.append(item);
134 }
135
136 item = new Gtk.SeparatorMenuItem();
137 menu.append(item);
138
139 item = Gtk.MenuItem.new_with_label('Set Label');
140 item.connect('activate', () => {
172141 indicator.set_label(`${new Date().getTime()}`, 'Blub');
173 item.connect('activate', () => indicator.set_icon(getRandomIcon()));
174 } else {
142 });
143 menu.append(item);
144
145 item = Gtk.MenuItem.new_with_label('Unset Label');
146 item.connect('activate', () => {
175147 indicator.set_label('', '');
176 indicator.set_icon(DEFAULT_ICON);
177 }
178 })
179 menu.append(item);
180 let toggleBrandingItem = item;
181
182 item = Gtk.CheckMenuItem.new_with_label('Toggle Attention');
183 let toggleAttentionId = item.connect('activate', () => {
184 indicator.set_status(indicator.get_status() != AppIndicator.IndicatorStatus.ATTENTION ?
185 AppIndicator.IndicatorStatus.ATTENTION :
186 AppIndicator.IndicatorStatus.ACTIVE);
148 });
149 menu.append(item);
150
151 item = Gtk.MenuItem.new_with_label('Autodestroy Label');
152 item.connect('activate', () => {
153 let i = 30;
154 GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
155 indicator.set_label(i > 0 ? `Label timeout ${i--}` : '', '');
156 return i >= 0;
157 });
158 });
159 menu.append(item);
160
161 item = Gtk.MenuItem.new_with_label('Set Random icon');
162 item.connect('activate', () => indicator.set_icon(getRandomIcon()));
163 menu.append(item);
164
165 item = Gtk.MenuItem.new_with_label('Set Random custom theme icon');
166 item.connect('activate', setRandomIconPath);
167 menu.append(item);
168
169 item = Gtk.CheckMenuItem.new_with_label('Toggle Label and Icon');
170 item.connect('activate', it => {
171 if (it.get_active()) {
172 indicator.set_label(`${new Date().getTime()}`, 'Blub');
173 it.connect('activate', () => indicator.set_icon(getRandomIcon()));
174 } else {
175 indicator.set_label('', '');
176 indicator.set_icon(DEFAULT_ICON);
177 }
178 });
179 menu.append(item);
180 let toggleBrandingItem = item;
181
182 item = Gtk.CheckMenuItem.new_with_label('Toggle Attention');
183 let toggleAttentionId = item.connect('activate', () => {
184 indicator.set_status(indicator.get_status() !== AppIndicator.IndicatorStatus.ATTENTION
185 ? AppIndicator.IndicatorStatus.ATTENTION
186 : AppIndicator.IndicatorStatus.ACTIVE);
187 });
188 menu.append(item);
189 let toggleAttentionItem = item;
190
191 item = new Gtk.SeparatorMenuItem();
192 menu.append(item);
193
194 /* Double separaptors test */
195
196 item = new Gtk.SeparatorMenuItem();
197 menu.append(item);
198
199 /* Simulate similar behavior of #226 and #236 */
200 item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates');
201 item.connect('activate', it => {
202 if (it.get_active()) {
203 it._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => {
204 setRandomIconPath();
205 indicator.set_label(`${new Date().getSeconds()}`, '');
206 return GLib.SOURCE_CONTINUE;
207 });
208 } else {
209 GLib.source_remove(item._timeoutID);
210 delete item._timeoutID;
211 }
212 });
213 menu.append(item);
214
215 item = Gtk.MenuItem.new_with_label('Hide for some time');
216 item.connect('activate', () => {
217 indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE);
218 GLib.timeout_add(0, 5000, () => {
219 indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
220 return false;
221 });
222 });
223 menu.append(item);
224
225 item = Gtk.MenuItem.new_with_label('Close in 5 seconds');
226 item.connect('activate', () => {
227 GLib.timeout_add(0, 5000, () => {
228 app.quit();
229 return false;
230 });
231 });
232 menu.append(item);
233
234 menu.show_all();
235
236 var indicator = AppIndicator.Indicator.new('Hello', 'indicator-test', AppIndicator.IndicatorCategory.APPLICATION_STATUS);
237
238 indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
239 indicator.set_icon(DEFAULT_ICON);
240 indicator.set_attention_icon(ATTENTION_ICON);
241 indicator.set_menu(menu);
242 indicator.set_secondary_activate_target(toggleBrandingItem);
243
244 indicator.connect('connection-changed', (_indicator, connected) => {
245 print(`Signal "connection-changed" emitted. Connected: ${connected}`);
246 });
247 indicator.connect('new-attention-icon', () => {
248 print('Signal "new-attention-icon" emitted.');
249 });
250 indicator.connect('new-icon', () => {
251 let icon = '<none>';
252 if (indicator.get_status() === AppIndicator.IndicatorStatus.ATTENTION)
253 icon = indicator.get_attention_icon();
254 else if (indicator.get_status() === AppIndicator.IndicatorStatus.ACTIVE)
255 icon = indicator.get_icon();
256
257 print(`Signal "new-icon" emitted. Icon: ${icon}`);
258 });
259 indicator.connect('new-icon-theme-path', (_indicator, path) => {
260 print(`Signal "new-icon-theme-path" emitted. Path: ${path}`);
261 });
262 indicator.connect('new-label', (_indicator, label, guide) => {
263 print(`Signal "new-label" emitted. Label: ${label}, Guide: ${guide}`);
264 });
265 indicator.connect('new-status', (_indicator, status) => {
266 print(`Signal "new-status" emitted. Status: ${status}`);
267
268 toggleAttentionItem.block_signal_handler(toggleAttentionId);
269 toggleAttentionItem.set_active(status === 'NeedsAttention');
270 toggleAttentionItem.unblock_signal_handler(toggleAttentionId);
271 });
272 indicator.connect('scroll-event', (_indicator, steps, direction) => {
273 print(`Signal "scroll-event" emitted. Steps: ${steps}, Direction: ${direction}`);
274 let currentIndex = iconsPool.indexOf(indicator.get_icon());
275 let iconIndex;
276
277 if (direction === ScrollType.UP)
278 iconIndex = (currentIndex + 1) % iconsPool.length;
279 else
280 iconIndex = (currentIndex <= 0 ? iconsPool.length : currentIndex) - 1;
281
282
283 indicator.set_icon(iconsPool[iconIndex]);
284 });
187285 });
188 menu.append(item);
189 let toggleAttentionItem = item;
190
191 item = new Gtk.SeparatorMenuItem();
192 menu.append(item);
193
194 /* Double separaptors test */
195
196 item = new Gtk.SeparatorMenuItem();
197 menu.append(item);
198
199 /* Simulate similar behavior of #226 and #236 */
200 item = Gtk.CheckMenuItem.new_with_label('Crazy icons updates');
201 item.connect('activate', (item) => {
202 if (item.get_active()) {
203 item._timeoutID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 16, () => {
204 setRandomIconPath();
205 indicator.set_label(`${new Date().getSeconds()}`, '');
206 return GLib.SOURCE_CONTINUE;
207 });
208 } else {
209 GLib.source_remove(item._timeoutID);
210 delete item._timeoutID;
211 }
212 });
213 menu.append(item);
214
215 item = Gtk.MenuItem.new_with_label("Hide for some time");
216 item.connect('activate', () => {
217 indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE);
218 GLib.timeout_add(0, 5000, () => {
219 indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
220 return false;
221 });
222 });
223 menu.append(item);
224
225 item = Gtk.MenuItem.new_with_label("Close in 5 seconds");
226 item.connect('activate', () => {
227 GLib.timeout_add(0, 5000, () => {
228 app.quit();
229 return false;
230 });
231 });
232 menu.append(item);
233
234 menu.show_all();
235
236 var indicator = AppIndicator.Indicator.new("Hello", "indicator-test", AppIndicator.IndicatorCategory.APPLICATION_STATUS);
237
238 indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
239 indicator.set_icon(DEFAULT_ICON);
240 indicator.set_attention_icon(ATTENTION_ICON);
241 indicator.set_menu(menu);
242 indicator.set_secondary_activate_target(toggleBrandingItem);
243
244 indicator.connect("connection-changed", (indicator, connected) => {
245 print(`Signal "connection-changed" emitted. Connected: ${connected}`);
246 });
247 indicator.connect("new-attention-icon", (indicator) => {
248 print(`Signal "new-attention-icon" emitted.`);
249 });
250 indicator.connect("new-icon", (indicator) => {
251 let icon = "<none>";
252 if (indicator.get_status() == AppIndicator.IndicatorStatus.ATTENTION)
253 icon = indicator.get_attention_icon();
254 else if (indicator.get_status() == AppIndicator.IndicatorStatus.ACTIVE)
255 icon = indicator.get_icon();
256
257 print(`Signal "new-icon" emitted. Icon: ${icon}`);
258 });
259 indicator.connect("new-icon-theme-path", (indicator, path) => {
260 print(`Signal "new-icon-theme-path" emitted. Path: ${path}`);
261 });
262 indicator.connect("new-label", (indicator, label, guide) => {
263 print(`Signal "new-label" emitted. Label: ${label}, Guide: ${guide}`);
264 });
265 indicator.connect("new-status", (indicator, status) => {
266 print(`Signal "new-status" emitted. Status: ${status}`);
267
268 toggleAttentionItem.block_signal_handler(toggleAttentionId);
269 toggleAttentionItem.set_active(status == 'NeedsAttention');
270 toggleAttentionItem.unblock_signal_handler(toggleAttentionId);
271 });
272 indicator.connect("scroll-event", (indicator, steps, direction) => {
273 print(`Signal "scroll-event" emitted. Steps: ${steps}, Direction: ${direction}`);
274 let currentIndex = iconsPool.indexOf(indicator.get_icon());
275 let iconIndex;
276
277 if (direction == ScrollType.UP) {
278 iconIndex = (currentIndex + 1) % iconsPool.length;
279 } else {
280 iconIndex = (currentIndex <= 0 ? iconsPool.length : currentIndex) - 1;
281 }
282
283 indicator.set_icon(iconsPool[iconIndex]);
284 });
285 });
286 app.run(ARGV);
286 app.run(ARGV);
287287
288288 })();
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 IndicatorStatusIcon */
17
1518 const Clutter = imports.gi.Clutter;
1619 const GObject = imports.gi.GObject;
1720 const St = imports.gi.St;
1922 const Main = imports.ui.main;
2023 const Panel = imports.ui.panel;
2124 const PanelMenu = imports.ui.panelMenu;
22 const PopupMenu = imports.ui.popupMenu;
2325
2426 const Config = imports.misc.config;
2527 const ExtensionUtils = imports.misc.extensionUtils;
2628 const Extension = ExtensionUtils.getCurrentExtension();
2729
28 const AppIndicator = Extension.imports.appIndicator
30 const AppIndicator = Extension.imports.appIndicator;
2931 const DBusMenu = Extension.imports.dbusMenu;
3032 const Util = Extension.imports.util;
3133
3335 * IndicatorStatusIcon implements an icon in the system status area
3436 */
3537 var IndicatorStatusIcon = GObject.registerClass(
36 class AppIndicators_IndicatorStatusIcon extends PanelMenu.Button {
38 class AppIndicatorsIndicatorStatusIcon extends PanelMenu.Button {
3739 _init(indicator) {
3840 super._init(0.5, indicator.uniqueId);
3941 this._indicator = indicator;
4547
4648 this._box.add_child(this._iconBox);
4749
48 Util.connectSmart(this._indicator, 'ready', this, '_display')
49 Util.connectSmart(this._indicator, 'menu', this, '_updateMenu')
50 Util.connectSmart(this._indicator, 'label', this, '_updateLabel')
51 Util.connectSmart(this._indicator, 'status', this, '_updateStatus')
50 Util.connectSmart(this._indicator, 'ready', this, '_display');
51 Util.connectSmart(this._indicator, 'menu', this, '_updateMenu');
52 Util.connectSmart(this._indicator, 'label', this, '_updateLabel');
53 Util.connectSmart(this._indicator, 'status', this, '_updateStatus');
5254 Util.connectSmart(this._indicator, 'reset', this, () => {
5355 this._updateStatus();
5456 this._updateLabel();
5961 this._menuClient.destroy();
6062 this._menuClient = null;
6163 }
62 })
64 });
6365
6466 if (this._indicator.isReady)
65 this._display()
67 this._display();
6668 }
6769
6870 _updateLabel() {
7072 if (label) {
7173 if (!this._label || !this._labelBin) {
7274 this._labelBin = new St.Bin({
73 y_align: ExtensionUtils.versionCheck(['3.34'], Config.PACKAGE_VERSION) ?
74 St.Align.MIDDLE : Clutter.ActorAlign.CENTER,
75 y_align: ExtensionUtils.versionCheck(['3.34'], Config.PACKAGE_VERSION)
76 ? St.Align.MIDDLE : Clutter.ActorAlign.CENTER,
7577 });
7678 this._label = new St.Label();
7779 this._labelBin.add_actor(this._label);
7880 this._box.add_actor(this._labelBin);
7981 }
8082 this._label.set_text(label);
81 if (!this._box.contains(this._labelBin)) this._box.add_actor(this._labelBin); //FIXME: why is it suddenly necessary?
82 } else {
83 if (this._label) {
84 this._labelBin.destroy_all_children();
85 this._box.remove_actor(this._labelBin);
86 this._labelBin.destroy();
87 delete this._labelBin;
88 delete this._label;
89 }
83 if (!this._box.contains(this._labelBin))
84 this._box.add_actor(this._labelBin); // FIXME: why is it suddenly necessary?
85 } else if (this._label) {
86 this._labelBin.destroy_all_children();
87 this._box.remove_actor(this._labelBin);
88 this._labelBin.destroy();
89 delete this._labelBin;
90 delete this._label;
9091 }
9192 }
9293
9394 _updateStatus() {
94 this.visible = this._indicator.status != AppIndicator.SNIStatus.PASSIVE;
95 this.visible = this._indicator.status !== AppIndicator.SNIStatus.PASSIVE;
9596 }
9697
9798 _updateMenu() {
103104
104105 if (this._indicator.menuPath) {
105106 this._menuClient = new DBusMenu.Client(this._indicator.busName,
106 this._indicator.menuPath);
107 this._indicator.menuPath);
107108 this._menuClient.attachToMenu(this.menu);
108109 }
109110 }
113114 this._updateStatus();
114115 this._updateMenu();
115116
116 Main.panel.addToStatusArea("appindicator-"+this._indicator.uniqueId, this, 1, 'right')
117 Main.panel.addToStatusArea(`appindicator-${this._indicator.uniqueId}`, this, 1, 'right');
117118 }
118119
119120 vfunc_button_press_event(buttonEvent) {
139140 // event, and we can choose which one we interpret.
140141 if (scrollEvent.direction === Clutter.ScrollDirection.SMOOTH) {
141142 const event = Clutter.get_current_event();
142 let [dx, dy] = event.get_scroll_delta()
143 let [dx, dy] = event.get_scroll_delta();
143144
144 this._indicator.scroll(dx, dy)
145 this._indicator.scroll(dx, dy);
145146 return Clutter.EVENT_STOP;
146147 }
147148
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 var StatusNotifierItem = loadInterfaceXml("StatusNotifierItem.xml")
17 const Properties = loadInterfaceXml("Properties.xml")
18 var StatusNotifierWatcher = loadInterfaceXml("StatusNotifierWatcher.xml")
19 var DBusMenu = loadInterfaceXml("DBusMenu.xml")
16 var StatusNotifierItem = loadInterfaceXml('StatusNotifierItem.xml');
17 const Properties = loadInterfaceXml('Properties.xml');
18 var StatusNotifierWatcher = loadInterfaceXml('StatusNotifierWatcher.xml');
19 var DBusMenu = loadInterfaceXml('DBusMenu.xml');
2020
2121 // loads a xml file into an in-memory string
2222 function loadInterfaceXml(filename) {
23 let extension = imports.misc.extensionUtils.getCurrentExtension()
23 let extension = imports.misc.extensionUtils.getCurrentExtension();
2424
25 let interfaces_dir = extension.dir.get_child("interfaces-xml")
25 let interfaces_dir = extension.dir.get_child('interfaces-xml');
2626
27 let file = interfaces_dir.get_child(filename)
27 let file = interfaces_dir.get_child(filename);
2828
29 let [ result, contents ] = imports.gi.GLib.file_get_contents(file.get_path())
29 let [result, contents] = imports.gi.GLib.file_get_contents(file.get_path());
3030
3131 if (result) {
32 //HACK: The "" + trick is important as hell because file_get_contents returns
32 // HACK: The "" + trick is important as hell because file_get_contents returns
3333 // an object (WTF?) but Gio.makeProxyWrapper requires `typeof() == "string"`
3434 // Otherwise, it will try to check `instanceof XML` and fail miserably because there
3535 // is no `XML` on very recent SpiderMonkey releases (or, if SpiderMonkey is old enough,
3636 // will spit out a TypeError soon).
3737 if (contents instanceof Uint8Array)
38 contents = imports.byteArray.toString(contents);
39 return "<node>" + contents + "</node>"
38 contents = imports.byteArray.toString(contents);
39 return `<node>${contents}</node>`;
4040 } else {
41 throw new Error("AppIndicatorSupport: Could not load file: "+filename)
41 throw new Error(`AppIndicatorSupport: Could not load file: ${filename}`);
4242 }
4343 }
0 ---
1 env:
2 es6: true
3 extends: 'eslint:recommended'
4 rules:
5 array-bracket-newline:
6 - error
7 - consistent
8 array-bracket-spacing:
9 - error
10 - never
11 array-callback-return: error
12 arrow-parens:
13 - error
14 - as-needed
15 arrow-spacing: error
16 block-scoped-var: error
17 block-spacing: error
18 brace-style: error
19 # Waiting for this to have matured a bit in eslint
20 # camelcase:
21 # - error
22 # - properties: never
23 # allow: [^vfunc_, ^on_, _instance_init]
24 comma-dangle:
25 - error
26 - always-multiline
27 comma-spacing:
28 - error
29 - before: false
30 after: true
31 comma-style:
32 - error
33 - last
34 computed-property-spacing: error
35 curly:
36 - error
37 - multi-or-nest
38 - consistent
39 dot-location:
40 - error
41 - property
42 eol-last: error
43 eqeqeq: error
44 func-call-spacing: error
45 func-name-matching: error
46 func-style:
47 - error
48 - declaration
49 - allowArrowFunctions: true
50 indent:
51 - error
52 - 4
53 - ignoredNodes:
54 # Allow not indenting the body of GObject.registerClass, since in the
55 # future it's intended to be a decorator
56 - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child'
57 # Allow dedenting chained member expressions
58 MemberExpression: 'off'
59 key-spacing:
60 - error
61 - beforeColon: false
62 afterColon: true
63 keyword-spacing:
64 - error
65 - before: true
66 after: true
67 linebreak-style:
68 - error
69 - unix
70 lines-between-class-members: error
71 max-nested-callbacks: error
72 max-statements-per-line: error
73 new-parens: error
74 no-array-constructor: error
75 no-await-in-loop: error
76 no-caller: error
77 no-constant-condition:
78 - error
79 - checkLoops: false
80 no-div-regex: error
81 no-empty:
82 - error
83 - allowEmptyCatch: true
84 no-extra-bind: error
85 no-extra-parens:
86 - error
87 - all
88 - conditionalAssign: false
89 nestedBinaryExpressions: false
90 returnAssign: false
91 no-implicit-coercion:
92 - error
93 - allow:
94 - '!!'
95 no-invalid-this: error
96 no-iterator: error
97 no-label-var: error
98 no-lonely-if: error
99 no-loop-func: error
100 no-nested-ternary: error
101 no-new-object: error
102 no-new-wrappers: error
103 no-octal-escape: error
104 no-proto: error
105 no-prototype-builtins: 'off'
106 no-restricted-properties:
107 - error
108 - object: Lang
109 property: bind
110 message: Use arrow notation or Function.prototype.bind()
111 - object: Lang
112 property: Class
113 message: Use ES6 classes
114 - object: imports
115 property: mainloop
116 message: Use GLib main loops and timeouts
117 no-restricted-syntax:
118 - error
119 - selector: >-
120 MethodDefinition[key.name="_init"] >
121 FunctionExpression[params.length=1] >
122 BlockStatement[body.length=1]
123 CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] >
124 Identifier:first-child
125 message: _init() that only calls super._init() is unnecessary
126 - selector: >-
127 MethodDefinition[key.name="_init"] >
128 FunctionExpression[params.length=0] >
129 BlockStatement[body.length=1]
130 CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"]
131 message: _init() that only calls super._init() is unnecessary
132 no-return-assign: error
133 no-return-await: error
134 no-self-compare: error
135 no-shadow: error
136 no-shadow-restricted-names: error
137 no-spaced-func: error
138 no-tabs: error
139 no-template-curly-in-string: error
140 no-throw-literal: error
141 no-trailing-spaces: error
142 no-undef-init: error
143 no-unneeded-ternary: error
144 no-unused-expressions: error
145 no-unused-vars:
146 - error
147 # Vars use a suffix _ instead of a prefix because of file-scope private vars
148 - varsIgnorePattern: (^unused|_$)
149 argsIgnorePattern: ^(unused|_)
150 no-useless-call: error
151 no-useless-computed-key: error
152 no-useless-concat: error
153 no-useless-constructor: error
154 no-useless-rename: error
155 no-useless-return: error
156 no-whitespace-before-property: error
157 no-with: error
158 nonblock-statement-body-position:
159 - error
160 - below
161 object-curly-newline:
162 - error
163 - consistent: true
164 object-curly-spacing: error
165 object-shorthand: error
166 operator-assignment: error
167 operator-linebreak: error
168 # These may be a bit controversial, we can try them out and enable them later
169 # prefer-const: error
170 # prefer-destructuring: error
171 prefer-numeric-literals: error
172 prefer-promise-reject-errors: error
173 prefer-rest-params: error
174 prefer-spread: error
175 prefer-template: error
176 quotes:
177 - error
178 - single
179 - avoidEscape: true
180 require-await: error
181 rest-spread-spacing: error
182 semi:
183 - error
184 - always
185 semi-spacing:
186 - error
187 - before: false
188 after: true
189 semi-style: error
190 space-before-blocks: error
191 space-before-function-paren:
192 - error
193 - named: never
194 # for `function ()` and `async () =>`, preserve space around keywords
195 anonymous: always
196 asyncArrow: always
197 space-in-parens: error
198 space-infix-ops:
199 - error
200 - int32Hint: false
201 space-unary-ops: error
202 spaced-comment: error
203 switch-colon-spacing: error
204 symbol-description: error
205 template-curly-spacing: error
206 template-tag-spacing: error
207 unicode-bom: error
208 valid-jsdoc:
209 - error
210 - requireReturn: false
211 wrap-iife:
212 - error
213 - inside
214 yield-star-spacing: error
215 yoda: error
216 globals:
217 ARGV: readonly
218 Debugger: readonly
219 GIRepositoryGType: readonly
220 globalThis: readonly
221 imports: readonly
222 Intl: readonly
223 log: readonly
224 logError: readonly
225 print: readonly
226 printerr: readonly
227 parserOptions:
228 ecmaVersion: 2020
0 rules:
1 eqeqeq: off
2 indent:
3 - error
4 - 4
5 - ignoredNodes:
6 - 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child'
7 CallExpression:
8 arguments: first
9 ArrayExpression: first
10 ObjectExpression: first
11 MemberExpression: off
12 prefer-template: off
13 quotes: off
0 rules:
1 camelcase:
2 - error
3 - properties: never
4 allow: [^vfunc_, ^on_]
5 consistent-return: error
6 key-spacing:
7 - error
8 - mode: minimum
9 beforeColon: false
10 afterColon: true
11 object-curly-spacing:
12 - error
13 - always
14 prefer-arrow-callback: error
15
16 overrides:
17 - files: js/**
18 excludedFiles:
19 - js/portalHelper/*
20 globals:
21 global: readonly
22 _: readonly
23 C_: readonly
24 N_: readonly
25 ngettext: readonly
26 - files: subprojects/extensions-app/js/**
27 globals:
28 _: readonly
29 C_: readonly
30 N_: readonly
297297 var _promisify = Gio._promisify;
298298 if (imports.system.version < 16501) {
299299 /* This is backported from upstream gjs, so that all the features are available */
300 _promisify = function(proto, asyncFunc, finishFunc) {
300 _promisify = function (proto, asyncFunc, finishFunc) {
301301 if (proto[`_original_${asyncFunc}`] !== undefined)
302302 return;
303303 proto[`_original_${asyncFunc}`] = proto[asyncFunc];
306306 return this[`_original_${asyncFunc}`](...args);
307307 return new Promise((resolve, reject) => {
308308 const callStack = new Error().stack.split('\n').filter(line => !line.match(/promisify/)).join('\n');
309 this[`_original_${asyncFunc}`](...args, function (source, res) {
309 this[`_original_${asyncFunc}`](...args, (source, res) => {
310310 try {
311311 const result = source !== null && source[finishFunc] !== undefined
312312 ? source[finishFunc](res)
324324 });
325325 });
326326 };
327 }
327 };
328328 }
329329
330330 if (!Promise.allSettled) {
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 Gio = imports.gi.Gio
17 const GLib = imports.gi.GLib
18
19 const Extension = imports.misc.extensionUtils.getCurrentExtension()
20
21 const AppIndicator = Extension.imports.appIndicator
22 const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon
23 const Interfaces = Extension.imports.interfaces
16 const Gio = imports.gi.Gio;
17 const GLib = imports.gi.GLib;
18
19 const Extension = imports.misc.extensionUtils.getCurrentExtension();
20
21 const AppIndicator = Extension.imports.appIndicator;
22 const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon;
23 const Interfaces = Extension.imports.interfaces;
2424 const PromiseUtils = Extension.imports.promiseUtils;
25 const Util = Extension.imports.util
25 const Util = Extension.imports.util;
2626
2727
2828 // TODO: replace with org.freedesktop and /org/freedesktop when approved
2929 const KDE_PREFIX = 'org.kde';
3030
31 var WATCHER_BUS_NAME = KDE_PREFIX + '.StatusNotifierWatcher';
31 var WATCHER_BUS_NAME = `${KDE_PREFIX}.StatusNotifierWatcher`;
3232 const WATCHER_OBJECT = '/StatusNotifierWatcher';
3333
3434 const DEFAULT_ITEM_OBJECT_PATH = '/StatusNotifierItem';
4242 this._watchDog = watchDog;
4343 this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this);
4444 this._dbusImpl.export(Gio.DBus.session, WATCHER_OBJECT);
45 this._cancellable = new Gio.Cancellable;
45 this._cancellable = new Gio.Cancellable();
4646 this._everAcquiredName = false;
4747 this._ownName = Gio.DBus.session.own_name(WATCHER_BUS_NAME,
48 Gio.BusNameOwnerFlags.NONE,
49 this._acquiredName.bind(this),
50 this._lostName.bind(this));
48 Gio.BusNameOwnerFlags.NONE,
49 this._acquiredName.bind(this),
50 this._lostName.bind(this));
5151 this._items = new Map();
5252
5353 this._seekStatusNotifierItems();
5959
6060 _lostName() {
6161 if (this._everAcquiredName)
62 Util.Logger.debug('Lost name' + WATCHER_BUS_NAME);
62 Util.Logger.debug(`Lost name${WATCHER_BUS_NAME}`);
6363 else
64 Util.Logger.warn('Failed to acquire ' + WATCHER_BUS_NAME);
64 Util.Logger.warn(`Failed to acquire ${WATCHER_BUS_NAME}`);
6565 this._watchDog.nameAcquired = false;
6666 }
6767
9191 GLib.PRIORITY_DEFAULT, this._cancellable);
9292 if (!indicator.hasNameOwner)
9393 this._itemVanished(id);
94 };
94 }
9595 });
9696
9797 // if the desktop is not ready delay the icon creation and signal emissions
115115 let item = this._items.get(id);
116116
117117 if (item) {
118 //delete the old one and add the new indicator
118 // delete the old one and add the new indicator
119119 Util.Logger.debug(`Attempting to re-register ${id}; resetting instead`);
120120 item.reset();
121121 return;
122122 }
123123
124 this._registerItem(service, bus_name, obj_path)
124 this._registerItem(service, bus_name, obj_path);
125125 }
126126
127127 async _seekStatusNotifierItems() {
170170 }
171171
172172 if (!bus_name || !obj_path) {
173 let error = "Impossible to register an indicator for parameters '"+
174 service.toString()+"'";
173 let error = `Impossible to register an indicator for parameters '${
174 service.toString()}'`;
175175 Util.Logger.warn(error);
176176
177177 invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
178 error);
178 error);
179179 return;
180180 }
181181
186186
187187 _itemVanished(id) {
188188 // FIXME: this is useless if the path name disappears while the bus stays alive (not unheard of)
189 if (this._items.has(id)) {
189 if (this._items.has(id))
190190 this._remove(id);
191 }
191
192192 }
193193
194194 _remove(id) {
1717 introspectBusObject, dbusNodeImplementsInterfaces, waitForStartupCompletion,
1818 BUS_ADDRESS_REGEX */
1919
20 const Gio = imports.gi.Gio
21 const GLib = imports.gi.GLib
20 const Gio = imports.gi.Gio;
21 const GLib = imports.gi.GLib;
2222 const Gtk = imports.gi.Gtk;
2323 const Gdk = imports.gi.Gdk;
2424 const Main = imports.ui.main;
25 const GObject = imports.gi.GObject
25 const GObject = imports.gi.GObject;
2626 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2727 const Params = imports.misc.params;
2828 const PromiseUtils = Extension.imports.promiseUtils;
2929
30 const Signals = imports.signals
31
32 var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/
30 const Signals = imports.signals;
31
32 var BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/;
3333
3434 PromiseUtils._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish');
3535
4343
4444 let cancellable = cancelRefreshPropertyOnProxy(proxy, {
4545 propertyName,
46 addNew: true
46 addNew: true,
4747 });
4848
4949 try {
5050 const [valueVariant] = (await proxy.g_connection.call(proxy.g_name,
5151 proxy.g_object_path, 'org.freedesktop.DBus.Properties', 'Get',
52 GLib.Variant.new('(ss)', [ proxy.g_interface_name, propertyName ]),
52 GLib.Variant.new('(ss)', [proxy.g_interface_name, propertyName]),
5353 GLib.VariantType.new('(v)'), Gio.DBusCallFlags.NONE, -1,
5454 cancellable)).deep_unpack();
5555
5959 proxy.get_cached_property(propertyName).equal(valueVariant))
6060 return;
6161
62 proxy.set_cached_property(propertyName, valueVariant)
62 proxy.set_cached_property(propertyName, valueVariant);
6363
6464 // synthesize a batched property changed event
6565 if (!proxy._proxyChangedProperties)
8484 }
8585 }
8686
87 var cancelRefreshPropertyOnProxy = function(proxy, params) {
87 var cancelRefreshPropertyOnProxy = function (proxy, params) {
8888 if (!proxy._proxyCancellables)
8989 return;
9090
112112 delete proxy._proxyChangedProperties;
113113 delete proxy._proxyCancellables;
114114 }
115 }
115 };
116116
117117 async function getUniqueBusName(bus, name, cancellable) {
118118 if (name[0] == ':')
154154
155155 async function introspectBusObject(bus, name, cancellable, path = undefined) {
156156 if (!path)
157 path = "/";
157 path = '/';
158158
159159 const [introspection] = (await bus.call(name, path, 'org.freedesktop.DBus.Introspectable',
160160 'Introspect', null, new GLib.VariantType('(s)'), Gio.DBusCallFlags.NONE,
182182 return nodes;
183183 }
184184
185 var dbusNodeImplementsInterfaces = function(node_info, interfaces) {
185 var dbusNodeImplementsInterfaces = function (node_info, interfaces) {
186186 if (!(node_info instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces))
187187 return false;
188188
192192 }
193193
194194 return false;
195 }
195 };
196196
197197 var NameWatcher = class AppIndicatorsNameWatcher {
198198 constructor(name) {
199199 this._watcherId = Gio.DBus.session.watch_name(name,
200200 Gio.BusNameWatcherFlags.NONE, () => {
201 this._nameOnBus = true
201 this._nameOnBus = true;
202202 Logger.debug(`Name ${name} appeared`);
203203 this.emit('changed');
204204 this.emit('appeared');
223223 };
224224 Signals.addSignalMethods(NameWatcher.prototype);
225225
226 const connectSmart3A = function(src, signal, handler) {
227 let id = src.connect(signal, handler)
226 const connectSmart3A = function (src, signal, handler) {
227 let id = src.connect(signal, handler);
228228
229229 if (src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src))) {
230230 let destroy_id = src.connect('destroy', () => {
231 src.disconnect(id)
232 src.disconnect(destroy_id)
233 })
234 }
235 }
236
237 const connectSmart4A = function(src, signal, target, method) {
231 src.disconnect(id);
232 src.disconnect(destroy_id);
233 });
234 }
235 };
236
237 const connectSmart4A = function (src, signal, target, method) {
238238 if (typeof method === 'string')
239 method = target[method].bind(target)
239 method = target[method].bind(target);
240240 if (typeof method === 'function')
241 method = method.bind(target)
242
243 let signal_id = src.connect(signal, method)
241 method = method.bind(target);
242
243 let signal_id = src.connect(signal, method);
244244
245245 // GObject classes might or might not have a destroy signal
246246 // 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
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;
249249
250250 function on_destroy() {
251 src.disconnect(signal_id)
252 if (src_destroy_id) src.disconnect(src_destroy_id)
253 if (tgt_destroy_id) target.disconnect(tgt_destroy_id)
254 }
255 }
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 };
256258
257259 /**
258260 * Connect signals to slots, and remove the connection when either source or
263265 * or
264266 * Util.connectSmart(srcOb, 'signal', () => { ... })
265267 */
266 var connectSmart = function() {
268 var connectSmart = function () {
267269 if (arguments.length == 4)
268 return connectSmart4A.apply(null, arguments)
270 return connectSmart4A.apply(null, arguments);
269271 else
270 return connectSmart3A.apply(null, arguments)
271 }
272 return connectSmart3A.apply(null, arguments);
273 };
272274
273275 // eslint-disable-next-line valid-jsdoc
274276 /**