Merge 'master' into ubuntu
Marco Trevisan (TreviƱo)
5 years ago
18 | 18 | const GdkPixbuf = imports.gi.GdkPixbuf |
19 | 19 | const Gio = imports.gi.Gio |
20 | 20 | const GLib = imports.gi.GLib |
21 | const GObject = imports.gi.GObject | |
21 | 22 | const Gtk = imports.gi.Gtk |
22 | 23 | const St = imports.gi.St |
23 | 24 | const Shell = imports.gi.Shell |
24 | 25 | |
25 | 26 | const Extension = imports.misc.extensionUtils.getCurrentExtension(); |
26 | const Lang = imports.lang | |
27 | 27 | const Signals = imports.signals |
28 | 28 | |
29 | 29 | const DBusMenu = Extension.imports.dbusMenu; |
48 | 48 | * the AppIndicator class serves as a generic container for indicator information and functions common |
49 | 49 | * for every displaying implementation (IndicatorMessageSource and IndicatorStatusIcon) |
50 | 50 | */ |
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) { | |
55 | 54 | this.busName = bus_name |
56 | 55 | this._uniqueId = bus_name + object |
57 | 56 | |
65 | 64 | g_name: bus_name, |
66 | 65 | g_object_path: object, |
67 | 66 | 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) => { | |
69 | 68 | try { |
70 | 69 | initable.init_finish(result); |
71 | 70 | |
74 | 73 | } catch(e) { |
75 | 74 | Util.Logger.warn("While intializing proxy for "+bus_name+object+": "+e) |
76 | 75 | } |
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'); | |
81 | 82 | |
82 | 83 | Util.connectSmart(this._proxy, 'g-properties-changed', this, '_onPropertiesChanged') |
83 | 84 | 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 | } | |
85 | 98 | |
86 | 99 | // 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) { | |
91 | 109 | if (this._proxyPropertyList.indexOf(prop) > -1) |
92 | 110 | Util.refreshPropertyOnProxy(this._proxy, prop) |
93 | 111 | |
96 | 114 | |
97 | 115 | if (this._proxyPropertyList.indexOf(prop + 'Name') > -1) |
98 | 116 | 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 | } | |
104 | 119 | |
105 | 120 | //public property getters |
106 | 121 | get title() { |
107 | 122 | return this._proxy.Title; |
108 | }, | |
123 | } | |
109 | 124 | get id() { |
110 | 125 | return this._proxy.Id; |
111 | }, | |
126 | } | |
112 | 127 | get uniqueId() { |
113 | 128 | return this._uniqueId; |
114 | }, | |
129 | } | |
115 | 130 | get status() { |
116 | 131 | return this._proxy.Status; |
117 | }, | |
132 | } | |
118 | 133 | 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 | } | |
124 | 136 | get menuPath() { |
125 | 137 | return this._proxy.Menu || "/MenuBar" |
126 | }, | |
138 | } | |
127 | 139 | |
128 | 140 | get attentionIcon() { |
129 | 141 | return [ |
131 | 143 | this._proxy.AttentionIconPixmap, |
132 | 144 | this._proxy.IconThemePath |
133 | 145 | ] |
134 | }, | |
146 | } | |
135 | 147 | |
136 | 148 | get icon() { |
137 | 149 | return [ |
139 | 151 | this._proxy.IconPixmap, |
140 | 152 | this._proxy.IconThemePath |
141 | 153 | ] |
142 | }, | |
154 | } | |
143 | 155 | |
144 | 156 | get overlayIcon() { |
145 | 157 | return [ |
147 | 159 | this._proxy.OverlayIconPixmap, |
148 | 160 | this._proxy.IconThemePath |
149 | 161 | ] |
150 | }, | |
151 | ||
152 | _onPropertiesChanged: function(proxy, changed, invalidated) { | |
162 | } | |
163 | ||
164 | _onPropertiesChanged(proxy, changed, invalidated) { | |
153 | 165 | let props = Object.keys(changed.deep_unpack()) |
154 | 166 | |
155 | props.forEach(function(property) { | |
167 | props.forEach((property) => { | |
156 | 168 | // some property changes require updates on our part, |
157 | 169 | // a few need to be passed down to the displaying code |
158 | 170 | |
178 | 190 | if (property == 'Status') |
179 | 191 | this.emit('status') |
180 | 192 | }, this); |
181 | }, | |
182 | ||
183 | reset: function() { | |
193 | } | |
194 | ||
195 | reset() { | |
184 | 196 | //TODO: reload all properties, or do some other useful things |
185 | 197 | this.emit('reset') |
186 | }, | |
187 | ||
188 | destroy: function() { | |
198 | } | |
199 | ||
200 | destroy() { | |
189 | 201 | this.emit('destroy') |
190 | 202 | |
191 | 203 | this.disconnectAll() |
192 | 204 | delete this._proxy |
193 | }, | |
194 | ||
195 | open: function() { | |
205 | } | |
206 | ||
207 | open() { | |
196 | 208 | // we can't use WindowID because we're not able to get the x11 window id from a MetaWindow |
197 | 209 | // nor can we call any X11 functions. Luckily, the Activate method usually works fine. |
198 | 210 | // parameters are "an hint to the item where to show eventual windows" [sic] |
199 | 211 | // ... and don't seem to have any effect. |
200 | 212 | this._proxy.ActivateRemote(0, 0) |
201 | }, | |
202 | ||
203 | secondaryActivate: function () { | |
213 | } | |
214 | ||
215 | secondaryActivate() { | |
204 | 216 | this._proxy.SecondaryActivateRemote(0, 0) |
205 | }, | |
206 | ||
207 | scroll: function(dx, dy) { | |
217 | } | |
218 | ||
219 | scroll(dx, dy) { | |
208 | 220 | if (dx != 0) |
209 | 221 | this._proxy.ScrollRemote(Math.floor(dx), 'horizontal') |
210 | 222 | |
211 | 223 | if (dy != 0) |
212 | 224 | this._proxy.ScrollRemote(Math.floor(dy), 'vertical') |
213 | 225 | } |
214 | }); | |
226 | }; | |
215 | 227 | Signals.addSignalMethods(AppIndicator.prototype); |
216 | 228 | |
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 | ||
224 | 236 | let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor; |
225 | 237 | this.width = icon_size * scale_factor |
226 | 238 | this.height = icon_size * scale_factor |
245 | 257 | |
246 | 258 | if (indicator.isReady) |
247 | 259 | this._invalidateIcon() |
248 | }, | |
260 | ||
261 | this.connect('destroy', () => { | |
262 | this._iconCache.destroy(); | |
263 | }); | |
264 | } | |
249 | 265 | |
250 | 266 | // Will look the icon up in the cache, if it's found |
251 | 267 | // it will return it. Otherwise, it will create it and cache it. |
253 | 269 | // the returned icon anymore, make sure to check the .inUse property |
254 | 270 | // and set it to false if needed so that it can be picked up by the garbage |
255 | 271 | // collector. |
256 | _cacheOrCreateIconByName: function(iconSize, iconName, themePath) { | |
272 | _cacheOrCreateIconByName(iconSize, iconName, themePath) { | |
257 | 273 | let id = iconName + '@' + iconSize + (themePath ? '##' + themePath : '') |
258 | 274 | |
259 | 275 | let icon = this._iconCache.get(id) || this._createIconByName(iconSize, iconName, themePath) |
264 | 280 | } |
265 | 281 | |
266 | 282 | return icon |
267 | }, | |
268 | ||
269 | _createIconByName: function(icon_size, icon_name, themePath) { | |
283 | } | |
284 | ||
285 | _createIconByName(icon_size, icon_name, themePath) { | |
270 | 286 | // real_icon_size will contain the actual icon size in contrast to the requested icon size |
271 | 287 | var real_icon_size = icon_size |
272 | 288 | var gicon = null |
294 | 310 | // we try to avoid messing with the default icon theme, so we'll create a new one if needed |
295 | 311 | if (themePath) { |
296 | 312 | 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) => { | |
298 | 314 | icon_theme.append_search_path(path) |
299 | 315 | }); |
300 | 316 | icon_theme.append_search_path(themePath) |
331 | 347 | return new St.Icon({ gicon: gicon, icon_size: real_icon_size }) |
332 | 348 | else |
333 | 349 | return null |
334 | }, | |
335 | ||
336 | _createIconFromPixmap: function(iconSize, iconPixmapArray) { | |
350 | } | |
351 | ||
352 | _createIconFromPixmap(iconSize, iconPixmapArray) { | |
337 | 353 | let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor; |
338 | 354 | iconSize = iconSize * scale_factor |
339 | 355 | // the pixmap actually is an array of pixmaps with different sizes |
343 | 359 | if (!iconPixmapArray || iconPixmapArray.length < 1) |
344 | 360 | return null |
345 | 361 | |
346 | let sortedIconPixmapArray = iconPixmapArray.sort(function(pixmapA, pixmapB) { | |
362 | let sortedIconPixmapArray = iconPixmapArray.sort((pixmapA, pixmapB) => { | |
347 | 363 | // we sort biggest to smallest |
348 | 364 | let areaA = pixmapA[0] * pixmapA[1] |
349 | 365 | let areaB = pixmapB[0] * pixmapB[1] |
351 | 367 | return areaB - areaA |
352 | 368 | }) |
353 | 369 | |
354 | let qualifiedIconPixmapArray = sortedIconPixmapArray.filter(function(pixmap) { | |
370 | let qualifiedIconPixmapArray = sortedIconPixmapArray.filter((pixmap) => { | |
355 | 371 | // we disqualify any pixmap that is bigger than our requested size |
356 | 372 | return pixmap[0] <= iconSize && pixmap[1] <= iconSize |
357 | 373 | }) |
389 | 405 | // we could log it here, but that doesn't really help in tracking it down. |
390 | 406 | return null |
391 | 407 | } |
392 | }, | |
408 | } | |
393 | 409 | |
394 | 410 | // updates the base icon |
395 | _updateIcon: function() { | |
411 | _updateIcon() { | |
396 | 412 | // remove old icon |
397 | 413 | if (this._mainIcon.get_child()) { |
398 | 414 | let child = this._mainIcon.get_child() |
430 | 446 | } |
431 | 447 | |
432 | 448 | this._mainIcon.set_child(newIcon) |
433 | }, | |
434 | ||
435 | _updateOverlayIcon: function() { | |
449 | } | |
450 | ||
451 | _updateOverlayIcon() { | |
436 | 452 | // remove old icon |
437 | 453 | if (this._overlayIcon.get_child()) { |
438 | 454 | let child = this._overlayIcon.get_child() |
462 | 478 | newIcon = this._createIconFromPixmap(iconSize, pixmap) |
463 | 479 | |
464 | 480 | this._overlayIcon.set_child(newIcon) |
465 | }, | |
466 | ||
467 | _handleScrollEvent: function(actor, event) { | |
481 | } | |
482 | ||
483 | _handleScrollEvent(actor, event) { | |
468 | 484 | if (actor != this) |
469 | 485 | return Clutter.EVENT_PROPAGATE |
470 | 486 | |
485 | 501 | } |
486 | 502 | |
487 | 503 | return Clutter.EVENT_STOP |
488 | }, | |
504 | } | |
489 | 505 | |
490 | 506 | // called when the icon theme changes |
491 | _invalidateIcon: function() { | |
507 | _invalidateIcon() { | |
492 | 508 | this._iconCache.clear() |
493 | 509 | |
494 | 510 | this._updateIcon() |
495 | 511 | this._updateOverlayIcon() |
496 | }, | |
497 | ||
498 | destroy: function() { | |
499 | this._iconCache.destroy() | |
500 | ||
501 | this.parent() | |
502 | } | |
503 | }) | |
512 | } | |
513 | }); |
17 | 17 | const Gio = imports.gi.Gio |
18 | 18 | const GLib = imports.gi.GLib |
19 | 19 | const GdkPixbuf = imports.gi.GdkPixbuf |
20 | const Lang = imports.lang | |
21 | 20 | const PopupMenu = imports.ui.popupMenu |
22 | 21 | const Signals = imports.signals |
23 | 22 | const St = imports.gi.St |
35 | 34 | /** |
36 | 35 | * Saves menu property values and handles type checking and defaults |
37 | 36 | */ |
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) { | |
42 | 40 | this._props = {} |
43 | 41 | |
44 | 42 | if (initial_properties) { |
46 | 44 | this.set(i, initial_properties[i]) |
47 | 45 | } |
48 | 46 | } |
49 | }, | |
50 | ||
51 | set: function(name, value) { | |
47 | } | |
48 | ||
49 | set(name, value) { | |
52 | 50 | if (name in PropertyStore.MandatedTypes && value && !value.is_of_type(PropertyStore.MandatedTypes[name])) |
53 | 51 | Util.Logger.warn("Cannot set property "+name+": type mismatch!") |
54 | 52 | else if (value) |
55 | 53 | this._props[name] = value |
56 | 54 | else |
57 | 55 | delete this._props[name] |
58 | }, | |
59 | ||
60 | get: function(name) { | |
56 | } | |
57 | ||
58 | get(name) { | |
61 | 59 | if (name in this._props) |
62 | 60 | return this._props[name] |
63 | 61 | else if (name in PropertyStore.DefaultValues) |
65 | 63 | else |
66 | 64 | return null |
67 | 65 | } |
68 | }) | |
66 | }; | |
69 | 67 | |
70 | 68 | // we list all the properties we know and use here, so we won' have to deal with unexpected type mismatches |
71 | 69 | PropertyStore.MandatedTypes = { |
91 | 89 | /** |
92 | 90 | * Represents a single menu item |
93 | 91 | */ |
94 | const DbusMenuItem = new Lang.Class({ | |
95 | Name: 'DbusMenuItem', | |
92 | var DbusMenuItem = class AppIndicators_DbusMenuItem { | |
96 | 93 | |
97 | 94 | // will steal the properties object |
98 | _init: function(client, id, properties, children_ids) { | |
95 | constructor(client, id, properties, children_ids) { | |
99 | 96 | this._client = client |
100 | 97 | this._id = id |
101 | 98 | this._propStore = new PropertyStore(properties) |
102 | 99 | this._children_ids = children_ids |
103 | }, | |
104 | ||
105 | property_get: function(prop_name) { | |
100 | } | |
101 | ||
102 | property_get(prop_name) { | |
106 | 103 | let prop = this.property_get_variant(prop_name) |
107 | 104 | return prop ? prop.get_string()[0] : null |
108 | }, | |
109 | ||
110 | property_get_variant: function(prop_name) { | |
105 | } | |
106 | ||
107 | property_get_variant(prop_name) { | |
111 | 108 | return this._propStore.get(prop_name) |
112 | }, | |
113 | ||
114 | property_get_bool: function(prop_name) { | |
109 | } | |
110 | ||
111 | property_get_bool(prop_name) { | |
115 | 112 | let prop = this.property_get_variant(prop_name) |
116 | 113 | return prop ? prop.get_boolean() : false |
117 | }, | |
118 | ||
119 | property_get_int: function(prop_name) { | |
114 | } | |
115 | ||
116 | property_get_int(prop_name) { | |
120 | 117 | let prop = this.property_get_variant(prop_name) |
121 | 118 | return prop ? prop.get_int32() : 0 |
122 | }, | |
123 | ||
124 | property_set: function(prop, value) { | |
119 | } | |
120 | ||
121 | property_set(prop, value) { | |
125 | 122 | this._propStore.set(prop, value) |
126 | 123 | |
127 | 124 | this.emit('property-changed', prop, this.property_get_variant(prop)) |
128 | }, | |
129 | ||
130 | get_children_ids: function() { | |
125 | } | |
126 | ||
127 | get_children_ids() { | |
131 | 128 | return this._children_ids.concat() // clone it! |
132 | }, | |
133 | ||
134 | add_child: function(pos, child_id) { | |
129 | } | |
130 | ||
131 | add_child(pos, child_id) { | |
135 | 132 | this._children_ids.splice(pos, 0, child_id) |
136 | 133 | 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) { | |
140 | 137 | // find it |
141 | 138 | let pos = -1 |
142 | 139 | for (let i = 0; i < this._children_ids.length; ++i) { |
152 | 149 | this._children_ids.splice(pos, 1) |
153 | 150 | this.emit('child-removed', this._client.get_item(child_id)) |
154 | 151 | } |
155 | }, | |
156 | ||
157 | move_child: function(child_id, newpos) { | |
152 | } | |
153 | ||
154 | move_child(child_id, newpos) { | |
158 | 155 | // find the old position |
159 | 156 | let oldpos = -1 |
160 | 157 | for (let i = 0; i < this._children_ids.length; ++i) { |
174 | 171 | this._children_ids.splice(newpos, 0, child_id) |
175 | 172 | this.emit('child-moved', oldpos, newpos, this._client.get_item(child_id)) |
176 | 173 | } |
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) => { | |
181 | 178 | return this._client.get_item(el) |
182 | 179 | }, this) |
183 | }, | |
184 | ||
185 | handle_event: function(event, data, timestamp) { | |
180 | } | |
181 | ||
182 | handle_event(event, data, timestamp) { | |
186 | 183 | if (!data) |
187 | 184 | data = GLib.Variant.new_int32(0) |
188 | 185 | |
189 | 186 | this._client.send_event(this._id, event, data, timestamp) |
190 | }, | |
191 | ||
192 | get_id: function() { | |
187 | } | |
188 | ||
189 | get_id() { | |
193 | 190 | return this._id |
194 | }, | |
195 | ||
196 | send_about_to_show: function() { | |
191 | } | |
192 | ||
193 | send_about_to_show() { | |
197 | 194 | this._client.send_about_to_show(this._id) |
198 | 195 | } |
199 | }) | |
196 | } | |
200 | 197 | Signals.addSignalMethods(DbusMenuItem.prototype) |
201 | 198 | |
202 | 199 | |
205 | 202 | /** |
206 | 203 | * The client does the heavy lifting of actually reading layouts and distributing events |
207 | 204 | */ |
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) { | |
212 | 208 | this._proxy = new BusClientProxy(Gio.DBus.session, busName, busPath, this._clientReady.bind(this)) |
213 | 209 | this._items = { 0: new DbusMenuItem(this, 0, { 'children-display': GLib.Variant.new_string('submenu') }, []) } |
214 | 210 | |
219 | 215 | |
220 | 216 | // property requests are queued |
221 | 217 | this._propertiesRequestedFor = [ /* ids */ ] |
222 | }, | |
223 | ||
224 | get_root: function() { | |
218 | } | |
219 | ||
220 | get_root() { | |
225 | 221 | return this._items[0] |
226 | }, | |
227 | ||
228 | _requestLayoutUpdate: function() { | |
222 | } | |
223 | ||
224 | _requestLayoutUpdate() { | |
229 | 225 | if (this._flagLayoutUpdateInProgress) |
230 | 226 | this._flagLayoutUpdateRequired = true |
231 | 227 | else |
232 | 228 | this._beginLayoutUpdate() |
233 | }, | |
234 | ||
235 | _requestProperties: function(id) { | |
229 | } | |
230 | ||
231 | _requestProperties(id) { | |
236 | 232 | // if we don't have any requests queued, we'll need to add one |
237 | 233 | if (this._propertiesRequestedFor.length < 1) |
238 | 234 | GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, this._beginRequestProperties.bind(this)) |
239 | 235 | |
240 | if (this._propertiesRequestedFor.filter(function(e) { return e === id }).length == 0) | |
236 | if (this._propertiesRequestedFor.filter((e) => { return e === id }).length == 0) | |
241 | 237 | this._propertiesRequestedFor.push(id) |
242 | 238 | |
243 | }, | |
244 | ||
245 | _beginRequestProperties: function() { | |
239 | } | |
240 | ||
241 | _beginRequestProperties() { | |
246 | 242 | this._proxy.GetGroupPropertiesRemote(this._propertiesRequestedFor, [], this._endRequestProperties.bind(this)) |
247 | 243 | |
248 | 244 | this._propertiesRequestedFor = [] |
249 | 245 | |
250 | 246 | return false |
251 | }, | |
252 | ||
253 | _endRequestProperties: function(result, error) { | |
247 | } | |
248 | ||
249 | _endRequestProperties(result, error) { | |
254 | 250 | if (error) { |
255 | 251 | Util.Logger.warn("Could not retrieve properties: "+error) |
256 | 252 | return |
257 | 253 | } |
258 | 254 | |
259 | 255 | // 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]) => { | |
261 | 257 | if (!(id in this._items)) |
262 | 258 | return |
263 | 259 | |
264 | 260 | for (let prop in properties) |
265 | 261 | this._items[id].property_set(prop, properties[prop]) |
266 | 262 | }, this) |
267 | }, | |
263 | } | |
268 | 264 | |
269 | 265 | // Traverses the list of cached menu items and removes everyone that is not in the list |
270 | 266 | // so we don't keep alive unused items |
271 | _gcItems: function() { | |
267 | _gcItems() { | |
272 | 268 | let tag = new Date().getTime() |
273 | 269 | |
274 | 270 | let toTraverse = [ 0 ] |
281 | 277 | for (let i in this._items) |
282 | 278 | if (this._items[i]._dbusClientGcTag != tag) |
283 | 279 | delete this._items[i] |
284 | }, | |
280 | } | |
285 | 281 | |
286 | 282 | // the original implementation will only request partial layouts if somehow possible |
287 | 283 | // we try to save us from multiple kinds of race conditions by always requesting a full layout |
288 | _beginLayoutUpdate: function() { | |
284 | _beginLayoutUpdate() { | |
289 | 285 | // we only read the type property, because if the type changes after reading all properties, |
290 | 286 | // the view would have to replace the item completely which we try to avoid |
291 | 287 | this._proxy.GetLayoutRemote(0, -1, [ 'type', 'children-display' ], this._endLayoutUpdate.bind(this)) |
292 | 288 | |
293 | 289 | this._flagLayoutUpdateRequired = false |
294 | 290 | this._flagLayoutUpdateInProgress = true |
295 | }, | |
296 | ||
297 | _endLayoutUpdate: function(result, error) { | |
291 | } | |
292 | ||
293 | _endLayoutUpdate(result, error) { | |
298 | 294 | 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) | |
300 | 296 | return |
301 | 297 | } |
302 | 298 | |
308 | 304 | this._beginLayoutUpdate() |
309 | 305 | else |
310 | 306 | this._flagLayoutUpdateInProgress = false |
311 | }, | |
312 | ||
313 | _doLayoutUpdate: function(item) { | |
307 | } | |
308 | ||
309 | _doLayoutUpdate(item) { | |
314 | 310 | let [ id, properties, children ] = item |
315 | 311 | |
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] }) | |
318 | 314 | |
319 | 315 | // make sure all our children exist |
320 | 316 | children_unpacked.forEach(this._doLayoutUpdate, this) |
348 | 344 | } |
349 | 345 | |
350 | 346 | // 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) | |
352 | 348 | } else { |
353 | 349 | // we don't, so let's create us |
354 | 350 | this._items[id] = new DbusMenuItem(this, id, properties, children_ids) |
356 | 352 | } |
357 | 353 | |
358 | 354 | return id |
359 | }, | |
360 | ||
361 | _clientReady: function(result, error) { | |
355 | } | |
356 | ||
357 | _clientReady(result, error) { | |
362 | 358 | if (error) { |
363 | 359 | Util.Logger.warn("Could not initialize menu proxy: "+error) |
364 | 360 | //FIXME: show message to the user? |
369 | 365 | // listen for updated layouts and properties |
370 | 366 | this._proxy.connectSignal("LayoutUpdated", this._onLayoutUpdated.bind(this)) |
371 | 367 | this._proxy.connectSignal("ItemsPropertiesUpdated", this._onPropertiesUpdated.bind(this)) |
372 | }, | |
373 | ||
374 | get_item: function(id) { | |
368 | } | |
369 | ||
370 | get_item(id) { | |
375 | 371 | if (id in this._items) |
376 | 372 | return this._items[id] |
377 | 373 | |
378 | 374 | Util.Logger.warn("trying to retrieve item for non-existing id "+id+" !?") |
379 | 375 | return null |
380 | }, | |
376 | } | |
381 | 377 | |
382 | 378 | // 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) => { | |
385 | 381 | if (error) |
386 | 382 | Util.Logger.warn("while calling AboutToShow: "+error) |
387 | 383 | else if (result && result[0]) |
388 | 384 | 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) { | |
393 | 389 | if (!this._proxy) |
394 | 390 | return |
395 | 391 | |
396 | 392 | this._proxy.EventRemote(id, event, params, timestamp, function(result, error) { /* we don't care */ }) |
397 | }, | |
398 | ||
399 | _onLayoutUpdated: function() { | |
393 | } | |
394 | ||
395 | _onLayoutUpdated() { | |
400 | 396 | 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]) => { | |
405 | 401 | if (!(id in this._items)) |
406 | 402 | return |
407 | 403 | |
408 | 404 | for (let prop in props) |
409 | 405 | this._items[id].property_set(prop, props[prop]) |
410 | 406 | }, this) |
411 | removed.forEach(function([id, propNames]) { | |
407 | removed.forEach(([id, propNames]) => { | |
412 | 408 | if (!(id in this._items)) |
413 | 409 | return |
414 | 410 | |
415 | propNames.forEach(function(propName) { | |
411 | propNames.forEach((propName) => { | |
416 | 412 | this._items[id].property_set(propName, null) |
417 | 413 | }, this) |
418 | 414 | }, this) |
419 | }, | |
420 | ||
421 | destroy: function() { | |
415 | } | |
416 | ||
417 | destroy() { | |
422 | 418 | this.emit('destroy') |
423 | 419 | |
424 | 420 | Signals._disconnectAll.apply(this._proxy) |
425 | 421 | |
426 | 422 | this._proxy = null |
427 | 423 | } |
428 | }) | |
424 | } | |
429 | 425 | Signals.addSignalMethods(DBusClient.prototype) |
430 | 426 | |
431 | 427 | ////////////////////////////////////////////////////////////////////////// |
492 | 488 | return shellItem |
493 | 489 | }, |
494 | 490 | |
495 | _onOpenStateChanged: function(menu, open) { | |
491 | _onOpenStateChanged(menu, open) { | |
496 | 492 | if (open) { |
497 | 493 | if (NEED_NESTED_SUBMENU_FIX) { |
498 | 494 | // close our own submenus |
519 | 515 | } |
520 | 516 | }, |
521 | 517 | |
522 | _onActivate: function() { | |
518 | _onActivate() { | |
523 | 519 | this._dbusItem.handle_event("clicked", GLib.Variant.new("i", 0), 0) |
524 | 520 | }, |
525 | 521 | |
526 | _onPropertyChanged: function(dbusItem, prop, value) { | |
522 | _onPropertyChanged(dbusItem, prop, value) { | |
527 | 523 | if (prop == "toggle-type" || prop == "toggle-state") |
528 | 524 | MenuItemFactory._updateOrnament.call(this) |
529 | 525 | else if (prop == "label") |
540 | 536 | // Util.Logger.debug("Unhandled property change: "+prop) |
541 | 537 | }, |
542 | 538 | |
543 | _onChildAdded: function(dbusItem, child, position) { | |
539 | _onChildAdded(dbusItem, child, position) { | |
544 | 540 | if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { |
545 | 541 | Util.Logger.warn("Tried to add a child to non-submenu item. Better recreate it as whole") |
546 | 542 | MenuItemFactory._replaceSelf.call(this) |
549 | 545 | } |
550 | 546 | }, |
551 | 547 | |
552 | _onChildRemoved: function(dbusItem, child) { | |
548 | _onChildRemoved(dbusItem, child) { | |
553 | 549 | if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { |
554 | 550 | Util.Logger.warn("Tried to remove a child from non-submenu item. Better recreate it as whole") |
555 | 551 | MenuItemFactory._replaceSelf.call(this) |
556 | 552 | } else { |
557 | 553 | // find it! |
558 | this.menu._getMenuItems().forEach(function(item) { | |
554 | this.menu._getMenuItems().forEach((item) => { | |
559 | 555 | if (item._dbusItem == child) |
560 | 556 | item.destroy() |
561 | 557 | }) |
562 | 558 | } |
563 | 559 | }, |
564 | 560 | |
565 | _onChildMoved: function(dbusItem, child, oldpos, newpos) { | |
561 | _onChildMoved(dbusItem, child, oldpos, newpos) { | |
566 | 562 | if (!(this instanceof PopupMenu.PopupSubMenuMenuItem)) { |
567 | 563 | Util.Logger.warn("Tried to move a child in non-submenu item. Better recreate it as whole") |
568 | 564 | MenuItemFactory._replaceSelf.call(this) |
571 | 567 | } |
572 | 568 | }, |
573 | 569 | |
574 | _updateLabel: function() { | |
570 | _updateLabel() { | |
575 | 571 | let label = this._dbusItem.property_get("label").replace(/_([^_])/, "$1") |
576 | 572 | |
577 | 573 | if (this.label) // especially on GS3.8, the separator item might not even have a hidden label |
578 | 574 | this.label.set_text(label) |
579 | 575 | }, |
580 | 576 | |
581 | _updateOrnament: function() { | |
577 | _updateOrnament() { | |
582 | 578 | if (!this.setOrnament) return // separators and alike might not have gotten the polyfill |
583 | 579 | |
584 | 580 | if (this._dbusItem.property_get("toggle-type") == "checkmark" && this._dbusItem.property_get_int("toggle-state")) |
589 | 585 | this.setOrnament(PopupMenu.Ornament.NONE) |
590 | 586 | }, |
591 | 587 | |
592 | _updateImage: function() { | |
588 | _updateImage() { | |
593 | 589 | if (!this._icon) return // might be missing on submenus / separators |
594 | 590 | |
595 | 591 | let iconName = this._dbusItem.property_get("icon-name") |
600 | 596 | this._icon.gicon = GdkPixbuf.Pixbuf.new_from_stream(Gio.MemoryInputStream.new_from_bytes(iconData.get_data_as_bytes()), null) |
601 | 597 | }, |
602 | 598 | |
603 | _updateVisible: function() { | |
599 | _updateVisible() { | |
604 | 600 | this.actor.visible = this._dbusItem.property_get_bool("visible") |
605 | 601 | }, |
606 | 602 | |
607 | _updateSensitive: function() { | |
603 | _updateSensitive() { | |
608 | 604 | this.setSensitive(this._dbusItem.property_get_bool("enabled")) |
609 | 605 | }, |
610 | 606 | |
611 | _replaceSelf: function(newSelf) { | |
607 | _replaceSelf(newSelf) { | |
612 | 608 | // create our new self if needed |
613 | 609 | if (!newSelf) |
614 | 610 | newSelf = MenuItemFactory.createItem(this._dbusClient, this._dbusItem) |
637 | 633 | * Utility functions not necessarily belonging into the item factory |
638 | 634 | */ |
639 | 635 | const MenuUtils = { |
640 | moveItemInMenu: function(menu, dbusItem, newpos) { | |
636 | moveItemInMenu(menu, dbusItem, newpos) { | |
641 | 637 | //HACK: we're really getting into the internals of the PopupMenu implementation |
642 | 638 | |
643 | 639 | // First, find our wrapper. Children tend to lie. We do not trust the old positioning. |
666 | 662 | * |
667 | 663 | * Something like a mini-god-object |
668 | 664 | */ |
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) { | |
674 | 668 | this._busName = busName |
675 | 669 | this._busPath = path |
676 | 670 | this._client = new DBusClient(busName, path) |
677 | 671 | this._rootMenu = null // the shell menu |
678 | 672 | this._rootItem = null // the DbusMenuItem for the root |
679 | }, | |
673 | } | |
680 | 674 | |
681 | 675 | // this will attach the client to an already existing menu that will be used as the root menu. |
682 | 676 | // it will also connect the client to be automatically destroyed when the menu dies. |
683 | attachToMenu: function(menu) { | |
677 | attachToMenu(menu) { | |
684 | 678 | this._rootMenu = menu |
685 | 679 | this._rootItem = this._client.get_root() |
686 | 680 | |
702 | 696 | this._rootItem.send_about_to_show() |
703 | 697 | |
704 | 698 | // fill the menu for the first time |
705 | this._rootItem.get_children().forEach(function(child) { | |
699 | this._rootItem.get_children().forEach((child) => { | |
706 | 700 | this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)) |
707 | 701 | }, this) |
708 | }, | |
709 | ||
710 | _setOpenedSubmenu: function(submenu) { | |
702 | } | |
703 | ||
704 | _setOpenedSubmenu(submenu) { | |
711 | 705 | if (!submenu) |
712 | 706 | return |
713 | 707 | |
721 | 715 | this._openedSubMenu.close(true) |
722 | 716 | |
723 | 717 | this._openedSubMenu = submenu |
724 | }, | |
725 | ||
726 | _onRootChildAdded: function(dbusItem, child, position) { | |
718 | } | |
719 | ||
720 | _onRootChildAdded(dbusItem, child, position) { | |
727 | 721 | this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position) |
728 | }, | |
729 | ||
730 | _onRootChildRemoved: function(dbusItem, child) { | |
722 | } | |
723 | ||
724 | _onRootChildRemoved(dbusItem, child) { | |
731 | 725 | // children like to play hide and seek |
732 | 726 | // but we know how to find it for sure! |
733 | this._rootMenu._getMenuItems().forEach(function(item) { | |
727 | this._rootMenu._getMenuItems().forEach((item) => { | |
734 | 728 | if (item._dbusItem == child) |
735 | 729 | item.destroy() |
736 | 730 | }) |
737 | }, | |
738 | ||
739 | _onRootChildMoved: function(dbusItem, child, oldpos, newpos) { | |
731 | } | |
732 | ||
733 | _onRootChildMoved(dbusItem, child, oldpos, newpos) { | |
740 | 734 | MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos) |
741 | }, | |
742 | ||
743 | _onMenuOpened: function(menu, state) { | |
735 | } | |
736 | ||
737 | _onMenuOpened(menu, state) { | |
744 | 738 | if (!this._rootItem) return |
745 | 739 | |
746 | 740 | if (state) { |
752 | 746 | } else { |
753 | 747 | this._rootItem.handle_event("closed", null, 0) |
754 | 748 | } |
755 | }, | |
756 | ||
757 | destroy: function() { | |
749 | } | |
750 | ||
751 | destroy() { | |
758 | 752 | this.emit('destroy') |
759 | 753 | |
760 | 754 | if (this._client) |
764 | 758 | this._rootItem = null |
765 | 759 | this._rootMenu = null |
766 | 760 | } |
767 | }) | |
761 | } | |
768 | 762 | Signals.addSignalMethods(Client.prototype) |
22 | 22 | |
23 | 23 | let statusNotifierWatcher = null; |
24 | 24 | let isEnabled = false; |
25 | let watchDog = null; | |
25 | 26 | |
26 | 27 | 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; | |
29 | 30 | |
30 | 31 | //HACK: we want to leave the watchdog alive when disabling the extension, |
31 | 32 | // but if we are being reloaded, we destroy it since it could be considered |
33 | 34 | if (typeof global['--appindicator-extension-on-reload'] == 'function') |
34 | 35 | global['--appindicator-extension-on-reload']() |
35 | 36 | |
36 | global['--appindicator-extension-on-reload'] = function() { | |
37 | global['--appindicator-extension-on-reload'] = () => { | |
37 | 38 | Util.Logger.debug("Reload detected, destroying old watchdog") |
38 | NameWatchdog.destroy() | |
39 | watchDog.destroy(); | |
39 | 40 | } |
40 | 41 | } |
41 | 42 | |
45 | 46 | // monitor the bus manually to find out when the name vanished so we can reclaim it again. |
46 | 47 | function maybe_enable_after_name_available() { |
47 | 48 | // 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) | |
49 | 50 | statusNotifierWatcher = new StatusNotifierWatcher.StatusNotifierWatcher(); |
50 | 51 | } |
51 | 52 | |
65 | 66 | /** |
66 | 67 | * NameWatchdog will monitor the ork.kde.StatusNotifierWatcher bus name for us |
67 | 68 | */ |
68 | const NameWatchdog = { | |
69 | onAppeared: null, | |
70 | onVanished: null, | |
69 | var NameWatchdog = class AppIndicators_NameWatchdog { | |
71 | 70 | |
72 | _watcher_id: null, | |
71 | constructor() { | |
72 | this.onAppeared = null; | |
73 | this.onVanished = null; | |
73 | 74 | |
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; | |
75 | 77 | |
76 | init: function() { | |
77 | 78 | this._watcher_id = Gio.DBus.session.watch_name("org.kde.StatusNotifierWatcher", 0, |
78 | 79 | this._appeared_handler.bind(this), this._vanished_handler.bind(this)); |
79 | }, | |
80 | } | |
80 | 81 | |
81 | destroy: function() { | |
82 | destroy() { | |
82 | 83 | Gio.DBus.session.unwatch_name(this._watcher_id); |
83 | }, | |
84 | } | |
84 | 85 | |
85 | _appeared_handler: function() { | |
86 | _appeared_handler() { | |
86 | 87 | this.isPresent = true; |
87 | 88 | if (this.onAppeared) this.onAppeared(); |
88 | }, | |
89 | } | |
89 | 90 | |
90 | _vanished_handler: function() { | |
91 | _vanished_handler() { | |
91 | 92 | this.isPresent = false; |
92 | 93 | if (this.onVanished) this.onVanished(); |
93 | 94 | } |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | 15 | |
16 | 16 | const GLib = imports.gi.GLib |
17 | const GObject = imports.gi.GObject | |
17 | 18 | |
18 | const Lang = imports.lang | |
19 | 19 | const Mainloop = imports.mainloop |
20 | 20 | |
21 | 21 | const Util = imports.misc.extensionUtils.getCurrentExtension().imports.util; |
26 | 26 | // If the lifetime of an icon is over, the cache will destroy the icon. (!) |
27 | 27 | // The presence of an inUse property set to true on the icon will extend the lifetime. |
28 | 28 | |
29 | const LIFETIME_TIMESPAN = 5000; // milli-seconds | |
30 | const GC_INTERVAL = 10; // seconds | |
31 | ||
29 | 32 | // 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() { | |
37 | 35 | this._cache = {}; |
38 | 36 | this._lifetime = {}; //we don't want to attach lifetime to the object |
39 | this._gc(); | |
40 | }, | |
37 | this._destroyNotify = {}; | |
38 | } | |
41 | 39 | |
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) { | |
46 | 45 | 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 | ||
49 | 60 | return o; |
50 | }, | |
61 | } | |
51 | 62 | |
52 | _remove: function(id) { | |
63 | _remove(id) { | |
64 | if (!(id in this._cache)) | |
65 | return; | |
66 | ||
53 | 67 | //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 | ||
55 | 77 | delete this._cache[id]; |
56 | 78 | delete this._lifetime[id]; |
57 | }, | |
79 | delete this._destroyNotify[id]; | |
58 | 80 | |
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) { | |
60 | 90 | this._remove(id); |
61 | }, | |
91 | } | |
62 | 92 | |
63 | 93 | // removes everything from the cache |
64 | clear: function() { | |
94 | clear() { | |
65 | 95 | for (let id in this._cache) |
66 | 96 | this._remove(id) |
67 | }, | |
97 | ||
98 | this._checkGC(); | |
99 | } | |
68 | 100 | |
69 | 101 | // returns an object from the cache, or null if it can't be found. |
70 | get: function(id) { | |
102 | get(id) { | |
71 | 103 | if (id in this._cache) { |
72 | 104 | //Util.Logger.debug('IconCache: retrieving '+id); |
73 | this._lifetime[id] = new Date().getTime() + this.LIFETIME_TIMESPAN; //renew lifetime | |
105 | this._renewLifetime(id); | |
74 | 106 | return this._cache[id]; |
75 | 107 | } |
76 | else return null; | |
77 | }, | |
78 | 108 | |
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() { | |
80 | 127 | var time = new Date().getTime(); |
81 | 128 | for (var id in this._cache) { |
82 | 129 | if (this._cache[id].inUse) { |
83 | //Util.Logger.debug ("IconCache: " + id + " is in use."); | |
130 | //Util.Logger.debug("IconCache: " + id + " is in use."); | |
84 | 131 | continue; |
85 | 132 | } else if (this._lifetime[id] < time) { |
86 | 133 | this._remove(id); |
88 | 135 | //Util.Logger.debug("IconCache: " + id + " survived this round."); |
89 | 136 | } |
90 | 137 | } |
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 | }, | |
94 | 138 | |
95 | destroy: function() { | |
96 | this._stopGc = true; | |
139 | return true; | |
140 | } | |
141 | ||
142 | destroy() { | |
97 | 143 | this.clear(); |
98 | 144 | } |
99 | }); | |
145 | }; |
8 | 8 | const AppIndicator = imports.gi.AppIndicator3; |
9 | 9 | const GLib = imports.gi.GLib; |
10 | 10 | |
11 | (function() { | |
11 | (() => { | |
12 | 12 | |
13 | 13 | var app = new Gtk.Application({ |
14 | 14 | application_id: null |
16 | 16 | |
17 | 17 | var window = null; |
18 | 18 | |
19 | app.connect("activate", function(){ | |
19 | app.connect("activate", () => { | |
20 | 20 | window.present(); |
21 | 21 | }); |
22 | 22 | |
23 | app.connect("startup", function() { | |
23 | app.connect("startup", () => { | |
24 | 24 | window = new Gtk.ApplicationWindow({ |
25 | 25 | title: "test", |
26 | 26 | application: app |
96 | 96 | menu.append(item); |
97 | 97 | |
98 | 98 | item = Gtk.MenuItem.new_with_label("Set Label"); |
99 | item.connect('activate', function() { | |
99 | item.connect('activate', () => { | |
100 | 100 | indicator.set_label(''+new Date().getSeconds(), 'Blub'); |
101 | 101 | }); |
102 | 102 | menu.append(item); |
103 | 103 | |
104 | 104 | item = Gtk.MenuItem.new_with_label("Unset Label"); |
105 | item.connect('activate', function() { | |
105 | item.connect('activate', () => { | |
106 | 106 | indicator.set_label('', ''); |
107 | 107 | }) |
108 | 108 | menu.append(item); |
111 | 111 | menu.append(item); |
112 | 112 | |
113 | 113 | item = Gtk.MenuItem.new_with_label("Hide for some time"); |
114 | item.connect('activate', function() { | |
114 | item.connect('activate', () => { | |
115 | 115 | indicator.set_status(AppIndicator.IndicatorStatus.PASSIVE); |
116 | GLib.timeout_add(0, 5000, function() { | |
116 | GLib.timeout_add(0, 5000, () => { | |
117 | 117 | indicator.set_status(AppIndicator.IndicatorStatus.ACTIVE); |
118 | 118 | return false; |
119 | 119 | }); |
121 | 121 | menu.append(item); |
122 | 122 | |
123 | 123 | 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, () => { | |
126 | 126 | app.quit(); |
127 | 127 | return false; |
128 | 128 | }); |
13 | 13 | // along with this program; if not, write to the Free Software |
14 | 14 | // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
15 | 15 | const Clutter = imports.gi.Clutter; |
16 | const GObject = imports.gi.GObject; | |
16 | 17 | const St = imports.gi.St; |
17 | 18 | |
18 | const Lang = imports.lang; | |
19 | 19 | const Main = imports.ui.main; |
20 | 20 | const Panel = imports.ui.panel; |
21 | 21 | const PanelMenu = imports.ui.panelMenu; |
30 | 30 | /* |
31 | 31 | * IndicatorStatusIcon implements an icon in the system status area |
32 | 32 | */ |
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); | |
40 | 37 | this._indicator = indicator; |
41 | 38 | |
42 | 39 | this._iconBox = new AppIndicator.IconActor(indicator, Panel.PANEL_ICON_SIZE + 6); |
51 | 48 | Util.connectSmart(this._indicator, 'label', this, '_updateLabel') |
52 | 49 | Util.connectSmart(this._indicator, 'status', this, '_updateStatus') |
53 | 50 | |
51 | this.connect('destroy', () => { | |
52 | if (this._menuClient) { | |
53 | this._menuClient.destroy(); | |
54 | this._menuClient = null; | |
55 | } | |
56 | }) | |
57 | ||
54 | 58 | if (this._indicator.isReady) |
55 | 59 | this._display() |
56 | }, | |
60 | } | |
57 | 61 | |
58 | _updateLabel: function() { | |
62 | _updateLabel() { | |
59 | 63 | var label = this._indicator.label; |
60 | 64 | if (label) { |
61 | 65 | if (!this._label || !this._labelBin) { |
75 | 79 | delete this._label; |
76 | 80 | } |
77 | 81 | } |
78 | }, | |
82 | } | |
79 | 83 | |
80 | _updateStatus: function() { | |
84 | _updateStatus() { | |
81 | 85 | if (this._indicator.status != AppIndicator.SNIStatus.PASSIVE) |
82 | 86 | this.actor.show() |
83 | 87 | else |
84 | 88 | this.actor.hide() |
85 | }, | |
89 | } | |
86 | 90 | |
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() { | |
101 | 92 | this._updateLabel() |
102 | 93 | this._updateStatus() |
103 | 94 | |
107 | 98 | } |
108 | 99 | |
109 | 100 | Main.panel.addToStatusArea("appindicator-"+this._indicator.uniqueId, this, 1, 'right') |
110 | }, | |
101 | } | |
111 | 102 | |
112 | _boxClicked: function(actor, event) { | |
103 | _boxClicked(actor, event) { | |
113 | 104 | // if middle mouse button clicked send SecondaryActivate dbus event and do not show appindicator menu |
114 | 105 | if (event.get_button() == 2) { |
115 | 106 | Main.panel.menuManager._closeMenu(true, Main.panel.menuManager.activeMenu); |
34 | 34 | // Otherwise, it will try to check `instanceof XML` and fail miserably because there |
35 | 35 | // is no `XML` on very recent SpiderMonkey releases (or, if SpiderMonkey is old enough, |
36 | 36 | // will spit out a TypeError soon). |
37 | if (contents instanceof Uint8Array) | |
38 | contents = imports.byteArray.toString(contents); | |
37 | 39 | return "<node>" + contents + "</node>" |
38 | 40 | } else { |
39 | 41 | throw new Error("AppIndicatorSupport: Could not load file: "+filename) |
4 | 4 | "uuid": "ubuntu-appindicators@ubuntu.com", |
5 | 5 | "url": "https://github.com/ubuntu/gnome-shell-extension-appindicator", |
6 | 6 | "shell-version": [ |
7 | "3.24", | |
8 | "3.26" | |
7 | "3.30", | |
8 | "3.31", | |
9 | "3.32" | |
9 | 10 | ] |
10 | 11 | } |
17 | 17 | const GLib = imports.gi.GLib |
18 | 18 | const Gtk = imports.gi.Gtk |
19 | 19 | |
20 | const Lang = imports.lang | |
21 | 20 | const Mainloop = imports.mainloop |
22 | 21 | const ShellConfig = imports.misc.config |
23 | 22 | const Signals = imports.signals |
42 | 41 | /* |
43 | 42 | * The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object |
44 | 43 | */ |
45 | var StatusNotifierWatcher = new Lang.Class({ | |
46 | Name: 'StatusNotifierWatcher', | |
47 | ||
48 | _init: function() { | |
44 | var StatusNotifierWatcher = class AppIndicators_StatusNotifierWatcher { | |
45 | ||
46 | constructor() { | |
49 | 47 | this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this); |
50 | 48 | this._dbusImpl.export(Gio.DBus.session, WATCHER_OBJECT); |
51 | 49 | this._cancellable = new Gio.Cancellable; |
52 | 50 | this._everAcquiredName = false; |
53 | 51 | 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)); | |
57 | 55 | this._items = { }; |
58 | 56 | this._nameWatcher = { }; |
59 | 57 | |
60 | 58 | this._seekStatusNotifierItems(); |
61 | }, | |
62 | ||
63 | _acquiredName: function() { | |
59 | } | |
60 | ||
61 | _acquiredName() { | |
64 | 62 | this._everAcquiredName = true; |
65 | }, | |
66 | ||
67 | _lostName: function() { | |
63 | } | |
64 | ||
65 | _lostName() { | |
68 | 66 | if (this._everAcquiredName) |
69 | 67 | Util.Logger.debug('Lost name' + WATCHER_BUS_NAME); |
70 | 68 | else |
71 | 69 | Util.Logger.warn('Failed to acquire ' + WATCHER_BUS_NAME); |
72 | }, | |
70 | } | |
73 | 71 | |
74 | 72 | |
75 | 73 | // create a unique index for the _items dictionary |
76 | _getItemId: function(bus_name, obj_path) { | |
74 | _getItemId(bus_name, obj_path) { | |
77 | 75 | return bus_name + obj_path; |
78 | }, | |
79 | ||
80 | _registerItem: function(service, bus_name, obj_path) { | |
76 | } | |
77 | ||
78 | _registerItem(service, bus_name, obj_path) { | |
81 | 79 | let id = this._getItemId(bus_name, obj_path); |
82 | 80 | |
83 | 81 | if (this._items[id]) { |
98 | 96 | this._itemVanished.bind(this)); |
99 | 97 | |
100 | 98 | 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) { | |
104 | 102 | let id = this._getItemId(bus_name, obj_path); |
105 | 103 | |
106 | 104 | if (this._items[id]) { |
110 | 108 | } |
111 | 109 | |
112 | 110 | this._registerItem(service, bus_name, obj_path) |
113 | }, | |
114 | ||
115 | _seekStatusNotifierItems: function() { | |
111 | } | |
112 | ||
113 | _seekStatusNotifierItems() { | |
116 | 114 | // Some indicators (*coff*, dropbox, *coff*) do not re-register again |
117 | 115 | // when the plugin is enabled/disabled, thus we need to manually look |
118 | 116 | // for the objects in the session bus that implements the |
119 | 117 | // 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]) { | |
128 | 124 | Util.Logger.debug("Using Brute-force mode for StatusNotifierItem "+id); |
129 | self._registerItem(path, name, path); | |
125 | this._registerItem(path, name, path); | |
130 | 126 | } |
131 | 127 | }) |
132 | 128 | }); |
133 | }, | |
134 | ||
135 | RegisterStatusNotifierItemAsync: function(params, invocation) { | |
129 | } | |
130 | ||
131 | RegisterStatusNotifierItemAsync(params, invocation) { | |
136 | 132 | // it would be too easy if all application behaved the same |
137 | 133 | // instead, ayatana patched gnome apps to send a path |
138 | 134 | // while kde apps send a bus name |
160 | 156 | this._ensureItemRegistered(service, bus_name, obj_path); |
161 | 157 | |
162 | 158 | invocation.return_value(null); |
163 | }, | |
164 | ||
165 | _itemVanished: function(proxy, bus_name) { | |
159 | } | |
160 | ||
161 | _itemVanished(proxy, bus_name) { | |
166 | 162 | // FIXME: this is useless if the path name disappears while the bus stays alive (not unheard of) |
167 | 163 | for (var i in this._items) { |
168 | 164 | if (i.indexOf(bus_name) == 0) { |
169 | 165 | this._remove(i); |
170 | 166 | } |
171 | 167 | } |
172 | }, | |
173 | ||
174 | _remove: function(id) { | |
168 | } | |
169 | ||
170 | _remove(id) { | |
175 | 171 | this._items[id].destroy(); |
176 | 172 | delete this._items[id]; |
177 | 173 | Gio.DBus.session.unwatch_name(this._nameWatcher[id]); |
178 | 174 | delete this._nameWatcher[id]; |
179 | 175 | this._dbusImpl.emit_signal('StatusNotifierItemUnregistered', GLib.Variant.new('(s)', id)); |
180 | 176 | this._dbusImpl.emit_property_changed('RegisteredStatusNotifierItems', GLib.Variant.new('as', this.RegisteredStatusNotifierItems)); |
181 | }, | |
182 | ||
183 | RegisterNotificationHost: function(service) { | |
177 | } | |
178 | ||
179 | RegisterNotificationHost(service) { | |
184 | 180 | throw new Gio.DBusError('org.gnome.Shell.UnsupportedMethod', |
185 | 181 | 'Registering additional notification hosts is not supported'); |
186 | }, | |
187 | ||
188 | IsNotificationHostRegistered: function() { | |
182 | } | |
183 | ||
184 | IsNotificationHostRegistered() { | |
189 | 185 | return true; |
190 | }, | |
191 | ||
192 | ProtocolVersion: function() { | |
186 | } | |
187 | ||
188 | ProtocolVersion() { | |
193 | 189 | // "The version of the protocol the StatusNotifierWatcher instance implements." [sic] |
194 | 190 | // 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 | } | |
197 | 193 | |
198 | 194 | get RegisteredStatusNotifierItems() { |
199 | 195 | return Object.keys(this._items); |
200 | }, | |
196 | } | |
201 | 197 | |
202 | 198 | get IsStatusNotifierHostRegistered() { |
203 | 199 | return true; |
204 | }, | |
205 | ||
206 | destroy: function() { | |
200 | } | |
201 | ||
202 | destroy() { | |
207 | 203 | if (!this._isDestroyed) { |
208 | 204 | // this doesn't do any sync operation and doesn't allow us to hook up the event of being finished |
209 | 205 | // which results in our unholy debounce hack (see extension.js) |
221 | 217 | this._isDestroyed = true; |
222 | 218 | } |
223 | 219 | } |
224 | }); | |
220 | }; |
16 | 16 | const GLib = imports.gi.GLib |
17 | 17 | const GObject = imports.gi.GObject |
18 | 18 | |
19 | const Lang = imports.lang | |
20 | 19 | const Signals = imports.signals |
21 | 20 | |
22 | const refreshPropertyOnProxy = function(proxy, property_name) { | |
21 | var refreshPropertyOnProxy = function(proxy, property_name) { | |
23 | 22 | proxy.g_connection.call(proxy.g_name, |
24 | 23 | proxy.g_object_path, |
25 | 24 | 'org.freedesktop.DBus.Properties', |
46 | 45 | }) |
47 | 46 | } |
48 | 47 | |
49 | const getUniqueBusNameSync = function(bus, name) { | |
48 | var getUniqueBusNameSync = function(bus, name) { | |
50 | 49 | if (name[0] == ':') |
51 | 50 | return name; |
52 | 51 | |
136 | 135 | let id = src.connect(signal, handler) |
137 | 136 | |
138 | 137 | 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', () => { | |
140 | 139 | src.disconnect(id) |
141 | 140 | src.disconnect(destroy_id) |
142 | 141 | }) |
170 | 169 | * Usage: |
171 | 170 | * Util.connectSmart(srcOb, 'signal', tgtObj, 'handler') |
172 | 171 | * or |
173 | * Util.connectSmart(srcOb, 'signal', function() { ... }) | |
172 | * Util.connectSmart(srcOb, 'signal', () => { ... }) | |
174 | 173 | */ |
175 | 174 | var connectSmart = function() { |
176 | 175 | if (arguments.length == 4) |
182 | 181 | /** |
183 | 182 | * Helper class for logging stuff |
184 | 183 | */ |
185 | var Logger = { | |
186 | _log: function(prefix, message) { | |
184 | var Logger = class AppIndicators_Logger { | |
185 | static _log(prefix, message) { | |
187 | 186 | 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 | |
191 | 191 | Logger._log("DEBUG", message); |
192 | }, | |
193 | ||
194 | warn: function(message) { | |
192 | } | |
193 | ||
194 | static warn(message) { | |
195 | 195 | Logger._log("WARN", message); |
196 | }, | |
197 | ||
198 | error: function(message) { | |
196 | } | |
197 | ||
198 | static error(message) { | |
199 | 199 | Logger._log("ERROR", message); |
200 | }, | |
201 | ||
202 | fatal: function(message) { | |
200 | } | |
201 | ||
202 | static fatal(message) { | |
203 | 203 | Logger._log("FATAL", message); |
204 | 204 | } |
205 | 205 | }; |
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']++ |