Codebase list gnome-shell-extension-appindicator / f2af2fd
ported dbusMenu.js to GDbus Jonas Kuemmerlin 11 years ago
3 changed file(s) with 116 addition(s) and 50 deletion(s). Raw diff Collapse all Expand all
99 ## Missing features
1010 * Tooltips: Not implemented in `libappindicator` and I've yet to see any indicator using it (KDE ones maybe?). They're likely to return (like implemented in the original patch) as soon as there is proven (and testable) real world usage.
1111 * Oversized icons like the ones used by `indicator-multiload` are unsupported. They will be shrunk to normal size.
12 * Icon pixmaps: Implementation is likely to return if we find a real world indicator as test case.
1213
1314 ## Buggy features
14 * Icon pixmaps are untested and will probably not work correctly. If we can't find any real world usage they will be removed in near future.
1515 * Ayatana labels are supported in the panel only.
1616 * The whole thing eats a bunch of memory. There is no evidence for memory leaks (yet) but the garbage collector seems to have severe problems dealing with changing icons.
1717 * `nm-applet` is broken: https://bugs.launchpad.net/ubuntu/+source/network-manager-applet/+bug/965895
1818
1919 ## TODO
20 * Move everything to GDBus - gjs master already removed the legacy DBus bindings.
2120 * Add Localization.
1717
1818 const Lang = imports.lang;
1919 const Mainloop = imports.mainloop;
20 const DBus = imports.dbus;
20 const Gio = imports.gi.Gio;
21 const GLib = imports.gi.GLib;
2122 const St = imports.gi.St;
2223
2324 const PopupMenu = imports.ui.popupMenu;
2627 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2728 const Util = Extension.imports.util;
2829
29 // To be replaced with org.freedesktop if and when approved
30 const AYATANA_PREFIX = 'com.canonical';
31
32 const DBusMenuInterface = {
33 name: AYATANA_PREFIX + '.dbusmenu',
34 methods: [
35 //{ name: 'version', inSignature: '', outSignature: 's' }, // not in libdbusmenu
36 { name: 'AboutToShow', inSignature: 'i', outSignature: 'b' },
37 { name: 'AboutToShowGroup', inSignature: 'ai', outSignature: '' },
38 { name: 'Event', inSignature: 'isvu', outSignature: 'aiai' },
39 { name: 'EventGroup', inSignature: '(isvu)', outSignature: 'ai'},
40 { name: 'GetChildren', inSignature: 'ias', outSignature: 'a(ia{sv})' }, // XXX: undocumented
41 { name: 'GetGroupProperties', inSignature: 'aias', outSignature: 'a(ia{sv})' },
42 { name: 'GetLayout', inSignature: 'iias', outSignature: 'us' }, //XXX: outSignature u(ia{sv}av)
43 //{ name: 'GetProperties', inSignature: 'ias', outSignature: 'a{sv}' }, //not in libdbusmenu
44 { name: 'GetProperty', inSignature: 'is', outSignature: 'v' }
45 ],
46 signals: [
47 { name: 'ItemsPropertiesUpdated', outSignature: 'a(ia{sv})a(ias)' },
48 { name: 'ItemUpdated', outSignature: 'i' }, //XXX: not in libdbusmenu, but some indicators dispatch it (?)
49 { name: 'LayoutUpdated', outSignature: 'ui' },
50 { name: 'ItemActivationRequested', outSignature: 'iu' }
51 ]
52 };
53
54 const DBusMenuProxy = DBus.makeProxyClass(DBusMenuInterface);
30 //copied from libdbusmenu
31 const DBusMenuInterface = <interface name="com.canonical.dbusmenu">
32 <!-- Properties -->
33 <property name="Version" type="u" access="read">
34 </property>
35 <property name="TextDirection" type="s" access="read">
36 </property>
37 <property name="Status" type="s" access="read">
38 </property>
39 <property name="IconThemePath" type="as" access="read">
40 </property>
41 <!-- Functions -->
42 <method name="GetLayout">
43 <arg type="i" name="parentId" direction="in" />
44 <arg type="i" name="recursionDepth" direction="in" />
45 <arg type="as" name="propertyNames" direction="in" />
46 <arg type="u(ia{sv}av)" name="layout" direction="out" />
47 </method>
48 <method name="GetGroupProperties">
49 <arg type="ai" name="ids" direction="in" >
50 </arg>
51 <arg type="as" name="propertyNames" direction="in" >
52 </arg>
53 <arg type="a(ia{sv})" name="properties" direction="out" >
54 </arg>
55 </method>
56 <method name="GetProperty">
57 <arg type="i" name="id" direction="in">
58 </arg>
59 <arg type="s" name="name" direction="in">
60 </arg>
61 <arg type="v" name="value" direction="out">
62 </arg>
63 </method>
64 <method name="Event">
65 <arg type="i" name="id" direction="in" >
66 </arg>
67 <arg type="s" name="eventId" direction="in" >
68 </arg>
69 <arg type="v" name="data" direction="in" >
70 </arg>
71 <arg type="u" name="timestamp" direction="in" >
72 </arg>
73 </method>
74 <method name="EventGroup">
75 <arg type="a(isvu)" name="events" direction="in">
76 </arg>
77 <arg type="ai" name="idErrors" direction="out">
78 </arg>
79 </method>
80 <method name="AboutToShow">
81 <arg type="i" name="id" direction="in">
82 </arg>
83 <arg type="b" name="needUpdate" direction="out">
84 </arg>
85 </method>
86 <method name="AboutToShowGroup">
87 <arg type="ai" name="ids" direction="in">
88 </arg>
89 <arg type="ai" name="updatesNeeded" direction="out">
90 </arg>
91 <arg type="ai" name="idErrors" direction="out">
92 </arg>
93 </method>
94 <!-- Signals -->
95 <signal name="ItemsPropertiesUpdated">
96 <arg type="a(ia{sv})" name="updatedProps" direction="out" />
97 <arg type="a(ias)" name="removedProps" direction="out" />
98 </signal>
99 <signal name="LayoutUpdated">
100 <arg type="ui" name="parent" direction="out" />
101 </signal>
102 <signal name="ItemActivationRequested">
103 <arg type="i" name="id" direction="out" >
104 </arg>
105 <arg type="u" name="timestamp" direction="out" >
106 </arg>
107 </signal>
108 <!-- End of interesting stuff -->
109 </interface>
110
111 const DBusMenuProxy = Gio.DBusProxy.makeProxyWrapper(DBusMenuInterface);
55112
56113 /**
57114 * Menu:
83140 this._itemProperties = { '0': { } };
84141 this._items = { };
85142
86 this._proxy = new DBusMenuProxy(DBus.session, this.busName, this.path);
87 this._proxy.connect('ItemsPropertiesUpdated', Lang.bind(this, this._itemsPropertiesUpdated));
88 this._proxy.connect('ItemUpdated', Lang.bind(this, this._itemUpdated));
89 this._proxy.connect('LayoutUpdated', Lang.bind(this, this._layoutUpdated));
143 this._proxy = new DBusMenuProxy(Gio.DBus.session, this.busName, this.path);
144 this._proxy.connectSignal('ItemsPropertiesUpdated', Lang.bind(this, this._itemsPropertiesUpdated));
145 this._proxy.connectSignal('ItemUpdated', Lang.bind(this, this._itemUpdated));
146 this._proxy.connectSignal('LayoutUpdated', Lang.bind(this, this._layoutUpdated));
90147 this._revision = 0;
91148
92149 // HACK: the spec mandates calling AboutToShow when opening the menu, but this
105162 },
106163
107164 _readLayout: function(subtree) {
108 this._proxy.GetLayoutRemote(subtree, -1, ['id'], Lang.bind(this, function(result, error) {
109 if (error) log(error);
165 this._proxy.GetLayoutRemote(subtree, -1, ['id'], (function(result, error) {
166 if (error) {
167 log(error);
168 error.stack.split("\n").forEach(function(e) { log(e); });
169 }
110170 let revision = result[0];
111171 let layout = result[1];
112172 if (this._revision >= revision)
117177 this._children[id] = [ ];
118178 let child;
119179 for each (child in element[2]) {
120 let childid = child[0];
180 let childid = child.deep_unpack()[0];
121181 this._children[id].push(childid);
122182 this._parents[childid] = id;
123183 if (!this._itemProperties[childid])
124184 this._readItem(childid);
125 recurse.call(this, child);
185 recurse.call(this, child.deep_unpack());
126186 }
127187 }
128188 recurse.call(this, root);
129189 this._revision = revision;
130190 this._buildMenu(subtree);
131191 this._GCItems();
132 }));
192 }).bind(this));
133193 },
134194
135195 _readItem: function(id) {
136196 this._proxy.GetGroupPropertiesRemote([id], [], Lang.bind(this, function (result, error) {
137197 if (error) {
138 log("While reading item "+id+"on "+this.busName+this.path+": ");
198 log("While reading item "+id+" on "+this.busName+this.path+": ");
139199 log(error);
140 } else if (!result[0]) {
200 error.stack.split("\n").forEach(function(e) { log(e); });
201 } else if (!result[0][0]) {
141202 //FIXME: how the hell does nm-applet manage to get us here?
142203 //it doesn't seem to have any negative effects, however
143204 log("While reading item "+id+" on "+this.busName+this.path+": ");
144205 log("Empty result set (?)");
145206 log(result);
146207 } else {
147 this._itemProperties[id] = result[0][1];
208 //the unpacking algorithm is very strange...
209 var props = result[0][0][1];
210 for (var i in props) {
211 props[i] = props[i].deep_unpack();
212 }
213 this._itemProperties[id] = props;
148214 if(id == 0)
149215 this._updateRoot();
150216 else
306372 this._readItem(id);
307373 },
308374
309 _itemsPropertiesUpdated: function (proxy, changed, removed) {
375 _itemsPropertiesUpdated: function (proxy, bus, [changed, removed]) {
310376 //FIXME: the array structure is weird
311377 for (var i = 0; i < changed.length; i++) {
312378 var id = changed[i][0];
313379 var properties = changed[i][1];
314380 for (var property in properties) {
315 this._itemPropertyUpdated(proxy, id, property, properties[property])
381 this._itemPropertyUpdated(proxy, id, property, properties[property].deep_unpack())
316382 }
317383 }
318384 },
365431 this._replaceItem(this._parents[id], true);
366432 },
367433
368 _layoutUpdated: function(proxy, revision, subtree) {
434 _layoutUpdated: function(proxy, bus, [revision, subtree]) {
369435 log(this.busName + this.path + " Layout updated for node "+subtree);
370436 if (revision <= this._revision)
371437 return;
391457 _itemActivate: function(item, event) {
392458 // we emit clicked also for keyboard activation
393459 // XXX: what is event specific data?
394 this._proxy.EventRemote(item._dbusId, 'clicked', '', event.get_time());
460 this._proxy.EventRemote(item._dbusId, 'clicked', GLib.Variant.new("s", ""), event.get_time());
395461 },
396462
397463 /* FIXME: apparently this is not correct
66 const Cogl = imports.gi.Cogl;
77 const Gio = imports.gi.Gio;
88 const GLib = imports.gi.GLib;
9 const byteArray = imports.byteArray;
910
1011 /*
1112 * UtilMixin:
7374
7475 //FIXME: Wouldn't it be way faster and less memory hungry to just write it to a temporary file and load it afterwards?
7576 const createActorFromMemoryImage = function(data) {
76 var stream = Gio.MemoryInputStream.new_from_data(byteArrayFromString(data));
77 var stream = Gio.MemoryInputStream.new_from_data(byteArrayFromArray(data));
7778 var pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
7879 pixbuf.savev("/tmp/debugbuf.png", "png", [], []);
7980 var img = new Clutter.Image();
8990 return widget;
9091 }
9192
92 //HACK: byteArray.fromString() doesn't accept our strings because of embedded nuls.
93 //HACK: byteArray.fromArray() causes a segfault at gc.
9394 // This honestly can't be efficient in no way.
94 const byteArrayFromString = function(data) {
95 const byteArrayFromArray = function(data) {
9596 var data_length = data.length;
96 var array = new imports.byteArray.ByteArray(data_length);
97 var array = new byteArray.ByteArray(data_length);
9798 for (var i = 0; i < data_length; i++) {
98 array[i] = data.charCodeAt(i);
99 array[i] = data[i];
99100 }
100101 return array;
101102 }