~mozillateam/thunderbird/thunderbird.head

« back to all changes in this revision

Viewing changes to debian/messagingmenu/res/modules/LibIndicateBackend.js

  • Committer: Chris Coulson
  • Date: 2012-08-27 09:10:13 UTC
  • Revision ID: chris.coulson@canonical.com-20120827091013-tsprx093o5t6bzbp
* Update messagingmenu-extension to 1.0 prerelease r135
  - Add support for libmessaging-menu (thanks Lars Uebernickel)
    LP: #1040259
* Make "Contacts" and "Compose" actions show up in the messaging menu
  - update debian/thunderbird.desktop.in

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 
2
/* ***** BEGIN LICENSE BLOCK *****
 
3
 *       Version: MPL 1.1/GPL 2.0/LGPL 2.1
 
4
 *
 
5
 * The contents of this file are subject to the Mozilla Public License Version
 
6
 * 1.1 (the "License"); you may not use this file except in compliance with
 
7
 * the License. You may obtain a copy of the License at
 
8
 * http://www.mozilla.org/MPL/
 
9
 * 
 
10
 * Software distributed under the License is distributed on an "AS IS" basis,
 
11
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 
12
 * for the specific language governing rights and limitations under the
 
13
 * License.
 
14
 *
 
15
 * The Original Code is messagingmenu-extension
 
16
 *
 
17
 * The Initial Developer of the Original Code is
 
18
 * Mozilla Messaging, Ltd.
 
19
 * Portions created by the Initial Developer are Copyright (C) 2010
 
20
 * the Initial Developer. All Rights Reserved.
 
21
 *
 
22
 * Contributor(s):
 
23
 *    Mike Conley <mconley@mozillamessaging.com>
 
24
 *    Chris Coulson <chris.coulson@canonical.com>
 
25
 *
 
26
 * Alternatively, the contents of this file may be used under the terms of
 
27
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 
28
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 
29
 * in which case the provisions of the GPL or the LGPL are applicable instead
 
30
 * of those above. If you wish to allow use of your version of this file only
 
31
 * under the terms of either the GPL or the LGPL, and not to allow others to
 
32
 * use your version of this file under the terms of the MPL, indicate your
 
33
 * decision by deleting the provisions above and replace them with the notice
 
34
 * and other provisions required by the GPL or the LGPL. If you do not delete
 
35
 * the provisions above, a recipient may use your version of this file under
 
36
 * the terms of any one of the MPL, the GPL or the LGPL.
 
37
 * 
 
38
 * ***** END LICENSE BLOCK ***** */
 
39
 
 
40
EXPORTS = [ "IndicateBackend" ];
 
41
 
 
42
Cu.import("resource://gre/modules/Services.jsm");
 
43
Cu.import("resource://gre/modules/NetUtil.jsm");
 
44
 
 
45
require("libs/glib.js");
 
46
require("libs/gobject.js");
 
47
require("libs/dbusmenu.js");
 
48
require("libs/indicate.js");
 
49
 
 
50
const kUserBlacklistDir = ".config/indicators/messages/applications-blacklist/";
 
51
 
 
52
function LauncherEntryFind(aDir, aDesktopFile, aCallback) {
 
53
  new LauncherEntryFinder(aDir, aDesktopFile, aCallback);
 
54
}
 
55
 
 
56
// Small helper class which takes a directory containing messaging menu
 
57
// launcher entries and tells the listener whether one of them is ours
 
58
function LauncherEntryFinder(aDir, aDesktopFile, aCallback) {
 
59
  LOG("Searching for launcher entry for " + aDesktopFile + " in " + aDir.path);
 
60
  if (!aDir.exists() || !aDir.isDirectory()) {
 
61
    LOG(aDir.path + " does not exist or is not a directory");
 
62
    aCallback(null);
 
63
    return;
 
64
  }
 
65
 
 
66
  this.callback = aCallback;
 
67
  this.desktopFile = aDesktopFile;
 
68
  this.entries = aDir.directoryEntries;
 
69
  this.dir = aDir;
 
70
 
 
71
  this.processNextEntry();
 
72
}
 
