Codebase list gnome-shell-extension-appindicator / f79aab3
Merge 'master' into ubuntu Marco Trevisan (TreviƱo) 5 years ago
10 changed file(s) with 459 addition(s) and 433 deletion(s). Raw diff Collapse all Expand all
1818 const GdkPixbuf = imports.gi.GdkPixbuf
1919 const Gio = imports.gi.Gio
2020 const GLib = imports.gi.GLib
21 const GObject = imports.gi.GObject
2122 const Gtk = imports.gi.Gtk
2223 const St = imports.gi.St
2324 const Shell = imports.gi.Shell
2425
2526 const Extension = imports.misc.extensionUtils.getCurrentExtension();
26 const Lang = imports.lang
2727 const Signals = imports.signals
2828
2929 const DBusMenu = Extension.imports.dbusMenu;
4848 * the AppIndicator class serves as a generic container for indicator information and functions common
4949 * for every displaying implementation (IndicatorMessageSource and IndicatorStatusIcon)
5050 */
51 var AppIndicator = new Lang.Class({
52 Name: 'AppIndicator',
53
54 _init: function(bus_name, object) {
51 var AppIndicator = class AppIndicators_AppIndicator {
52
53 constructor(bus_name, object) {
5554 this.busName = bus_name
5655 this._uniqueId = bus_name + object
5756
6564 g_name: bus_name,
6665 g_object_path: object,
6766 g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES })
68 this._proxy.init_async(GLib.PRIORITY_DEFAULT, null, (function(initable, result) {
67 this._proxy.init_async(GLib.PRIORITY_DEFAULT, null, ((initable, result) => {
6968 try {
7069 initable.init_finish(result);
7170
7473 } catch(e) {
7574 Util.Logger.warn("While intializing proxy for "+bus_name+object+": "+e)
7675 }
77 }).bind(this))
78
79 this._proxyPropertyList = interface_info.properties.map(function(propinfo) { return propinfo.name })
80
76 }))
77
78 this._proxyPropertyList = interface_info.properties.map((propinfo) => { return propinfo.name })
79 this._addExtraProperty('XAyatanaLabel');
80 this._addExtraProperty('XAyatanaLabelGuide');
81 this._addExtraProperty('XAyatanaOrderingIndex');
8182
8283 Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged')
8384 Util.connectSmart(this._proxy, 'g-signal', this, '_translateNewSignals')
84 },
85 }
86
87 _addExtraProperty(name) {
88 let propertyProps = { configurable: false, enumerable: true };
89
90 propertyProps.get = () => {
91 let v = this._proxy.get_cached_property(name);
92 return v ? v.deep_unpack() : null
93 };
94
95 Object.defineProperty(this._proxy, name, propertyProps);
96 this._proxyPropertyList.push(name);
97 }
8598
8699 // The Author of the spec didn't like the PropertiesChanged signal, so he invented his own
87 _translateNewSignals: function(proxy, sender, signal, params) {
88 if (signal.substr(0, 3) == 'New') {
89 let prop = signal.substr(3)
90
100 _translateNewSignals(proxy, sender, signal, params) {
101 let prop = null;
102
103 if (signal.substr(0, 3) == 'New')
104 prop = signal.substr(3)
105 else if (signal.substr(0, 11) == 'XAyatanaNew')
106 prop = 'XAyatana' + signal.substr(11)
107
108 if (prop) {
91109 if (this._proxyPropertyList.indexOf(prop) > -1)
92110 Util.refreshPropertyOnProxy(this._proxy, prop)
93111
96114
97115 if (this._proxyPropertyList.indexOf(prop + 'Name') > -1)
98116 Util.refreshPropertyOnProxy(this._proxy, prop + 'Name')
99 } else if (signal == 'XAyatanaNewLabel') {
100 // and the ayatana guys made sure to invent yet another way of composing these signals...
101 Util.refreshPropertyOnProxy(this._proxy, 'XAyatanaLabel')
102 }
103 },
117 }
118 }
104119
105120 //public property getters
106121 get title() {
107122 return this._proxy.Title;
108 },
123 }
109124 get id() {
110125 return this._proxy.Id;
111 },
126 }
112127 get uniqueId() {
113128 return this._uniqueId;
114 },
129 }
115130 get status() {
116131 return this._proxy.Status;
117 },
132 }
118133 get label() {
119 let v = this._proxy.get_cached_property('XAyatanaLabel');
120
121 if (v) return v.deep_unpack()
122 else return null
123 },
134 return this._proxy.XAyatanaLabel;
135 }
124136 get menuPath() {
125137 return this._proxy.Menu || "/MenuBar"
126 },
138 }
127139
128140 get attentionIcon() {
129141 return [
131143 this._proxy.AttentionIconPixmap,
132144 this._proxy.IconThemePath
133145 ]
134 },
146 }
135147
136148 get icon() {
137149 return [
139151 this._proxy.IconPixmap,
140152 this._proxy.IconThemePath
141153 ]
142 },
154 }
143155
144156 get overlayIcon() {
145157 return [
147159 this._proxy.OverlayIconPixmap,
148160 this._proxy.IconThemePath
149161 ]
150 },
151
152 _onPropertiesChanged: function(proxy, changed, invalidated) {
162 }
163
164 _onPropertiesChanged(proxy, changed, invalidated) {
153165 let props = Object.keys(changed.deep_unpack())
154166
155 props.forEach(function(property) {
167 props.forEach((property) => {
156168 // some property changes require updates on our part,
157169 // a few need to be passed down to the displaying code
158170
178190 if (property == 'Status')
179191 this.emit('status')
180192 }, this);
181 },
182
183 reset: function() {
193 }
194
195 reset() {
184196 //TODO: reload all properties, or do some other useful things
185197 this.emit('reset')
186 },
187
188 destroy: function() {
198 }
199
200 destroy() {
189201 this.emit('destroy')
190202
191203 this.disconnectAll()
192204 delete this._proxy
193 },
194
195 open: function() {
205 }
206
207 open() {
196208 // we can't use WindowID because we're not able to get the x11 window id from a MetaWindow
197209 // nor can we call any X11 functions. Luckily, the Activate method usually works fine.
198210 // parameters are "an hint to the item where to show eventual windows" [sic]
199211 // ... and don't seem to have any effect.
200212 this._proxy.ActivateRemote(0, 0)
201 },
202
203 secondaryActivate: function () {
213 }
214
215 secondaryActivate() {
204216 this._proxy.SecondaryActivateRemote(0, 0)
205 },
206
207 scroll: function(dx, dy) {
217 }
218
219 scroll(dx, dy) {
208220 if (dx != 0)
209221 this._proxy.ScrollRemote(Math.floor(dx), 'horizontal')
210222
211223 if (dy != 0)
212224 this._proxy.ScrollRemote(Math.floor(dy), 'vertical')
213225 }
214 });
226 };
215227 Signals.addSignalMethods(AppIndicator.prototype);
216228
217 var IconActor = new Lang.Class({
218 Name: 'AppIndicatorIconActor',
219 Extends: Shell.Stack,
220 GTypeName: Util.WORKAROUND_RELOAD_TYPE_REGISTER('AppIndicatorIconActor'),
221
222 _init: function(indicator, icon_size) {
223 this.parent({ reactive: true })
229 var IconActor = GObject.registerClass(
230 class AppIndicators_IconActor extends Shell.Stack {
231
232 _init(indicator, icon_size) {
233 super._init({ reactive: true })
234 this.name = this.constructor.name;
235
224236 let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
225237 this.width = icon_size * scale_factor
226238 this.height = icon_size * scale_factor
245257
246258 if (indicator.isReady)
247259 this._invalidateIcon()
248 },
260
261 this.connect('destroy', () => {
262 this._iconCache.destroy();
263 });
264 }
249265
250266 // Will look the icon up in the cache, if it's found
251267 // it will return it. Otherwise, it will create it and cache it.
253269 // the returned icon anymore, make sure to check the .inUse property
254270 // and set it to false if needed so that it can be picked up by the garbage
255271 // collector.
256 _cacheOrCreateIconByName: function(iconSize, iconName, themePath) {
272 _cacheOrCreateIconByName(iconSize, iconName, themePath) {
257273 let id = iconName + '@' + iconSize + (themePath ? '##' + themePath : '')
258274
259275 let icon = this._iconCache.get(id) || this._createIconByName(iconSize, iconName, themePath)
264280 }
265281
266282 return icon
267 },
268
269 _createIconByName: function(icon_size, icon_name, themePath) {
283 }
284
285 _createIconByName(icon_size, icon_name, themePath) {
270286 // real_icon_size will contain the actual icon size in contrast to the requested icon size
271287 var real_icon_size = icon_size
272288 var gicon = null
294310 // we try to avoid messing with the default icon theme, so we'll create a new one if needed
295311 if (themePath) {
296312 var icon_theme = new Gtk.IconTheme()
297 Gtk.IconTheme.get_default().get_search_path().forEach(function(path) {
313 Gtk.IconTheme.get_default().get_search_path().forEach((path) => {
298314 icon_theme.append_search_path(path)
299315 });
300316 icon_theme.append_search_path(themePath)
331347 return new St.Icon({ gicon: gicon, icon_size: real_icon_size })
332348 else
333349 return null
334 },
335
336 _createIconFromPixmap: function(iconSize, iconPixmapArray) {
350 }
351
352 _createIconFromPixmap(iconSize, iconPixmapArray) {
337353 let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
338354 iconSize = iconSize * scale_factor
339355 // the pixmap actually is an array of pixmaps with different sizes
343359 if (!iconPixmapArray || iconPixmapArray.length < 1)
344360 return null
345361
346 let sortedIconPixmapArray = iconPixmapArray.sort(function(pixmapA, pixmapB) {
362 let sortedIconPixmapArray = iconPixmapArray.sort((pixmapA, pixmapB) => {
347363 // we sort biggest to smallest
348364 let areaA = pixmapA[0] * pixmapA[1]
349365 let areaB = pixmapB[0] * pixmapB[1]
351367 return areaB - areaA
352368 })
353369
354 let qualifiedIconPixmapArray = sortedIconPixmapArray.filter(function(pixmap) {
370 let qualifiedIconPixmapArray = sortedIconPixmapArray.filter((pixmap) => {
355371 // we disqualify any pixmap that is bigger than our requested size
356372 return pixmap[0] <= iconSize && pixmap[1] <= iconSize
357373 })
389405 // we could log it here, but that doesn't really help in tracking it down.
390406 return null
391407 }
392 },
408 }
393409
394410 // updates the base icon
395 _updateIcon: function() {
411 _updateIcon() {
396412 // remove old icon
397413 if (this._mainIcon.get_child()) {
398414 let child = this._mainIcon.get_child()
430446 }
431447
432448 this._mainIcon.set_child(newIcon)
433 },
434
435 _updateOverlayIcon: function() {
449 }
450
451 _updateOverlayIcon() {
436452 // remove old icon
437453 if (this._overlayIcon.get_child()) {
438454 let child = this._overlayIcon.get_child()
462478 newIcon = this._createIconFromPixmap(iconSize, pixmap)
463479
464480 this._overlayIcon.set_child(newIcon)
465 },
466
467 _handleScrollEvent: function(actor, event) {
481 }
482
483 _handleScrollEvent(actor, event) {
468484 if (actor != this)
469485 return Clutter.EVENT_PROPAGATE
470486
485501 }
486502
487503 return Clutter.EVENT_STOP
488 },
504 }
489505
490506 // called when the icon theme changes
491 _invalidateIcon: function() {
507 _invalidateIcon() {
492508 this._iconCache.clear()
493509
494510 this._updateIcon()
495511 this._updateOverlayIcon()
496 },
497
498 destroy: function() {
499 this._iconCache.destroy()
500
501 this.parent()
502 }
503 })
512 }
513 });
1717 const Gio = imports.gi.Gio
1818 const GLib = imports.gi.GLib
1919 const GdkPixbuf = imports.gi.GdkPixbuf
20 const Lang = imports.lang
2120 const PopupMenu = imports.ui.popupMenu
2221 const Signals = imports.signals
2322 const St = imports.gi.St
3534 /**
3635 * Saves menu property values and handles type checking and defaults
3736 */
38 const PropertyStore = new Lang.Class({
39 Name: 'DbusMenuPropertyStore',
40
41 _init: function(initial_properties) {
37 var PropertyStore = class AppIndicators_PropertyStore {
38
39 constructor(initial_properties) {
4240 this._props = {}
4341
4442 if (initial_properties) {
4644 this.set(i, initial_properties[i])
4745 }
4846 }
49 },
50
51 set: function(name, value) {
47 }
48
49 set(name, value) {
5250 if (name in PropertyStore.MandatedTypes && value && !value.is_of_type(PropertyStore.MandatedTypes[name]))
5351 Util.Logger.warn("Cannot set property "+name+": type mismatch!")
5452 else if (value)
5553 this._props[name] = value
5654 else
5755 delete this._props[name]
58 },
59
60 get: function(name) {
56 }
57
58 get(name) {
6159 if (name in this._props)
6260 return this._props[name]
6361 else if (name in PropertyStore.DefaultValues)
6563 else
6664 return null
6765 }
68 })
66 };
6967
7068 // we list all the properties we know and use here, so we won' have to deal with unexpected type mismatches
7169 PropertyStore.MandatedTypes = {
9189 /**
9290 * Represents a single menu item
9391 */
94 const DbusMenuItem = new Lang.Class({
95 Name: 'DbusMenuItem',
92 var DbusMenuItem = class AppIndicators_DbusMenuItem {
9693
9794 // will steal the properties object
98 _init: function(client, id, properties, children_ids) {
95 constructor(client, id, properties, children_ids) {
9996 this._client = client
10097 this._id = id
10198 this._propStore = new PropertyStore(properties)
10299 this._children_ids = children_ids
103 },
104
105 property_get: function(prop_name) {
100 }
101
102 property_get(prop_name) {
106103 let prop = this.property_get_variant(prop_name)
107104 return prop ? prop.get_string()[0] : null
108 },
109
110 property_get_variant: function(prop_name) {
105 }
106
107 property_get_variant(prop_name) {
111108 return this._propStore.get(prop_name)
112 },
113
114 property_get_bool: function(prop_name) {
109 }
110
111 property_get_bool(prop_name) {
115112 let prop = this.property_get_variant(prop_name)
116113 return prop ? prop.get_boolean() : false
117 },
118
119 property_get_int: function(prop_name) {
114 }
115
116 property_get_int(prop_name) {
120117 let prop = this.property_get_variant(prop_name)
121118 return prop ? prop.get_int32() : 0
122 },
123
124 property_set: function(prop, value) {
119 }
120
121 property_set(prop, value) {
125122 this._propStore.set(prop, value)
126123
127124 this.emit('property-changed', prop, this.property_get_variant(prop))
128 },
129
130 get_children_ids: function() {
125 }
126
127 get_children_ids() {
131128 return this._children_ids.concat() // clone it!
132 },
133
134 add_child: function(pos, child_id) {
129 }
130
131 add_child(pos, child_id) {
135132 this._children_ids.splice(pos, 0, child_id)
136133 this.emit('child-added', this._client.get_item(child_id), pos)
137 },
138
139 remove_child: function(child_id) {
134 }
135
136 remove_child(child_id) {
140137 // find it
141138 let pos = -1
142139 for (let i = 0; i < this._children_ids.length; ++i) {
152149 this._children_ids.splice(pos, 1)
153150 this.emit('child-removed', this._client.get_item(child_id))
154151 }
155 },
156
157 move_child: function(child_id, newpos) {
152 }
153
154 move_child(child_id, newpos) {
158155 // find the old position
159156 let oldpos = -1
160157 for (let i = 0; i < this._children_ids.length; ++i) {
174171 this._children_ids.splice(newpos, 0, child_id)
175172 this.emit('child-moved', oldpos, newpos, this._client.get_item(child_id))
176173 }
177 },
178
179 get_children: function() {
180 return this._children_ids.map(function(el) {
174 }
175
176 get_children() {
177 return this._children_ids.map((el) => {
181178 return this._client.get_item(el)
182179 }, this)
183 },
184
185 handle_event: function(event, data, timestamp) {
180 }
181
182 handle_event(event, data, timestamp) {
186183 if (!data)
187184 data = GLib.Variant.new_int32(0)
188185
189186 this._client.send_event(this._id, event, data, timestamp)
190 },
191
192 get_id: function() {
187 }
188
189 get_id() {
193190 return this._id
194 },
195
196 send_about_to_show: function() {
191 }
192
193 send_about_to_show() {
197194 this._client.send_about_to_show(this._id)
198195 }
199 })
196 }
200197 Signals.addSignalMethods(DbusMenuItem.prototype)
201198
202199
205202 /**
206203 * The client does the heavy lifting of actually reading layouts and distributing events
207204 */
208 const DBusClient = new Lang.Class({
209 Name: 'DbusMenuBusClient',
210
211 _init: function(busName, busPath) {
205 var DBusClient = class AppIndicators_DBusClient {
206
207 constructor(busName, busPath) {
212208 this._proxy = new BusClientProxy(Gio.DBus.session, busName, busPath, this._clientReady.bind(this))
213209 this._items = { 0: new DbusMenuItem(this, 0, { 'children-display': GLib.Variant.new_string('submenu') }, []) }
214210
219215
220216 // property requests are queued
221217 this._propertiesRequestedFor = [ /* ids */ ]
222 },
223
224 get_root: function() {
218 }
219
220 get_root() {
225221 return this._items[0]
226 },
227
228 _requestLayoutUpdate: function() {
222 }
223
224 _requestLayoutUpdate() {
229225 if (this._flagLayoutUpdateInProgress)
230226 this._flagLayoutUpdateRequired = true
231227 else
232228 this._beginLayoutUpdate()
233 },
234
235 _requestProperties: function(id) {
229 }
230
231 _requestProperties(id) {
236232 // if we don't have any requests queued, we'll need to add one
237233 if (this._propertiesRequestedFor.length < 1)
238234 GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, this._beginRequestProperties.bind(this))
239235
240 if (this._propertiesRequestedFor.filter(function(e) { return e === id }).length == 0)
236 if (this._propertiesRequestedFor.filter((e) => { return e === id }).length == 0)
241237 this._propertiesRequestedFor.push(id)
242238
243 },
244
245 _beginRequestProperties: function() {
239 }
240
241 _beginRequestProperties() {
246242 this._proxy.GetGroupPropertiesRemote(this._propertiesRequestedFor, [], this._endRequestProperties.bind(this))
247243
248244 this._propertiesRequestedFor = []
249245
250246 return false
251 },
252
253 _endRequestProperties: function(result, error) {
247 }
248
249 _endRequestProperties(result, error) {
254250 if (error) {
255251 Util.Logger.warn("Could not retrieve properties: "+error)
256252 return
257253 }
258254
259255 // for some funny reason, the result array is hidden in an array
260 result[0].forEach(function([id, properties]) {
256 result[0].forEach(([id, properties]) => {
261257 if (!(id in this._items))
262258 return
263259
264260 for (let prop in properties)
265261 this._items[id].property_set(prop, properties[prop])
266262 }, this)
267 },
263 }
268264
269265 // Traverses the list of cached menu items and removes everyone that is not in the list
270266 // so we don't keep alive unused items
271 _gcItems: function() {
267 _gcItems() {
272268 let tag = new Date().getTime()
273269
274270 let toTraverse = [ 0 ]
281277 for (let i in this._items)
282278 if (this._items[i]._dbusClientGcTag != tag)
283279 delete this._items[i]
284 },
280 }
285281
286282 // the original implementation will only request partial layouts if somehow possible
287283 // we try to save us from multiple kinds of race conditions by always requesting a full layout
288 _beginLayoutUpdate: function() {
284 _beginLayoutUpdate() {
289285 // we only read the type property, because if the type changes after reading all properties,
290286 // the view would have to replace the item completely which we try to avoid
291287 this._proxy.GetLayoutRemote(0, -1, [ 'type', 'children-display' ], this._endLayoutUpdate.bind(this))
292288
293289 this._flagLayoutUpdateRequired = false
294290 this._flagLayoutUpdateInProgress = true
295 },
296
297 _endLayoutUpdate: function(result, error) {
291 }
292
293 _endLayoutUpdate(result, error) {
298294 if (error) {
299 Util.Logger.warn("While reading menu layout: "+error)
295 Util.Logger.warn("While reading menu layout on proxy '"+this._proxy.g_name_owner+": "+error)
300296 return
301297 }
302298
308304 this._beginLayoutUpdate()
309305 else
310306 this._flagLayoutUpdateInProgress = false
311 },
312
313 _doLayoutUpdate: function(item) {
307 }
308
309 _doLayoutUpdate(item) {
314310 let [ id, properties, children ] = item
315311
316 let children_unpacked = children.map(function(child) { return child.deep_unpack() })
317 let children_ids = children_unpacked.map(function(child) { return child[0] })
312 let children_unpacked = children.map((child) => { return child.deep_unpack() })
313 let children_ids = children_unpacked.map((child) => { return child[0] })
318314
319315 // make sure all our children exist
320316 children_unpacked.forEach(this._doLayoutUpdate, this)
348344 }
349345
350346 // remove any old children that weren't reused
351 old_children_ids.forEach(function(child_id) { this._items[id].remove_child(child_id) }, this)
347 old_children_ids.forEach((child_id) => { this._items[id].remove_child(child_id) }, this)
352348 } else {
353349 // we don't, so let's create us
354350 this._items[id] = new DbusMenuItem(this, id, properties, children_ids)
356352 }
357353
358354 return id
359 },
360
361 _clientReady: function(result, error) {
355 }
356
357 _clientReady(result, error) {
362358 if (error) {
363359 Util.Logger.warn("Could not initialize menu proxy: "+error)
364360 //FIXME: show message to the user?
369365 // listen for updated layouts and properties
370366 this._proxy.connectSignal("LayoutUpdated", this._onLayoutUpdated.bind(this))
371367 this._proxy.connectSignal("ItemsPropertiesUpdated", this._onPropertiesUpdated.bind(this))
372 },
373
374 get_item: function(id) {
368 }
369
370 get_item(id) {
375371 if (id in this._items)
376372 return this._items[id]
377373
378374 Util.Logger.warn("trying to retrieve item for non-existing id "+id+" !?")
379375 return null
380 },
376 }
381377
382378 // we don't need to cache and burst-send that since it will not happen that frequently
383 send_about_to_show: function(id) {
384 this._proxy.AboutToShowRemote(id, (function(result, error) {
379 send_about_to_show(id) {
380 this._proxy.AboutToShowRemote(id, ((result, error) => {
385381 if (error)
386382 Util.Logger.warn("while calling AboutToShow: "+error)
387383 else if (result && result[0])
388384 this._requestLayoutUpdate()
389 }).bind(this))
390 },
391
392 send_event: function(id, event, params, timestamp) {
385 }))
386 }
387
388 send_event(id, event, params, timestamp) {
393389 if (!this._proxy)
394390 return
395391
396392 this._proxy.EventRemote(id, event, params, timestamp, function(result, error) { /* we don't care */ })
397 },
398
399 _onLayoutUpdated: function() {
393 }
394
395 _onLayoutUpdated() {
400396 this._requestLayoutUpdate()
401 },
402
403 _onPropertiesUpdated: function(proxy, name, [changed, removed]) {
404 changed.forEach(function([id, props]) {
397 }
398
399 _onPropertiesUpdated(proxy, name, [changed, removed]) {
400 changed.forEach(([id, props]) => {
405401 if (!(id in this._items))
406402 return
407403
408404 for (let prop in props)
409405 this._items[id].property_set(prop, props[prop])
410406 }, this)
411 removed.forEach(function([id, propNames]) {
407 removed.forEach(([id, propNames]) => {
412408 if (!(id in this._items))
413409 return
414410
415 propNames.forEach(function(propName) {
411 propNames.forEach((propName) => {
416412 this._items[id].property_set(propName, null)
417413 }, this)
418414 }, this)
419 },
420
421 destroy: function() {
415 }
416
417 destroy() {
422418 this.emit('destroy')
423419
424420 Signals._disconnectAll.apply(this._proxy)
425421
426422 this._proxy = null
427423 }
428 })
424 }
429425 Signals.addSignalMethods(DBusClient.prototype)
430426
431427 //////////////////////////////////////////////////////////////////////////
492488 return shellItem
493489 },
494490
495 _onOpenStateChanged: function(menu, open) {
491 _onOpenStateChanged(menu, open) {
496492 if (open) {
497493 if (NEED_NESTED_SUBMENU_FIX) {
498494 // close our own submenus
519515 }
520516 },
521517
522 _onActivate: function() {
518 _onActivate() {
523519 this._dbusItem.handle_event("clicked", GLib.Variant.new("i", 0), 0)
524520 },
525521
526 _onPropertyChanged: function(dbusItem, prop, value) {
522 _onPropertyChanged(dbusItem, prop, value) {
527523 if (prop == "toggle-type" || prop == "toggle-state")
528524 MenuItemFactory._updateOrnament.call(this)
529525 else if (prop == "label")
540536 // Util.Logger.debug("Unhandled property change: "+prop)
541537 },
542538
543 _onChildAdded: function(dbusItem, child, position) {
539 _onChildAdded(dbusItem, child, position) {
544540 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
545541 Util.Logger.warn("Tried to add a child to non-submenu item. Better recreate it as whole")
546542 MenuItemFactory._replaceSelf.call(this)
549545 }
550546 },
551547
552 _onChildRemoved: function(dbusItem, child) {
548 _onChildRemoved(dbusItem, child) {
553549 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
554550 Util.Logger.warn("Tried to remove a child from non-submenu item. Better recreate it as whole")
555551 MenuItemFactory._replaceSelf.call(this)
556552 } else {
557553 // find it!
558 this.menu._getMenuItems().forEach(function(item) {
554 this.menu._getMenuItems().forEach((item) => {
559555 if (item._dbusItem == child)
560556 item.destroy()
561557 })
562558 }
563559 },
564560
565 _onChildMoved: function(dbusItem, child, oldpos, newpos) {
561 _onChildMoved(dbusItem, child, oldpos, newpos) {
566562 if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) {
567563 Util.Logger.warn("Tried to move a child in non-submenu item. Better recreate it as whole")
568564 MenuItemFactory._replaceSelf.call(this)
571567 }
572568 },
573569
574 _updateLabel: function() {
570 _updateLabel() {
575571 let label = this._dbusItem.property_get("label").replace(/_([^_])/, "$1")
576572
577573 if (this.label) // especially on GS3.8, the separator item might not even have a hidden label
578574 this.label.set_text(label)
579575 },
580576
581 _updateOrnament: function() {
577 _updateOrnament() {
582578 if (!this.setOrnament) return // separators and alike might not have gotten the polyfill
583579
584580 if (this._dbusItem.property_get("toggle-type") == "checkmark" && this._dbusItem.property_get_int("toggle-state"))
589585 this.setOrnament(PopupMenu.Ornament.NONE)
590586 },
591587
592 _updateImage: function() {
588 _updateImage() {
593589 if (!this._icon) return // might be missing on submenus / separators
594590
595591 let iconName = this._dbusItem.property_get("icon-name")
600596 this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null)
601597 },
602598
603 _updateVisible: function() {
599 _updateVisible() {
604600 this.actor.visible = this._dbusItem.property_get_bool("visible")
605601 },
606602
607 _updateSensitive: function() {
603 _updateSensitive() {
608604 this.setSensitive(this._dbusItem.property_get_bool("enabled"))
609605 },
610606
611 _replaceSelf: function(newSelf) {
607 _replaceSelf(newSelf) {
612608 // create our new self if needed
613609 if (!newSelf)
614610 newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem)
637633 * Utility functions not necessarily belonging into the item factory
638634 */
639635 const MenuUtils = {
640 moveItemInMenu: function(menu, dbusItem, newpos) {
636 moveItemInMenu(menu, dbusItem, newpos) {
641637 //HACK: we're really getting into the internals of the PopupMenu implementation
642638
643639 // First, find our wrapper. Children tend to lie. We do not trust the old positioning.
666662 *
667663 * Something like a mini-god-object
668664 */
669 var Client = new Lang.Class({
670 Name: 'DbusMenuClient',
671
672 _init: function(busName, path) {
673 this.parent()
665 var Client = class AppIndicators_Client {
666
667 constructor(busName, path) {
674668 this._busName = busName
675669 this._busPath = path
676670 this._client = new DBusClient(busName, path)
677671 this._rootMenu = null // the shell menu
678672 this._rootItem = null // the DbusMenuItem for the root
679 },
673 }
680674
681675 // this will attach the client to an already existing menu that will be used as the root menu.
682676 // it will also connect the client to be automatically destroyed when the menu dies.
683 attachToMenu: function(menu) {
677 attachToMenu(menu) {
684678 this._rootMenu = menu
685679 this._rootItem = this._client.get_root()
686680
702696 this._rootItem.send_about_to_show()
703697
704698 // fill the menu for the first time
705 this._rootItem.get_children().forEach(function(child) {
699 this._rootItem.get_children().forEach((child) => {
706700 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child))
707701 }, this)
708 },
709
710 _setOpenedSubmenu: function(submenu) {
702 }
703
704 _setOpenedSubmenu(submenu) {
711705 if (!submenu)
712706 return
713707
721715 this._openedSubMenu.close(true)
722716
723717 this._openedSubMenu = submenu
724 },
725
726 _onRootChildAdded: function(dbusItem, child, position) {
718 }
719
720 _onRootChildAdded(dbusItem, child, position) {
727721 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position)
728 },
729
730 _onRootChildRemoved: function(dbusItem, child) {
722 }
723
724 _onRootChildRemoved(dbusItem, child) {
731725 // children like to play hide and seek
732726 // but we know how to find it for sure!
733 this._rootMenu._getMenuItems().forEach(function(item) {
727 this._rootMenu._getMenuItems().forEach((item) => {
734728 if (item._dbusItem == child)
735729 item.destroy()
736730 })
737 },
738
739 _onRootChildMoved: function(dbusItem, child, oldpos, newpos) {
731 }
732
733 _onRootChildMoved(dbusItem, child, oldpos, newpos) {
740734 MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos)
741 },
742
743 _onMenuOpened: function(menu, state) {
735 }
736
737 _onMenuOpened(menu, state) {
744738 if (!this._rootItem) return
745739
746740 if (state) {
752746 } else {
753747 this._rootItem.handle_event("closed", null, 0)
754748 }
755 },
756
757 destroy: function() {
749 }
750
751 destroy() {
758752 this.emit('destroy')
759753
760754 if (this._client)
764758 this._rootItem = null
765759 this._rootMenu = null
766760 }
767 })
761 }
768762 Signals.addSignalMethods(Client.prototype)
2222
2323 let statusNotifierWatcher = null;
2424 let isEnabled = false;
25 let watchDog = null;
2526
2627 function init() {
27 NameWatchdog.init();
28 NameWatchdog.onVanished = maybe_enable_after_name_available;
28 watchDog = new NameWatchdog();
29 watchDog.onVanished = maybe_enable_after_name_available;
2930
3031 //HACK: we want to leave the watchdog alive when disabling the extension,
3132 // but if we are being reloaded, we destroy it since it could be considered
3334 if (typeof global['--appindicator-extension-on-reload'] == 'function')
3435 global['--appindicator-extension-on-reload']()
3536
36 global['--appindicator-extension-on-reload'] = function() {
37 global['--appindicator-extension-on-reload'] = () => {
3738 Util.Logger.debug("Reload detected, destroying old watchdog")
38 NameWatchdog.destroy()
39 watchDog.destroy();
3940 }
4041 }
4142
4546 // monitor the bus manually to find out when the name vanished so we can reclaim it again.
4647 function maybe_enable_after_name_available() {
4748 // by the time we get called whe might not be enabled
48 if (isEnabled && !NameWatchdog.isPresent && statusNotifierWatcher === null)
49 if (isEnabled && !watchDog.isPresent && statusNotifierWatcher === null)
4950 statusNotifierWatcher = new StatusNotifierWatcher.StatusNotifierWatcher();
5051 }
5152
6566 /**
6667 * NameWatchdog will monitor the ork.kde.StatusNotifierWatcher bus name for us
6768 */
68 const NameWatchdog = {
69 onAppeared: null,
70 onVanished: null,
69 var NameWatchdog = class AppIndicators_NameWatchdog {
7170
72 _watcher_id: null,
71 constructor() {
72 this.onAppeared = null;
73 this.onVanished = null;
7374
74 isPresent: false, //will be set in the handlers which are guaranteed to be called at least once
75 // will be set in the handlers which are guaranteed to be called at least once
76 this.isPresent = false;
7577
76 init: function() {
7778 this._watcher_id = Gio.DBus.session.watch_name("org.kde.StatusNotifierWatcher", 0,
7879 this._appeared_handler.bind(this), this._vanished_handler.bind(this));
79 },
80 }
8081
81 destroy: function() {
82 destroy() {
8283 Gio.DBus.session.unwatch_name(this._watcher_id);
83 },
84 }
8485
85 _appeared_handler: function() {
86 _appeared_handler() {
8687 this.isPresent = true;
8788 if (this.onAppeared) this.onAppeared();
88 },
89 }
8990
90 _vanished_handler: function() {
91 _vanished_handler() {
9192 this.isPresent = false;
9293 if (this.onVanished) this.onVanished();
9394 }
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1515
1616 const GLib = imports.gi.GLib
17 const GObject = imports.gi.GObject
1718
18 const Lang = imports.lang
1919 const Mainloop = imports.mainloop
2020
2121 const Util = imports.misc.extensionUtils.getCurrentExtension().imports.util;
2626 // If the lifetime of an icon is over, the cache will destroy the icon. (!)
2727 // The presence of an inUse property set to true on the icon will extend the lifetime.
2828
29 const LIFETIME_TIMESPAN = 5000; // milli-seconds
30 const GC_INTERVAL = 10; // seconds
31
2932 // how to use: see IconCache.add, IconCache.get
30 var IconCache = new Lang.Class({
31 Name: 'IconCache',
32
33 LIFETIME_TIMESPAN: 5000, //5s
34 GC_INTERVAL: 10000, //10s
35
36 _init: function() {
33 var IconCache = class AppIndicators_IconCache {
34 constructor() {
3735 this._cache = {};
3836 this._lifetime = {}; //we don't want to attach lifetime to the object
39 this._gc();
40 },
37 this._destroyNotify = {};
38 }
4139
42 add: function(id, o) {
43 //Util.Logger.debug("IconCache: adding "+id);
44 if (!(o && id)) return null;
45 if (id in this._cache && this._cache[id] !== o)
40 add(id, o) {
41 if (!(o && id))
42 return null;
43
44 if (!(id in this._cache) || this._cache[id] !== o) {
4645 this._remove(id);
47 this._cache[id] = o;
48 this._lifetime[id] = new Date().getTime() + this.LIFETIME_TIMESPAN;
46
47 //Util.Logger.debug("IconCache: adding "+id,o);
48 this._cache[id] = o;
49
50 if ((o instanceof GObject.Object) && GObject.signal_lookup('destroy', o)) {
51 this._destroyNotify[id] = o.connect('destroy', () => {
52 this._remove(id);
53 });
54 }
55 }
56
57 this._renewLifetime(id);
58 this._checkGC();
59
4960 return o;
50 },
61 }
5162
52 _remove: function(id) {
63 _remove(id) {
64 if (!(id in this._cache))
65 return;
66
5367 //Util.Logger.debug('IconCache: removing '+id);
54 if ('destroy' in this._cache[id]) this._cache[id].destroy();
68
69 let object = this._cache[id];
70
71 if ((object instanceof GObject.Object) && GObject.signal_lookup('destroy', object))
72 object.disconnect(this._destroyNotify[id]);
73
74 if (typeof object.destroy === 'function')
75 object.destroy();
76
5577 delete this._cache[id];
5678 delete this._lifetime[id];
57 },
79 delete this._destroyNotify[id];
5880
59 forceDestroy: function(id) {
81 this._checkGC();
82 }
83
84 _renewLifetime(id) {
85 if (id in this._cache)
86 this._lifetime[id] = new Date().getTime() + LIFETIME_TIMESPAN;
87 }
88
89 forceDestroy(id) {
6090 this._remove(id);
61 },
91 }
6292
6393 // removes everything from the cache
64 clear: function() {
94 clear() {
6595 for (let id in this._cache)
6696 this._remove(id)
67 },
97
98 this._checkGC();
99 }
68100
69101 // returns an object from the cache, or null if it can't be found.
70 get: function(id) {
102 get(id) {
71103 if (id in this._cache) {
72104 //Util.Logger.debug('IconCache: retrieving '+id);
73 this._lifetime[id] = new Date().getTime() + this.LIFETIME_TIMESPAN; //renew lifetime
105 this._renewLifetime(id);
74106 return this._cache[id];
75107 }
76 else return null;
77 },
78108
79 _gc: function() {
109 return null;
110 }
111
112 _checkGC() {
113 let cacheIsEmpty = (Object.keys(this._cache).length === 0);
114
115 if (!cacheIsEmpty && !this._gcTimeout) {
116 //Util.Logger.debug("IconCache: garbage collector started");
117 this._gcTimeout = Mainloop.timeout_add_seconds(GC_INTERVAL,
118 this._gc.bind(this));
119 } else if (cacheIsEmpty && this._gcTimeout) {
120 //Util.Logger.debug("IconCache: garbage collector stopped");
121 GLib.Source.remove(this._gcTimeout);
122 this._gcTimeout = 0;
123 }
124 }
125
126 _gc() {
80127 var time = new Date().getTime();
81128 for (var id in this._cache) {
82129 if (this._cache[id].inUse) {
83 //Util.Logger.debug ("IconCache: " + id + " is in use.");
130 //Util.Logger.debug("IconCache: " + id + " is in use.");
84131 continue;
85132 } else if (this._lifetime[id] < time) {
86133 this._remove(id);
88135 //Util.Logger.debug("IconCache: " + id + " survived this round.");
89136 }
90137 }
91 if (!this._stopGc) Mainloop.timeout_add(this.GC_INTERVAL, Lang.bind(this, this._gc));
92 return false; //we just added our timeout again.
93 },
94138
95 destroy: function() {
96 this._stopGc = true;
139 return true;
140 }
141
142 destroy() {
97143 this.clear();
98144 }
99 });
145 };
88 const AppIndicator = imports.gi.AppIndicator3;
99 const GLib = imports.gi.GLib;
1010
11 (function() {
11 (() => {
1212
1313 var app = new Gtk.Application({
1414 application_id: null
1616
1717 var window = null;
1818
19 app.connect("activate", function(){
19 app.connect("activate", () => {
2020 window.present();
2121 });
2222
23 app.connect("startup", function() {
23 app.connect("startup", () => {
2424 window = new Gtk.ApplicationWindow({
2525 title: "test",
2626 application: app
9696 menu.append(item);
9797
9898 item = Gtk.MenuItem.new_with_label("Set Label");
99 item.connect('activate', function() {
99 item.connect('activate', () => {
100100 indicator.set_label(''+new Date().getSeconds(), 'Blub');
101101 });
102102 menu.append(item);
103103
104104 item = Gtk.MenuItem.new_with_label("Unset Label");
105 item.connect('activate', function() {
105 item.connect('activate', () => {
106106 indicator.set_label('', '');
107107 })
108108 menu.append(item);
111111 menu.append(item);
112112
113113 item = Gtk.MenuItem.new_with_label("Hide for some time");
114 item.connect('activate', function() {
114 item.connect('activate', () => {
115115 indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE);
116 GLib.timeout_add(0, 5000, function() {
116 GLib.timeout_add(0, 5000, () => {
117117 indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE);
118118 return false;
119119 });
121121 menu.append(item);
122122
123123 item = Gtk.MenuItem.new_with_label("Close in 5 seconds");
124 item.connect('activate', function() {
125 GLib.timeout_add(0, 5000, function() {
124 item.connect('activate', () => {
125 GLib.timeout_add(0, 5000, () => {
126126 app.quit();
127127 return false;
128128 });
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 Clutter = imports.gi.Clutter;
16 const GObject = imports.gi.GObject;
1617 const St = imports.gi.St;
1718
18 const Lang = imports.lang;
1919 const Main = imports.ui.main;
2020 const Panel = imports.ui.panel;
2121 const PanelMenu = imports.ui.panelMenu;
3030 /*
3131 * IndicatorStatusIcon implements an icon in the system status area
3232 */
33 var IndicatorStatusIcon = new Lang.Class({
34 Name: 'IndicatorStatusIcon',
35 Extends: PanelMenu.Button,
36
37 _init: function(indicator) {
38 this.parent(null, 'FIXME'); //no name yet (?)
39
33 var IndicatorStatusIcon = GObject.registerClass(
34 class AppIndicators_IndicatorStatusIcon extends PanelMenu.Button {
35 _init(indicator) {
36 super._init(null, indicator._uniqueId);
4037 this._indicator = indicator;
4138
4239 this._iconBox = new AppIndicator.IconActor(indicator, Panel.PANEL_ICON_SIZE + 6);
5148 Util.connectSmart(this._indicator, 'label', this, '_updateLabel')
5249 Util.connectSmart(this._indicator, 'status', this, '_updateStatus')
5350
51 this.connect('destroy', () => {
52 if (this._menuClient) {
53 this._menuClient.destroy();
54 this._menuClient = null;
55 }
56 })
57
5458 if (this._indicator.isReady)
5559 this._display()
56 },
60 }
5761
58 _updateLabel: function() {
62 _updateLabel() {
5963 var label = this._indicator.label;
6064 if (label) {
6165 if (!this._label || !this._labelBin) {
7579 delete this._label;
7680 }
7781 }
78 },
82 }
7983
80 _updateStatus: function() {
84 _updateStatus() {
8185 if (this._indicator.status != AppIndicator.SNIStatus.PASSIVE)
8286 this.actor.show()
8387 else
8488 this.actor.hide()
85 },
89 }
8690
87 destroy: function() {
88 // destroy stuff owned by us
89 if (this._menuClient)
90 this._menuClient.destroy()
91
92 this._iconBox.destroy()
93
94 this._box.destroy_all_children()
95
96 //call parent
97 this.parent()
98 },
99
100 _display: function() {
91 _display() {
10192 this._updateLabel()
10293 this._updateStatus()
10394
10798 }
10899
109100 Main.panel.addToStatusArea("appindicator-"+this._indicator.uniqueId, this, 1, 'right')
110 },
101 }
111102
112 _boxClicked: function(actor, event) {
103 _boxClicked(actor, event) {
113104 // if middle mouse button clicked send SecondaryActivate dbus event and do not show appindicator menu
114105 if (event.get_button() == 2) {
115106 Main.panel.menuManager._closeMenu(true, Main.panel.menuManager.activeMenu);
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).
37 if (contents instanceof Uint8Array)
38 contents = imports.byteArray.toString(contents);
3739 return "<node>" + contents + "</node>"
3840 } else {
3941 throw new Error("AppIndicatorSupport: Could not load file: "+filename)
44 "uuid": "ubuntu-appindicators@ubuntu.com",
55 "url": "https://github.com/ubuntu/gnome-shell-extension-appindicator",
66 "shell-version": [
7 "3.24",
8 "3.26"
7 "3.30",
8 "3.31",
9 "3.32"
910 ]
1011 }
1717 const GLib = imports.gi.GLib
1818 const Gtk = imports.gi.Gtk
1919
20 const Lang = imports.lang
2120 const Mainloop = imports.mainloop
2221 const ShellConfig = imports.misc.config
2322 const Signals = imports.signals
4241 /*
4342 * The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object
4443 */
45 var StatusNotifierWatcher = new Lang.Class({
46 Name: 'StatusNotifierWatcher',
47
48 _init: function() {
44 var StatusNotifierWatcher = class AppIndicators_StatusNotifierWatcher {
45
46 constructor() {
4947 this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this);
5048 this._dbusImpl.export(Gio.DBus.session, WATCHER_OBJECT);
5149 this._cancellable = new Gio.Cancellable;
5250 this._everAcquiredName = false;
5351 this._ownName = Gio.DBus.session.own_name(WATCHER_BUS_NAME,
54 Gio.BusNameOwnerFlags.NONE,
55 Lang.bind(this, this._acquiredName),
56 Lang.bind(this, this._lostName));
52 Gio.BusNameOwnerFlags.NONE,
53 this._acquiredName.bind(this),
54 this._lostName.bind(this));
5755 this._items = { };
5856 this._nameWatcher = { };
5957
6058 this._seekStatusNotifierItems();
61 },
62
63 _acquiredName: function() {
59 }
60
61 _acquiredName() {
6462 this._everAcquiredName = true;
65 },
66
67 _lostName: function() {
63 }
64
65 _lostName() {
6866 if (this._everAcquiredName)
6967 Util.Logger.debug('Lost name' + WATCHER_BUS_NAME);
7068 else
7169 Util.Logger.warn('Failed to acquire ' + WATCHER_BUS_NAME);
72 },
70 }
7371
7472
7573 // create a unique index for the _items dictionary
76 _getItemId: function(bus_name, obj_path) {
74 _getItemId(bus_name, obj_path) {
7775 return bus_name + obj_path;
78 },
79
80 _registerItem: function(service, bus_name, obj_path) {
76 }
77
78 _registerItem(service, bus_name, obj_path) {
8179 let id = this._getItemId(bus_name, obj_path);
8280
8381 if (this._items[id]) {
9896 this._itemVanished.bind(this));
9997
10098 this._dbusImpl.emit_property_changed('RegisteredStatusNotifierItems', GLib.Variant.new('as', this.RegisteredStatusNotifierItems));
101 },
102
103 _ensureItemRegistered: function(service, bus_name, obj_path) {
99 }
100
101 _ensureItemRegistered(service, bus_name, obj_path) {
104102 let id = this._getItemId(bus_name, obj_path);
105103
106104 if (this._items[id]) {
110108 }
111109
112110 this._registerItem(service, bus_name, obj_path)
113 },
114
115 _seekStatusNotifierItems: function() {
111 }
112
113 _seekStatusNotifierItems() {
116114 // Some indicators (*coff*, dropbox, *coff*) do not re-register again
117115 // when the plugin is enabled/disabled, thus we need to manually look
118116 // for the objects in the session bus that implements the
119117 // StatusNotifierItem interface...
120 let self = this;
121 Util.traverseBusNames(Gio.DBus.session, this._cancellable, function(bus, name, cancellable) {
122 Util.introspectBusObject(bus, name, cancellable, function(node_info) {
123 return Util.dbusNodeImplementsInterfaces(node_info, ["org.kde.StatusNotifierItem"]);
124 },
125 function(name, path) {
126 let id = self._getItemId(name, path);
127 if (!self._items[id]) {
118 Util.traverseBusNames(Gio.DBus.session, this._cancellable, (bus, name, cancellable) => {
119 Util.introspectBusObject(bus, name, cancellable, (node_info) => {
120 return Util.dbusNodeImplementsInterfaces(node_info, ['org.kde.StatusNotifierItem']);
121 }, (name, path) => {
122 let id = this._getItemId(name, path);
123 if (!this._items[id]) {
128124 Util.Logger.debug("Using Brute-force mode for StatusNotifierItem "+id);
129 self._registerItem(path, name, path);
125 this._registerItem(path, name, path);
130126 }
131127 })
132128 });
133 },
134
135 RegisterStatusNotifierItemAsync: function(params, invocation) {
129 }
130
131 RegisterStatusNotifierItemAsync(params, invocation) {
136132 // it would be too easy if all application behaved the same
137133 // instead, ayatana patched gnome apps to send a path
138134 // while kde apps send a bus name
160156 this._ensureItemRegistered(service, bus_name, obj_path);
161157
162158 invocation.return_value(null);
163 },
164
165 _itemVanished: function(proxy, bus_name) {
159 }
160
161 _itemVanished(proxy, bus_name) {
166162 // FIXME: this is useless if the path name disappears while the bus stays alive (not unheard of)
167163 for (var i in this._items) {
168164 if (i.indexOf(bus_name) == 0) {
169165 this._remove(i);
170166 }
171167 }
172 },
173
174 _remove: function(id) {
168 }
169
170 _remove(id) {
175171 this._items[id].destroy();
176172 delete this._items[id];
177173 Gio.DBus.session.unwatch_name(this._nameWatcher[id]);
178174 delete this._nameWatcher[id];
179175 this._dbusImpl.emit_signal('StatusNotifierItemUnregistered', GLib.Variant.new('(s)', id));
180176 this._dbusImpl.emit_property_changed('RegisteredStatusNotifierItems', GLib.Variant.new('as', this.RegisteredStatusNotifierItems));
181 },
182
183 RegisterNotificationHost: function(service) {
177 }
178
179 RegisterNotificationHost(service) {
184180 throw new Gio.DBusError('org.gnome.Shell.UnsupportedMethod',
185181 'Registering additional notification hosts is not supported');
186 },
187
188 IsNotificationHostRegistered: function() {
182 }
183
184 IsNotificationHostRegistered() {
189185 return true;
190 },
191
192 ProtocolVersion: function() {
186 }
187
188 ProtocolVersion() {
193189 // "The version of the protocol the StatusNotifierWatcher instance implements." [sic]
194190 // in what syntax?
195 return "appindicatorsupport@rgcjonas.gmail.com (KDE; compatible; mostly) GNOME Shell/%s".format(ShellConfig.PACKAGE_VERSION);
196 },
191 return `${Extension.uuid} (KDE; compatible; mostly) GNOME Shell/${ShellConfig.PACKAGE_VERSION}`;
192 }
197193
198194 get RegisteredStatusNotifierItems() {
199195 return Object.keys(this._items);
200 },
196 }
201197
202198 get IsStatusNotifierHostRegistered() {
203199 return true;
204 },
205
206 destroy: function() {
200 }
201
202 destroy() {
207203 if (!this._isDestroyed) {
208204 // this doesn't do any sync operation and doesn't allow us to hook up the event of being finished
209205 // which results in our unholy debounce hack (see extension.js)
221217 this._isDestroyed = true;
222218 }
223219 }
224 });
220 };
1616 const GLib = imports.gi.GLib
1717 const GObject = imports.gi.GObject
1818
19 const Lang = imports.lang
2019 const Signals = imports.signals
2120
22 const refreshPropertyOnProxy = function(proxy, property_name) {
21 var refreshPropertyOnProxy = function(proxy, property_name) {
2322 proxy.g_connection.call(proxy.g_name,
2423 proxy.g_object_path,
2524 'org.freedesktop.DBus.Properties',
4645 })
4746 }
4847
49 const getUniqueBusNameSync = function(bus, name) {
48 var getUniqueBusNameSync = function(bus, name) {
5049 if (name[0] == ':')
5150 return name;
5251
136135 let id = src.connect(signal, handler)
137136
138137 if (src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src))) {
139 let destroy_id = src.connect('destroy', function() {
138 let destroy_id = src.connect('destroy', () => {
140139 src.disconnect(id)
141140 src.disconnect(destroy_id)
142141 })
170169 * Usage:
171170 * Util.connectSmart(srcOb, 'signal', tgtObj, 'handler')
172171 * or
173 * Util.connectSmart(srcOb, 'signal', function() { ... })
172 * Util.connectSmart(srcOb, 'signal', () => { ... })
174173 */
175174 var connectSmart = function() {
176175 if (arguments.length == 4)
182181 /**
183182 * Helper class for logging stuff
184183 */
185 var Logger = {
186 _log: function(prefix, message) {
184 var Logger = class AppIndicators_Logger {
185 static _log(prefix, message) {
187186 global.log("[AppIndicatorSupport-"+prefix+"] "+message)
188 },
189
190 debug: function(message) {
187 }
188
189 static debug(message) {
190 // CHeck the shell env variable to get what level to use
191191 Logger._log("DEBUG", message);
192 },
193
194 warn: function(message) {
192 }
193
194 static warn(message) {
195195 Logger._log("WARN", message);
196 },
197
198 error: function(message) {
196 }
197
198 static error(message) {
199199 Logger._log("ERROR", message);
200 },
201
202 fatal: function(message) {
200 }
201
202 static fatal(message) {
203203 Logger._log("FATAL", message);
204204 }
205205 };
206
207 /**
208 * Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=734071
209 *
210 * Will append the given name with a number to distinguish code loaded later from the last loaded version
211 */
212 var WORKAROUND_RELOAD_TYPE_REGISTER = function(name) {
213 return 'Gjs_' + name + '__' + global['--appindicator-loaded-count']
214 }
215
216 // this will only execute once when the extension is loaded
217 if (!global['--appindicator-loaded-count'])
218 global['--appindicator-loaded-count'] = 1
219 else
220 global['--appindicator-loaded-count']++