mirror of
https://github.com/Ximi1970/systray-x.git
synced 2025-11-09 06:46:07 +01:00
491 lines
14 KiB
JavaScript
491 lines
14 KiB
JavaScript
/* eslint-disable object-shorthand */
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
// Get various parts of the WebExtension framework that we need.
|
|
var { ExtensionCommon } = ChromeUtils.import(
|
|
"resource://gre/modules/ExtensionCommon.jsm"
|
|
);
|
|
|
|
// You probably already know what this does.
|
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
var { MailServices } = ChromeUtils.import(
|
|
"resource:///modules/MailServices.jsm"
|
|
);
|
|
var { fixIterator } = ChromeUtils.import(
|
|
"resource:///modules/iteratorUtils.jsm"
|
|
);
|
|
|
|
// ChromeUtils.import() works in experiments for core resource urls as it did
|
|
// in legacy add-ons. However, chrome:// urls that point to add-on resources no
|
|
// longer work, as the "chrome.manifest" file is no longer supported, which
|
|
// defined the root path for each add-on. Instead, ChromeUtils.import() needs
|
|
// a url generated by
|
|
//
|
|
// let url = context.extension.rootURI.resolve("path/to/file.jsm")
|
|
//
|
|
// Instead of taking the extension object from the context, you may generate
|
|
// the extension object from a given add-on ID as shown in the example below.
|
|
// This allows to import a JSM without context, for example inside another JSM.
|
|
//
|
|
var { ExtensionParent } = ChromeUtils.import(
|
|
"resource://gre/modules/ExtensionParent.jsm"
|
|
);
|
|
var extension = ExtensionParent.GlobalManager.getExtension(
|
|
"systray-x@Ximi1970"
|
|
);
|
|
var { folderChange } = ChromeUtils.import(
|
|
extension.rootURI.resolve("modules/folderChange.jsm")
|
|
);
|
|
|
|
// This is the important part. It implements the functions and events defined in schema.json.
|
|
// The variable must have the same name you've been using so far, "myapi" in this case.
|
|
var folderChange = class extends ExtensionCommon.ExtensionAPI {
|
|
getAPI(context) {
|
|
console.log("folderChange module started");
|
|
|
|
// To be notified of the extension going away, call callOnClose with any object that has a
|
|
// close function, such as this one.
|
|
context.callOnClose(this);
|
|
|
|
//
|
|
// Setup folder listener
|
|
//
|
|
SysTrayX.init();
|
|
|
|
return {
|
|
// Again, this key must have the same name.
|
|
folderChange: {
|
|
setCountType: async function (type) {
|
|
SysTrayX.setCountType(type);
|
|
},
|
|
|
|
setFilters: async function (filters) {
|
|
SysTrayX.setFilters(filters);
|
|
},
|
|
|
|
onUnreadMailChange: new ExtensionCommon.EventManager({
|
|
context,
|
|
name: "folderChange.onUnreadMailChange",
|
|
// In this function we add listeners for any events we want to listen to, and return a
|
|
// function that removes those listeners. To have the event fire in your extension,
|
|
// call fire.async.
|
|
register(fire) {
|
|
function callback(event, unread) {
|
|
return fire.async(unread);
|
|
}
|
|
|
|
SysTrayX.add(callback);
|
|
return function () {
|
|
SysTrayX.remove(callback);
|
|
};
|
|
},
|
|
}).api(),
|
|
|
|
// An event. Most of this is boilerplate you don't need to worry about, just copy it.
|
|
onToolbarClick: new ExtensionCommon.EventManager({
|
|
context,
|
|
name: "folderChange.onToolbarClick",
|
|
// In this function we add listeners for any events we want to listen to, and return a
|
|
// function that removes those listeners. To have the event fire in your extension,
|
|
// call fire.async.
|
|
register(fire) {
|
|
function callback(event, id, x, y) {
|
|
return fire.async(id, x, y);
|
|
}
|
|
|
|
windowListener.add(callback);
|
|
return function () {
|
|
windowListener.remove(callback);
|
|
};
|
|
},
|
|
}).api(),
|
|
},
|
|
};
|
|
}
|
|
|
|
close() {
|
|
/*
|
|
* Remove the folder listener
|
|
*/
|
|
SysTrayX.shutdown();
|
|
|
|
// This function is called if the extension is disabled or removed, or Thunderbird closes.
|
|
// We registered it with callOnClose, above.
|
|
console.log("folderChange module closed");
|
|
|
|
// Unload the JSM we imported above. This will cause Thunderbird to forget about the JSM, and
|
|
// load it afresh next time `import` is called. (If you don't call `unload`, Thunderbird will
|
|
// remember this version of the module and continue to use it, even if your extension receives
|
|
// an update.) You should *always* unload JSMs provided by your extension.
|
|
Cu.unload(extension.getURL("modules/folderChange.jsm"));
|
|
}
|
|
};
|
|
|
|
// A helpful class for listening to windows opening and closing.
|
|
// (This file had a lowercase E in Thunderbird 65 and earlier.)
|
|
var { ExtensionSupport } = ChromeUtils.import(
|
|
"resource:///modules/ExtensionSupport.jsm"
|
|
);
|
|
|
|
var SysTrayX = {
|
|
MESSAGE_COUNT_TYPE_UNREAD: 0,
|
|
MESSAGE_COUNT_TYPE_NEW: 1,
|
|
|
|
countType: this.MESSAGE_COUNT_TYPE_UNREAD,
|
|
|
|
initialized: false,
|
|
|
|
accounts: undefined,
|
|
filters: undefined,
|
|
|
|
currentMsgCount: null,
|
|
newMsgCount: null,
|
|
|
|
callback: undefined,
|
|
|
|
init: function () {
|
|
if (this.initialized) {
|
|
console.warn("Folder listener already initialized");
|
|
return;
|
|
}
|
|
|
|
console.log("Initializing folder listener");
|
|
|
|
// Get the mail accounts using MailServices
|
|
this.getAccounts();
|
|
|
|
// Trigger first count
|
|
this.updateMsgCountWithCb();
|
|
|
|
// Start listener
|
|
MailServices.mailSession.AddFolderListener(
|
|
this.mailSessionListener,
|
|
this.mailSessionListener.notificationFlags
|
|
);
|
|
|
|
this.initialized = true;
|
|
},
|
|
|
|
shutdown: function () {
|
|
if (!this.initialized) {
|
|
return;
|
|
}
|
|
|
|
log.log("Shutting down folder listener");
|
|
|
|
// Stop listener
|
|
MailServices.mailSession.RemoveFolderListener(this.mailSessionListener);
|
|
|
|
this.initialized = false;
|
|
},
|
|
|
|
setCountType: function (type) {
|
|
if (type === 0) {
|
|
this.countType = this.MESSAGE_COUNT_TYPE_UNREAD;
|
|
} else if (type === 1) {
|
|
this.countType = this.MESSAGE_COUNT_TYPE_NEW;
|
|
} else console.log("Unknown count type");
|
|
|
|
// Update count
|
|
this.updateMsgCountWithCb();
|
|
},
|
|
|
|
setFilters: function (filters) {
|
|
this.filters = filters;
|
|
|
|
// Update count
|
|
this.updateMsgCountWithCb();
|
|
},
|
|
|
|
mailSessionListener: {
|
|
notificationFlags:
|
|
Ci.nsIFolderListener.propertyFlagChanged |
|
|
Ci.nsIFolderListener.boolPropertyChanged |
|
|
Ci.nsIFolderListener.intPropertyChanged,
|
|
|
|
OnItemIntPropertyChanged(item, property, oldValue, newValue) {
|
|
// TotalUnreadMessages, BiffState (per server)
|
|
/*
|
|
console.debug(
|
|
"OnItemIntPropertyChanged " +
|
|
property +
|
|
" for folder " +
|
|
item.prettyName +
|
|
" was " +
|
|
oldValue +
|
|
" became " +
|
|
newValue +
|
|
" NEW MESSAGES=" +
|
|
item.getNumNewMessages(true)
|
|
);
|
|
*/
|
|
this.onMsgCountChange(item, property, oldValue, newValue);
|
|
},
|
|
|
|
OnItemBoolPropertyChanged: function (item, property, oldValue, newValue) {
|
|
// NewMessages (per folder)
|
|
/*
|
|
console.debug(
|
|
"OnItemBoolPropertyChanged " +
|
|
property +
|
|
" for folder " +
|
|
item.prettyName +
|
|
" was " +
|
|
oldValue +
|
|
" became " +
|
|
newValue +
|
|
" NEW MESSAGES=" +
|
|
item.getNumNewMessages(true)
|
|
);
|
|
*/
|
|
this.onMsgCountChange(item, property, oldValue, newValue);
|
|
},
|
|
|
|
OnItemPropertyFlagChanged: function (item, property, oldFlag, newFlag) {
|
|
/*
|
|
console.debug(
|
|
"OnItemPropertyFlagChanged" +
|
|
property +
|
|
" for " +
|
|
item +
|
|
" was " +
|
|
oldFlag +
|
|
" became " +
|
|
newFlag
|
|
);
|
|
*/
|
|
this.onMsgCountChange(item, property, oldFlag, newFlag);
|
|
},
|
|
|
|
onMsgCountChange: function (item, property, oldValue, newValue) {
|
|
let msgCountType = SysTrayX.countType;
|
|
|
|
let prop = property.toString();
|
|
if (
|
|
prop === "TotalUnreadMessages" &&
|
|
msgCountType === SysTrayX.MESSAGE_COUNT_TYPE_UNREAD
|
|
) {
|
|
SysTrayX.updateMsgCountWithCb();
|
|
} else {
|
|
if (
|
|
prop === "NewMessages" &&
|
|
msgCountType === SysTrayX.MESSAGE_COUNT_TYPE_NEW
|
|
) {
|
|
if (oldValue === true && newValue === false) {
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=727460
|
|
item.setNumNewMessages(0);
|
|
}
|
|
SysTrayX.updateMsgCountWithCb();
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
updateMsgCountWithCb(callback) {
|
|
if (callback === undefined || !callback) {
|
|
callback = function (currentMsgCount, newMsgCount) {
|
|
// default
|
|
// .updateIcon(newMsgCount);
|
|
console.debug("Update icon: " + newMsgCount);
|
|
|
|
if (SysTrayX.callback) {
|
|
SysTrayX.callback("unread-changed", newMsgCount);
|
|
}
|
|
};
|
|
}
|
|
|
|
let msgCountType = SysTrayX.countType;
|
|
if (msgCountType === SysTrayX.MESSAGE_COUNT_TYPE_UNREAD) {
|
|
this.countMessages("UnreadMessages");
|
|
} else if (msgCountType === SysTrayX.MESSAGE_COUNT_TYPE_NEW) {
|
|
this.countMessages("HasNewMessages");
|
|
} else console.error("Unknown message count type");
|
|
|
|
// currentMsgCount and newMsgCount may be integers or bool, which do
|
|
// also support comparison operations
|
|
callback.call(this, this.currentMsgCount, this.newMsgCount);
|
|
this.currentMsgCount = this.newMsgCount;
|
|
},
|
|
|
|
countMessages(countType) {
|
|
console.debug("countMessages: " + countType);
|
|
|
|
this.newMsgCount = 0;
|
|
for (let accountServer of this.accounts) {
|
|
// if (accountServer.type === ACCOUNT_SERVER_TYPE_IM) {
|
|
// continue;
|
|
// }
|
|
|
|
// if (excludedAccounts.indexOf(accountServer.key) >= 0)
|
|
// {
|
|
// continue;
|
|
// }
|
|
|
|
this.applyToSubfolders(
|
|
accountServer.prettyName,
|
|
accountServer.rootFolder,
|
|
true,
|
|
function (folder) {
|
|
this.msgCountIterate(countType, accountServer.prettyName, folder);
|
|
}
|
|
);
|
|
}
|
|
|
|
console.debug("Total " + countType + " = " + this.newMsgCount);
|
|
},
|
|
|
|
applyToSubfolders(account, folder, recursive, fun) {
|
|
if (folder.hasSubFolders) {
|
|
let subFolders = folder.subFolders;
|
|
while (subFolders.hasMoreElements()) {
|
|
let subFolder = subFolders.getNext().QueryInterface(Ci.nsIMsgFolder);
|
|
if (recursive && subFolder.hasSubFolders)
|
|
this.applyToSubfoldersRecursive(account, subFolder, recursive, fun);
|
|
else fun.call(this, subFolder);
|
|
}
|
|
}
|
|
},
|
|
|
|
applyToSubfoldersRecursive(account, folder, recursive, fun) {
|
|
fun.call(this, folder);
|
|
this.applyToSubfolders(account, folder, recursive, fun);
|
|
},
|
|
|
|
msgCountIterate(type, account, folder) {
|
|
const match = SysTrayX.filters?.filter(
|
|
(filter) =>
|
|
filter.folder.accountName === account &&
|
|
filter.folder.name === folder.prettyName
|
|
);
|
|
|
|
const count = match
|
|
? match.length > 0
|
|
: folder.getFlag(Ci.nsMsgFolderFlags.Inbox);
|
|
|
|
if (count) {
|
|
SysTrayX["add" + type](folder);
|
|
}
|
|
},
|
|
|
|
addUnreadMessages(folder) {
|
|
let folderUnreadMsgCount = folder["getNumUnread"](false);
|
|
|
|
console.debug(
|
|
"folder: " +
|
|
folder.prettyName +
|
|
" folderUnreadMsgCount= " +
|
|
folderUnreadMsgCount
|
|
);
|
|
|
|
/* nsMsgDBFolder::GetNumUnread basically returns mNumUnreadMessages +
|
|
mNumPendingUnreadMessages, while mNumPendingUnreadMessages may get -1
|
|
when updated from the cache. Which means getNumUnread might return -1. */
|
|
if (folderUnreadMsgCount > 0) {
|
|
this.newMsgCount += folderUnreadMsgCount;
|
|
}
|
|
},
|
|
|
|
addHasNewMessages(folder) {
|
|
let folderNewMsgCount = folder.hasNewMessages;
|
|
|
|
console.debug(
|
|
"folder: " + folder.prettyName + " hasNewMessages= " + folderNewMsgCount
|
|
);
|
|
|
|
this.newMsgCount = this.newMsgCount || folderNewMsgCount;
|
|
},
|
|
|
|
getAccounts() {
|
|
console.debug("getAccounts");
|
|
|
|
let accountServers = [];
|
|
for (let accountServer of fixIterator(
|
|
MailServices.accounts.accounts,
|
|
Ci.nsIMsgAccount
|
|
)) {
|
|
accountServers.push(accountServer.incomingServer);
|
|
}
|
|
|
|
for (let i = 0, len = accountServers.length; i < len; ++i) {
|
|
console.debug(
|
|
"ACCOUNT: " +
|
|
accountServers[i].prettyName +
|
|
" type: " +
|
|
accountServers[i].type +
|
|
" key: " +
|
|
accountServers[i].key.toString()
|
|
);
|
|
}
|
|
|
|
// Store the accounts
|
|
this.accounts = accountServers;
|
|
},
|
|
|
|
add(callback) {
|
|
this.callback = callback;
|
|
},
|
|
|
|
remove(callback) {
|
|
this.callback = undefined;
|
|
},
|
|
};
|
|
|
|
// This object is just what we're using to listen for toolbar clicks. The implementation isn't
|
|
// what this example is about, but you might be interested as it's a common pattern. We count the
|
|
// number of callbacks waiting for events so that we're only listening if we need to be.
|
|
var windowListener = new (class extends ExtensionCommon.EventEmitter {
|
|
constructor() {
|
|
super();
|
|
this.callbackCount = 0;
|
|
}
|
|
|
|
handleEvent(event) {
|
|
let toolbar = event.target.closest("toolbar");
|
|
windowListener.emit(
|
|
"toolbar-clicked",
|
|
toolbar.id,
|
|
event.clientX,
|
|
event.clientY
|
|
);
|
|
}
|
|
|
|
add(callback) {
|
|
this.on("toolbar-clicked", callback);
|
|
this.callbackCount++;
|
|
|
|
if (this.callbackCount == 1) {
|
|
ExtensionSupport.registerWindowListener("changeFolderListener", {
|
|
chromeURLs: [
|
|
"chrome://messenger/content/messenger.xhtml",
|
|
"chrome://messenger/content/messenger.xul",
|
|
],
|
|
onLoadWindow: function (window) {
|
|
let toolbox = window.document.getElementById("mail-toolbox");
|
|
toolbox.addEventListener("click", windowListener.handleEvent);
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
remove(callback) {
|
|
this.off("toolbar-clicked", callback);
|
|
this.callbackCount--;
|
|
|
|
if (this.callbackCount == 0) {
|
|
for (let window of ExtensionSupport.openWindows) {
|
|
if (
|
|
[
|
|
"chrome://messenger/content/messenger.xhtml",
|
|
"chrome://messenger/content/messenger.xul",
|
|
].includes(window.location.href)
|
|
) {
|
|
let toolbox = window.document.getElementById("mail-toolbox");
|
|
toolbox.removeEventListener("click", this.handleEvent);
|
|
}
|
|
}
|
|
ExtensionSupport.unregisterWindowListener("changeFolderListener");
|
|
}
|
|
}
|
|
})();
|