73
 
 
74
LauncherEntryFinder.prototype = {
 
75
  processNextEntry: function MMEF_processNextEntry() {
 
76
    if (this.entries.hasMoreElements()) {
 
77
      var entry = this.entries.getNext().QueryInterface(Ci.nsIFile);
 
78
      if (!entry.isFile()) {
 
79
        this.processNextEntry();
 
80
      }
 
81
      var self = this;
 
82
      NetUtil.asyncFetch(entry, function(inputStream, status) {
 
83
        let data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
 
84
        if (data.replace(/\n$/,"") == self.desktopFile) {
 
85
          LOG("Found launcher entry " + entry.path);
 
86
          self.callback(entry);
 
87
        } else {
 
88
          self.processNextEntry();
 
89
        }
 
90
      });
 
91
    } else {
 
92
      LOG("No launcher entry found");
 
93
      this.callback(null);
 
94
    }
 
95
  }
 
96
};
 
97
 
 
98
function IndicatorImpl(aIndicator, aBackend) {
 
99
  this._attention = false;
 
100
  this._newCount = 0;
 
101
  this._label = null;
 
102
 
 
103
  this._indicator = aIndicator;
 
104
  this._backend = aBackend;
 
105
  this._nativeIndicator = indicate.indicate_indicator_new();
 
106
 
 
107
  let self = this;
 
108
  gobject.g_signal_connect(this._nativeIndicator, "user-display",
 
109
                           gobject.wrapSignalHandler(function(aNativeIndicator,
 
110
                                                              aTimestamp) {
 
111
    aBackend.activateCallback(self._indicator.folderURL, aTimestamp);
 
112
  }, ctypes.void_t, [indicate.Indicator.ptr, glib.guint, glib.gpointer]));
 
113
 
 
114
  aIndicator.registerImpl(SimpleObjectWrapper(this));
 
115
}
 
116
 
 
117
IndicatorImpl.prototype = {
 
118
  __exposedProps__: ["requestAttention", "cancelAttention", "show", "hide",
 
119
                     "newCount", "label", "visible", "hasAttention",
 
120
                     "destroy"],
 
121
 
 
122
  requestAttention: function II_requestAttention() {
 
123
    if (this.visible) {
 
124
      indicate.indicate_indicator_set_property(this._nativeIndicator,
 
125
                                               indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
 
126
                                               "true");
 
127
    }
 
128
 
 
129
    this._attention = true;
 
130
  },
 
131
 
 
132
  cancelAttention: function II_cancelAttention() {
 
133
    if (this.visible) {
 
134
      indicate.indicate_indicator_set_property(this._nativeIndicator,
 
135
                                               indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
 
136
                                               "false");
 
137
    }
 
138
 
 
139
    this._attention = false;
 
140
  },
 
141
 
 
142
  show: function II_show() {
 
143
    if (!this.visible) {
 
144
      indicate.indicate_indicator_show(this._nativeIndicator);
 
145
 
 
146
      if (this._attention) {
 
147
        indicate.indicate_indicator_set_property(this._nativeIndicator,
 
148
                                                 indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
 
149
                                                 "true");
 
150
      }
 
151
    }
 
152
  },
 
153
 
 
154
  hide: function II_hide() {
 
155
    if (this.visible) {
 
156
      indicate.indicate_indicator_hide(this._nativeIndicator);
 
157
 
 
158
      if (this._attention) {
 
159
        indicate.indicate_indicator_set_property(this._nativeIndicator,
 
160
                                                 indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
 
161
                                                 "false");
 
162
      }
 
163
    }
 
164
  },
 
165
 
 
166
  get newCount() {
 
167
    return this._newCount;
 
168
  },
 
169
 
 
170
  set newCount(aCount) {
 
171
    indicate.indicate_indicator_set_property(this._nativeIndicator,
 
172
                                             indicate.INDICATOR_MESSAGES_PROP_COUNT,
 
173
                                             aCount.toString());
 
174
 
 
175
    this._newCount = aCount;
 
176
  },
 
177
 
 
178
  get label() {
 
179
    return this._label;
 
180
  },
 
181
 
 
182
  set label(aLabel) {
 
183
    indicate.indicate_indicator_set_property(this._nativeIndicator,
 
184
                                             indicate.INDICATOR_MESSAGES_PROP_NAME,
 
185
                                             aLabel);
 
186
    this._label = aLabel;
 
187
  },
 
188
 
 
189
  get visible() {
 
190
    return indicate.indicate_indicator_is_visible(this._nativeIndicator) != 0;
 
191
  },
 
192
 
 
193
  get hasAttention() {
 
194
    return this._attention;
 
195
  },
 
196
 
 
197
  destroy: function() {
 
198
    if (this._nativeIndicator) {
 
199
      gobject.g_object_unref(this._nativeIndicator);
 
200
      this._nativeIndicator = null;
 
201
    }
 
202
 
 
203
    this._backend.unregisterIndicator(this._indicator);
 
204
  }
 
205
};
 
