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
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/
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
15
* The Original Code is messagingmenu-extension
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.
23
* Mike Conley <mconley@mozillamessaging.com>
24
* Chris Coulson <chris.coulson@canonical.com>
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.
38
* ***** END LICENSE BLOCK ***** */
40
EXPORTS = [ "IndicateBackend" ];
42
Cu.import("resource://gre/modules/Services.jsm");
43
Cu.import("resource://gre/modules/NetUtil.jsm");
45
require("libs/glib.js");
46
require("libs/gobject.js");
47
require("libs/dbusmenu.js");
48
require("libs/indicate.js");
50
const kUserBlacklistDir = ".config/indicators/messages/applications-blacklist/";
52
function LauncherEntryFind(aDir, aDesktopFile, aCallback) {
53
new LauncherEntryFinder(aDir, aDesktopFile, aCallback);
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");
66
this.callback = aCallback;
67
this.desktopFile = aDesktopFile;
68
this.entries = aDir.directoryEntries;
71
this.processNextEntry();
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();
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);
88
self.processNextEntry();
92
LOG("No launcher entry found");
98
function IndicatorImpl(aIndicator, aBackend) {
99
this._attention = false;
103
this._indicator = aIndicator;
104
this._backend = aBackend;
105
this._nativeIndicator = indicate.indicate_indicator_new();
108
gobject.g_signal_connect(this._nativeIndicator, "user-display",
109
gobject.wrapSignalHandler(function(aNativeIndicator,
111
aBackend.activateCallback(self._indicator.folderURL, aTimestamp);
112
}, ctypes.void_t, [indicate.Indicator.ptr, glib.guint, glib.gpointer]));
114
aIndicator.registerImpl(SimpleObjectWrapper(this));
117
IndicatorImpl.prototype = {
118
__exposedProps__: ["requestAttention", "cancelAttention", "show", "hide",
119
"newCount", "label", "visible", "hasAttention",
122
requestAttention: function II_requestAttention() {
124
indicate.indicate_indicator_set_property(this._nativeIndicator,
125
indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
129
this._attention = true;
132
cancelAttention: function II_cancelAttention() {
134
indicate.indicate_indicator_set_property(this._nativeIndicator,
135
indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
139
this._attention = false;
142
show: function II_show() {
144
indicate.indicate_indicator_show(this._nativeIndicator);
146
if (this._attention) {
147
indicate.indicate_indicator_set_property(this._nativeIndicator,
148
indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
154
hide: function II_hide() {
156
indicate.indicate_indicator_hide(this._nativeIndicator);
158
if (this._attention) {
159
indicate.indicate_indicator_set_property(this._nativeIndicator,
160
indicate.INDICATOR_MESSAGES_PROP_ATTENTION,
167
return this._newCount;
170
set newCount(aCount) {
171
indicate.indicate_indicator_set_property(this._nativeIndicator,
172
indicate.INDICATOR_MESSAGES_PROP_COUNT,
175
this._newCount = aCount;
183
indicate.indicate_indicator_set_property(this._nativeIndicator,
184
indicate.INDICATOR_MESSAGES_PROP_NAME,
186
this._label = aLabel;
190
return indicate.indicate_indicator_is_visible(this._nativeIndicator) != 0;
194
return this._attention;
197
destroy: function() {
198
if (this._nativeIndicator) {
199
gobject.g_object_unref(this._nativeIndicator);
200
this._nativeIndicator = null;
203
this._backend.unregisterIndicator(this._indicator);
207
function IndicateBackend(aName, aActivationCallback, aOpen3PaneCallback,
208
aOpenComposeCallback, aOpenContactsCallback) {
210
this.activateCallback = aActivationCallback;
212
this._server = indicate.indicate_server_ref_default();
213
if (!this._server || this._server.isNull()) {
214
throw Error("Failed to create libindicate server");
217
indicate.indicate_server_set_type(this._server, "message.email");
218
indicate.indicate_server_set_desktop_file(this._server, this._desktopFile);
220
gobject.g_signal_connect(this._server, "server-display",
221
gobject.wrapSignalHandler(function(aServer,
223
aOpen3PaneCallback(aTimestamp);
224
}, ctypes.void_t, [indicate.IndicateServer.ptr, glib.guint, glib.gpointer]));
226
let bundle = Services.strings.createBundle(
227
"chrome://messagingmenu/locale/messagingmenu.properties");
229
let server = dbusmenu.dbusmenu_server_new("/messaging/commands");
230
let root = dbusmenu.dbusmenu_menuitem_new();
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);
237
gobject.g_signal_connect(composeMi, dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
238
gobject.wrapSignalHandler(function(aItem,
240
aOpenComposeCallback(aTimestamp);
241
}, ctypes.void_t, [dbusmenu.DbusmenuMenuitem.ptr, glib.guint, glib.gpointer]));
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);
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);
253
gobject.g_signal_connect(contactsMi, dbusmenu.MENUITEM_SIGNAL_ITEM_ACTIVATED,
254
gobject.wrapSignalHandler(function(aItem,
256
aOpenContactsCallback(aTimestamp);
257
}, ctypes.void_t, [dbusmenu.DbusmenuMenuitem.ptr, glib.guint, glib.gpointer]));
259
dbusmenu.dbusmenu_menuitem_child_append(root, contactsMi);
260
gobject.g_object_unref(contactsMi); // This too
262
dbusmenu.dbusmenu_server_set_root(server, root);
263
gobject.g_object_unref(root); // And this...
265
indicate.indicate_server_set_menu(this._server, server);
266
gobject.g_object_unref(server);
268
this._indicators = {};
270
return SimpleObjectWrapper(this);
273
IndicateBackend.prototype = {
274
__exposedProps__: ["enable", "disable", "remove", "shutdown",
275
"registerIndicator"],
277
enable: function IB_enable() {
278
if (!this._server || this._server.isNull()) {
279
throw Error("We have been shut down already");
282
indicate.indicate_server_show(this._server);
284
let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
285
userBlacklistDir.appendRelativePath(kUserBlacklistDir);
286
LauncherEntryFind(userBlacklistDir, this._desktopFile, function(aFile) {
288
LOG("Removing launcher entry " + aFile.path);
294
disable: function IB_disable() {
295
if (!this._server || this._server.isNull()) {
296
throw Error("We have been shut down already");
299
indicate.indicate_server_hide(this._server);
302
remove: function IB_remove() {
303
if (!this._server || this._server.isNull()) {
304
throw Error("We have been shut down already");
307
let userBlacklistDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
308
userBlacklistDir.appendRelativePath(kUserBlacklistDir);
309
LauncherEntryFind(userBlacklistDir, this._desktopFile, function(aFile) {
314
if (!userBlacklistDir.exists()) {
315
userBlacklistDir.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0755);
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);
329
NetUtil.asyncCopy(istream, ostream, null);
333
shutdown: function IB_shutdown() {
334
if (this._server || this._server.isNull()) {
335
gobject.g_object_unref(this._server);
340
registerIndicator: function IB_registerIndicator(aIndicator) {
341
if (!this._server || this._server.isNull()) {
342
throw Error("We have been shut down already");
346
throw Error("Invalid indicator entry");
349
if (aIndicator.folderURL in this._indicators) {
350
throw Error("Indicator already registered with backend");
353
this._indicators[aIndicator.folderURL] = new IndicatorImpl(aIndicator,
357
unregisterIndicator: function IB_unregisterIndicator(aIndicator) {
359
throw Error("Invalid indicator entry");
362
if (!(aIndicator.folderURL in this._indicators)) {
363
throw Error("Indicator is not registered with backend");
366
delete this._indicators[aIndicator.folderURL];
370
return "/usr/share/applications/" + this._name;