17 | 17 |
|
18 | 18 |
const Lang = imports.lang;
|
19 | 19 |
const Mainloop = imports.mainloop;
|
20 | |
const DBus = imports.dbus;
|
|
20 |
const Gio = imports.gi.Gio;
|
|
21 |
const GLib = imports.gi.GLib;
|
21 | 22 |
const St = imports.gi.St;
|
22 | 23 |
|
23 | 24 |
const PopupMenu = imports.ui.popupMenu;
|
|
26 | 27 |
const Extension = imports.misc.extensionUtils.getCurrentExtension();
|
27 | 28 |
const Util = Extension.imports.util;
|
28 | 29 |
|
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);
|
55 | 112 |
|
56 | 113 |
/**
|
57 | 114 |
* Menu:
|
|
83 | 140 |
this._itemProperties = { '0': { } };
|
84 | 141 |
this._items = { };
|
85 | 142 |
|
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));
|
90 | 147 |
this._revision = 0;
|
91 | 148 |
|
92 | 149 |
// HACK: the spec mandates calling AboutToShow when opening the menu, but this
|
|
105 | 162 |
},
|
106 | 163 |
|
107 | 164 |
_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 |
}
|
110 | 170 |
let revision = result[0];
|
111 | 171 |
let layout = result[1];
|
112 | 172 |
if (this._revision >= revision)
|
|
117 | 177 |
this._children[id] = [ ];
|
118 | 178 |
let child;
|
119 | 179 |
for each (child in element[2]) {
|
120 | |
let childid = child[0];
|
|
180 |
let childid = child.deep_unpack()[0];
|
121 | 181 |
this._children[id].push(childid);
|
122 | 182 |
this._parents[childid] = id;
|
123 | 183 |
if (!this._itemProperties[childid])
|
124 | 184 |
this._readItem(childid);
|
125 | |
recurse.call(this, child);
|
|
185 |
recurse.call(this, child.deep_unpack());
|
126 | 186 |
}
|
127 | 187 |
}
|
128 | 188 |
recurse.call(this, root);
|
129 | 189 |
this._revision = revision;
|
130 | 190 |
this._buildMenu(subtree);
|
131 | 191 |
this._GCItems();
|
132 | |
}));
|
|
192 |
}).bind(this));
|
133 | 193 |
},
|
134 | 194 |
|
135 | 195 |
_readItem: function(id) {
|
136 | 196 |
this._proxy.GetGroupPropertiesRemote([id], [], Lang.bind(this, function (result, error) {
|
137 | 197 |
if (error) {
|
138 | |
log("While reading item "+id+"on "+this.busName+this.path+": ");
|
|
198 |
log("While reading item "+id+" on "+this.busName+this.path+": ");
|
139 | 199 |
log(error);
|
140 | |
} else if (!result[0]) {
|
|
200 |
error.stack.split("\n").forEach(function(e) { log(e); });
|
|
201 |
} else if (!result[0][0]) {
|
141 | 202 |
//FIXME: how the hell does nm-applet manage to get us here?
|
142 | 203 |
//it doesn't seem to have any negative effects, however
|
143 | 204 |
log("While reading item "+id+" on "+this.busName+this.path+": ");
|
144 | 205 |
log("Empty result set (?)");
|
145 | 206 |
log(result);
|
146 | 207 |
} 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;
|
148 | 214 |
if(id == 0)
|
149 | 215 |
this._updateRoot();
|
150 | 216 |
else
|
|
306 | 372 |
this._readItem(id);
|
307 | 373 |
},
|
308 | 374 |
|
309 | |
_itemsPropertiesUpdated: function (proxy, changed, removed) {
|
|
375 |
_itemsPropertiesUpdated: function (proxy, bus, [changed, removed]) {
|
310 | 376 |
//FIXME: the array structure is weird
|
311 | 377 |
for (var i = 0; i < changed.length; i++) {
|
312 | 378 |
var id = changed[i][0];
|
313 | 379 |
var properties = changed[i][1];
|
314 | 380 |
for (var property in properties) {
|
315 | |
this._itemPropertyUpdated(proxy, id, property, properties[property])
|
|
381 |
this._itemPropertyUpdated(proxy, id, property, properties[property].deep_unpack())
|
316 | 382 |
}
|
317 | 383 |
}
|
318 | 384 |
},
|
|
365 | 431 |
this._replaceItem(this._parents[id], true);
|
366 | 432 |
},
|
367 | 433 |
|
368 | |
_layoutUpdated: function(proxy, revision, subtree) {
|
|
434 |
_layoutUpdated: function(proxy, bus, [revision, subtree]) {
|
369 | 435 |
log(this.busName + this.path + " Layout updated for node "+subtree);
|
370 | 436 |
if (revision <= this._revision)
|
371 | 437 |
return;
|
|
391 | 457 |
_itemActivate: function(item, event) {
|
392 | 458 |
// we emit clicked also for keyboard activation
|
393 | 459 |
// 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());
|
395 | 461 |
},
|
396 | 462 |
|
397 | 463 |
/* FIXME: apparently this is not correct
|