206
 
 
207
function IndicateBackend(aName, aActivationCallback, aOpen3PaneCallback,
 
208
                         aOpenComposeCallback, aOpenContactsCallback) {
 
209
  this._name = aName;
 
210
  this.activateCallback = aActivationCallback;
 
211
 
 
212
  this._server = indicate.indicate_server_ref_default();
 
213
  if (!this._server || this._server.isNull()) {
 
214
    throw Error("Failed to create libindicate server");
 
215
  }
 
216
 
 
217
  indicate.indicate_server_set_type(this._server, "message.email");
 
218
  indicate.indicate_server_set_desktop_file(this._server, this._desktopFile);
 
219
 
 
220
  gobject.g_signal_connect(this._server, "server-display",
 
221
                           gobject.wrapSignalHandler(function(aServer,
 
222
                                                              aTimestamp) {
 
223
    aOpen3PaneCallback(aTimestamp);
 
224
  }, ctypes.void_t, [indicate.IndicateServer.ptr, glib.guint, glib.gpointer]));
 
225
 
 
226
  let bundle = Services.strings.createBundle(
 
227
                "chrome://messagingmenu/locale/messagingmenu.properties");
 
228
 
 
229
  let server = dbusmenu.dbusmenu_server_new("/messaging/commands");
 
230
  let root = dbusmenu.dbusmenu_menuitem_new();
 
231
 
 
232
  let composeMi = dbusmenu.dbusmenu_menuitem_new();
 
233
  dbusmenu.dbusmenu_menuitem_property_set(composeMi, "label",
 
234
                                          bundle.GetStringFromName("composeNewMessage"));
 
235
  dbusmenu.dbusmenu_menuitem_property_set_bool(composeMi, "visible", true);
 
236
 
 
237
  gobject.g_signal_connect(composeMi, dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
 
238
                           gobject.wrapSignalHandler(function(aItem,
 
239
                                                              aTimestamp) {
 
240
    aOpenComposeCallback(aTimestamp);
 
241
  }, ctypes.void_t, [dbusmenu.DbusmenuMenuitem.ptr, glib.guint, glib.gpointer]));
 
242
 
 
243
  dbusmenu.dbusmenu_menuitem_child_append(root, composeMi);
 
244
  // I can't believe that this doesn't inherit from GInitiallyUnowned.
 
245
  // It really, really sucks that we need to do this....
 
246
  gobject.g_object_unref(composeMi);
 
247
 
 
248
  let contactsMi = dbusmenu.dbusmenu_menuitem_new();
 
249
  dbusmenu.dbusmenu_menuitem_property_set(contactsMi, "label",
 
250
                                          bundle.GetStringFromName("contacts"));
 
251
  dbusmenu.dbusmenu_menuitem_property_set_bool(contactsMi, "visible", true);
 
252
 
 
253
  gobject.g_signal_connect(contactsMi, dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED, 
 
254
                           gobject.wrapSignalHandler(function(aItem,
 
255
                                                              aTimestamp) {
 
256
    aOpenContactsCallback(aTimestamp);
 
257
  }, ctypes.void_t, [dbusmenu.DbusmenuMenuitem.ptr, glib.guint, glib.gpointer]));
 
258
 
 
259
  dbusmenu.dbusmenu_menuitem_child_append(root, contactsMi);
 
260
  gobject.g_object_unref(contactsMi); // This too
 
261
 
 
262
  dbusmenu.dbusmenu_server_set_root(server, root);
 
263
  gobject.g_object_unref(root); // And this...
 
264
 
 
265
  indicate.indicate_server_set_menu(this._server, server);
 
266
  gobject.g_object_unref(server);
 
267
 
 
268
  this._indicators = {};
 
269
 
 
270
  return SimpleObjectWrapper(this);
 
271
}
 
272
 
 
273
IndicateBackend.prototype = {
 
274
  __exposedProps__: ["enable", "disable", "remove", "shutdown",
 
275
                     "registerIndicator"],
 
276
 
 
277
  enable: function IB_enable() {
 
278
    if (!this._server || this._server.isNull()) {
 
279
      throw Error("We have been shut down already");
 
280
    }
 
281
 
 
282
    indicate.indicate_server_show(this._server);
 
283
 
 
284
    let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
 
285
    userBlacklistDir.appendRelativePath(kUserBlacklistDir);
 
286
    LauncherEntryFind(userBlacklistDir, this._desktopFile, function(aFile) {
 
287
      if (aFile) {
 
288
        LOG("Removing launcher entry " + aFile.path);
 
289
        aFile.remove(false);
 
290
      }
 
291
    });
 
292
  },
 
293
 
 
294
  disable: function IB_disable() {
 
295
    if (!this._server || this._server.isNull()) {
 
296
      throw Error("We have been shut down already");
 
297
    }
 
298
 
 
299
    indicate.indicate_server_hide(this._server);
 
300
  },
 
301
 
 
302
  remove: function IB_remove() {
 
303
    if (!this._server || this._server.isNull()) {
 
304
      throw Error("We have been shut down already");
 
305
    }
 
306
 
 
307
    let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
 
308
    userBlacklistDir.appendRelativePath(kUserBlacklistDir);
 
309
    LauncherEntryFind(userBlacklistDir, this._desktopFile, function(aFile) {
 
310
      if (aFile) {
 
311
        return;
 
312
      }
 
313
 
 
314
      if (!userBlacklistDir.exists()) {
 
315
        userBlacklistDir.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0755);
 
316
      }
 
317
 
 
318
      let entry = userBlacklistDir.clone();
 
319
      entry.append(Services.appinfo.name.toLowerCase());
 
320
      let ostream = FileUtils.openSafeFileOutputStream(entry,
 
321
                                                       FileUtils.MODE_WRONLY |
 
322
                                                       FileUtils.MODE_CREATE |
 
323
                                                       FileUtils.MODE_TRUNCATE);
 
324
      let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
 
325
                      .createInstance(Ci.nsIScriptableUnicodeConverter);
 
326
      converter.charset = "UTF-8";
 
327
      let istream = converter.convertToInputStream(this._desktopFile);
 
328
      let self = this;
 
329
      NetUtil.asyncCopy(istream, ostream, null);
 
330
    });
 
331
  },
 
332
 
 
333
  shutdown: function IB_shutdown() {
 
334
    if (this._server || this._server.isNull()) {
 
335
      gobject.g_object_unref(this._server);
 
336
      this._server = null;
 
337
    }
 
338
  },
 
339
 
 
340
  registerIndicator: function IB_registerIndicator(aIndicator) {
 
341
    if (!this._server || this._server.isNull()) {
 
342
      throw Error("We have been shut down already");
 
343
    }
 
344
 
 
345
    if (!aIndicator) {
 
346
      throw Error("Invalid indicator entry");
 
347
    }
 
348
 
 
349
    if (aIndicator.folderURL in this._indicators) {
 
350
      throw Error("Indicator already registered with backend");
 
351
    }
 
352
 
 
353
    this._indicators[aIndicator.folderURL] = new IndicatorImpl(aIndicator,
 
354
                                                               this);
 
355
  },
 
356
 
 
357
  unregisterIndicator: function IB_unregisterIndicator(aIndicator) {
 
358
    if (!aIndicator) {
 
359
      throw Error("Invalid indicator entry");
 
360
    }
 
361
 
 
362
    if (!(aIndicator.folderURL in this._indicators)) {
 
363
      throw Error("Indicator is not registered with backend");
 
364
    }
 
365
 
 
366
    delete this._indicators[aIndicator.folderURL];
 
367
  },
 
368
 
 
369
  get _desktopFile() {
 
370
    return "/usr/share/applications/" + this._name;
 
371
  }
 
372
};