diff --git a/webext/background.js b/webext/background.js index dd5ec2a..fa791e3 100644 --- a/webext/background.js +++ b/webext/background.js @@ -1,4 +1,5 @@ var SysTrayX = { + mainWindowId: undefined, startupState: undefined, restorePositions: false, @@ -349,10 +350,31 @@ SysTrayX.Messaging = { deleteFolderFromFilters(deletedFolder); }, - onCloseButton: function () { + + onNewWindow: async function () { + const window = await browser.windows.getCurrent(); + + console.debug("onNewWindow: " + JSON.stringify( window ) ); + }, + + onCloseButton: async function () { //console.debug("Minimize all") - SysTrayX.Link.postSysTrayXMessage({ window: "minimized_all" }); + const window = await browser.windows.getCurrent(); + + console.debug("onCloseButton2 Window: " + JSON.stringify( window ) ); + + if( window.id === SysTrayX.mainWindowId ) { + SysTrayX.Link.postSysTrayXMessage({ window: "minimized_all" }); + } else { +// browser.windows.remove( window.id ); + + browser.windows.update( window.id, { + state: "docked", +// state: "minimized", + }); + } + /* browser.windows.update(browser.windows.WINDOW_ID_CURRENT, { state: "minimized", @@ -970,6 +992,9 @@ SysTrayX.Window = { }; async function start() { + // Setup the link first + SysTrayX.Link.init(); + // Set platform SysTrayX.Info.platformInfo = await browser.runtime .getPlatformInfo() @@ -1026,11 +1051,55 @@ async function start() { browser.windowEvent.setCloseType(Number(SysTrayX.Messaging.closeType)); // Intercept close button? + /* if (SysTrayX.Messaging.closeType !== "0") { browser.windowEvent.onCloseButtonClick.addListener( SysTrayX.Messaging.onCloseButton ); } + */ + + // Get main window id + const window = await browser.windows.getCurrent(); + SysTrayX.mainWindowId = window.id; + + console.debug( "Main window ID: " + SysTrayX.mainWindowId ); + + // Get all window ids + const windows = await browser.windows.getAll(); + + console.debug( "All window IDs: " + JSON.stringify( windows.map( (win) => win.id ) ) ); + + // Sent it to the companion app + SysTrayX.Link.postSysTrayXMessage( { + windowList: { + main: SysTrayX.mainWindowId, + list: windows.map( (win) => win.id ) + } + }); + + + + // Get the window id + const id = browser.windowHandler.getWindowId(SysTrayX.mainWindowId); + + console.debug("Main window real ID: " + id); + + + + // Get the close type + browser.windowEvent2.setCloseType( Number( SysTrayX.Messaging.closeType ) ); + + // Intercept close button? + if (SysTrayX.Messaging.closeType !== "0") { + // Intercept new window + browser.windowEvent2.onNewWindow.addListener( SysTrayX.Messaging.onNewWindow ); + + browser.windowEvent2.onCloseButtonClick.addListener( + SysTrayX.Messaging.onCloseButton + ); + } + // Hide the default icon const hideDefaultIcon = await getHideDefaultIcon(); @@ -1060,9 +1129,6 @@ async function start() { const getIconPromise = () => new Promise((res) => res(getIcon())); await getIconPromise(); - // Setup the link first - SysTrayX.Link.init(); - // Main start SysTrayX.Messaging.init(); } diff --git a/webext/js/experimental.js b/webext/js/experimental.js new file mode 100644 index 0000000..757458f --- /dev/null +++ b/webext/js/experimental.js @@ -0,0 +1,159 @@ +/* eslint-disable object-shorthand */ + +"use strict"; + +// Using a closure to not leak anything but the API to the outside world. +(function (exports) { + + // 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"); + + // A helpful class for listening to windows opening and closing. + var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm"); + + /** + * 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. + * + * An EventEmitter has the following basic functions: + * + * EventEmitter.on(emitterName, callback) + * Registers a callback for a custom emitter. + * + * EventEmitter.off(emitterName, callback) + * Unregisters a callback for a custom emitter. + * + * EventEmitter.emit(emitterName) + * Emit a custom emitter, all provided parameters will be forwarded to the registered callbacks. + */ + + let windowListener; + + class WindowListener extends ExtensionCommon.EventEmitter { + constructor(extension) { + super(); + this.extension = extension; + this.callbackCount = 0; + } + + get listenerId() { + return `experiment_listener_${this.extension.uuid}_${this.extension.instanceId}`; + } + + handleEvent(event) { + // Only react to the secondary mouse button. + if (event.button == 0) { + let toolbar = event.target.closest("toolbar"); + // Emit "toolbar-clicked" and send toolbar.id, event.clientX, event.clientY to + // the registered callbacks. + windowListener.emit("toolbar-clicked", toolbar.id, event.clientX, event.clientY); + } + } + + add(callback) { + // Registering the callback for "toolbar-clicked". + this.on("toolbar-clicked", callback); + this.callbackCount++; + + if (this.callbackCount == 1) { + ExtensionSupport.registerWindowListener(this.listenerId, { + 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) { + // Un-Registering the callback for "toolbar-clicked". + 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(this.listenerId); + } + } + }; + + + // This is the important part. It implements the functions and events defined + // in the schema.json. The name must match what you've been using so far, + // "ExampleAPI" in this case. + class ExampleAPI extends ExtensionCommon.ExtensionAPI { + // An alternative to defining a constructor here, is to use the onStartup + // event. However, this causes the API to be instantiated directly after the + // add-on has been loaded, not when the API is first used. Depends on what is + // desired. + constructor(extension) { + super(extension); + windowListener = new WindowListener(extension); + } + + getAPI(context) { + return { + // This key must match the class name. + ExampleAPI: { + + // A function. + sayHello: async function (name) { + Services.wm.getMostRecentWindow("mail:3pane").alert(name); + }, + + // An event. Most of this is boilerplate you don't need to worry about, just copy it. + onToolbarClick: new ExtensionCommon.EventManager({ + context, + name: "ExampleAPI.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(), + + }, + }; + } + + onShutdown(isAppShutdown) { + // This function is called if the extension is disabled or removed, or Thunderbird closes. + // We usually do not have to do any cleanup, if Thunderbird is shutting down entirely + if (isAppShutdown) { + return; + } + + console.log("Goodbye world!"); + } + }; + + // Export the api by assigning in to the exports parameter of the anonymous closure + // function, which is the global this. + exports.ExampleAPI = ExampleAPI; + +})(this) diff --git a/webext/js/windowEvent2.js b/webext/js/windowEvent2.js new file mode 100644 index 0000000..f424dfa --- /dev/null +++ b/webext/js/windowEvent2.js @@ -0,0 +1,278 @@ +/* eslint-disable object-shorthand */ + +"use strict"; + +// Using a closure to not leak anything but the API to the outside world. +(function (exports) { + + // 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"); + + // A helpful class for listening to windows opening and closing. + var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm"); + + /** + * 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. + * + * An EventEmitter has the following basic functions: + * + * EventEmitter.on(emitterName, callback) + * Registers a callback for a custom emitter. + * + * EventEmitter.off(emitterName, callback) + * Unregisters a callback for a custom emitter. + * + * EventEmitter.emit(emitterName) + * Emit a custom emitter, all provided parameters will be forwarded to the registered callbacks. + */ + + let windowListener; + + class WindowListener extends ExtensionCommon.EventEmitter { + constructor(extension) { + super(); + this.extension = extension; + this.onNewWindowCallbackCount = 0; + this.onCloseButtonClickCallbackCount = 0; + + this.closeType = this.MESSAGE_CLOSE_TYPE_DEFAULT; + this.oldClose = undefined; + } + + get listenerIdNewWindow() { + return `window_event_listener_new_window_${this.extension.uuid}_${this.extension.instanceId}`; + } + + get listenerIdCloseButton() { + return `window_event_listener_close_button_${this.extension.uuid}_${this.extension.instanceId}`; + } + + setCloseType(closeType) { + if (closeType === 0) { + this.closeType = this.MESSAGE_CLOSE_TYPE_DEFAULT; + } else if (closeType === 1) { + this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_MAIN_TRAY_CLOSE_CHILDREN; + } else if (closeType === 2) { + this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_ALL_TRAY; + } else if (closeType === 3) { + this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_MAIN_CLOSE_CHILDREN; + } else if (closeType === 4) { + this.closeType = this.MESSAGE_CLOSE_TYPE_MIN_ALL; + } else console.log("Unknown close type: " + closeType); + } + + addOnNewWindow( callback ) { + // Registering the callback for "new-window". + this.on("new-window", callback); + this.onNewWindowCallbackCount++; + + if (this.onNewWindowCallbackCount == 1) { + ExtensionSupport.registerWindowListener( this.listenerIdNewWindow, { + chromeURLs: [ + "chrome://messenger/content/messenger.xhtml", + "chrome://messenger/content/messenger.xul", + ], + onLoadWindow: function ( window ) { + + windowListener.emit( "new-window" ); + + console.log("New window added"); + }, + }); + } + } + + removeOnNewWindow( callback ) { + // Un-Registering the callback for "new-window". + this.off("new-window", callback); + this.onNewWindowCallbackCount--; + + if (this.onNewWindowCallbackCount == 0) { + for (let window of ExtensionSupport.openWindows) { + if ( [ + "chrome://messenger/content/messenger.xhtml", + "chrome://messenger/content/messenger.xul", + ].includes( window.location.href ) ) { + console.log( "New window listener removed" ); + } + } + ExtensionSupport.unregisterWindowListener( this.listenerIdNewWindow ); + } + } + + + onCloseButton( event ) { + if ( event ) event.preventDefault(); + windowListener.emit( "close-clicked" ); + return true; + } + + addOnCloseButtonClick( callback, context, window ) { + // Registering the callback for "close-clicked". + this.on( "close-clicked", callback ); + this.onCloseButtonClickCallbackCount++; + + if (this.onCloseButtonClickCallbackCount == 1) { + ExtensionSupport.registerWindowListener( this.listenerIdCloseButton, { + chromeURLs: [ + "chrome://messenger/content/messenger.xhtml", + "chrome://messenger/content/messenger.xul", + ], + onLoadWindow: function ( window ) { + window.addEventListener( + "close", + windowListener.onCloseButton, + true + ); + + windowListener.oldClose = window.close; + window.close = () => windowListener.onCloseButton( null ); + +// console.debug("Window: " + JSON.stringify(window)); + +/* +console.debug( "Window id1: " + window.id ); +console.debug( "Window id1: " + window.windowId ); + + let windowId2 = getInnerWindowID(window); + console.debug( "Window id2: " + windowId2 ); + + // Get a real window from a window ID: + let windowObject = context.extension.windowManager.get(windowId2); +// let windowObject = context.extension.windowManager.get(windowId); + let realWindow = windowObject.window; + + console.debug( "WinObj: " + JSON.stringify( windowObject ) ); + + // Get a window ID from a real window: + const id = context.extension.windowManager.getWrapper(realWindow).id; + + console.debug( "WinObj Real: " + id ); + + // Get all windows: (note this returns a Generator, not an array like the API) +// context.extension.windowManager.getAll(); + + + + +*/ + console.log( "Close listener added" ); + }, + }); + } + } + + removeOnCloseButtonClick( callback ) { + // Un-Registering the callback for "close-clicked". + this.off( "close-clicked", callback ); + this.onCloseButtonClickCallbackCount--; + + if ( this.onCloseButtonClickCallbackCount == 0 ) { + for ( let window of ExtensionSupport.openWindows ) { + if ( [ + "chrome://messenger/content/messenger.xhtml", + "chrome://messenger/content/messenger.xul", + ].includes( window.location.href ) ) { + window.removeEventListener( + "close", + windowListener.onCloseButton, + true + ); + + window.close = windowListener.oldClose; + + console.log( "Close listener removed" ); + } + } + ExtensionSupport.unregisterWindowListener( this.listenerIdCloseButton ); + } + } + }; + + // This is the important part. It implements the functions and events defined + // in the schema.json. The name must match what you've been using so far, + // "windowEvent2" in this case. + class windowEvent2 extends ExtensionCommon.ExtensionAPI { + // An alternative to defining a constructor here, is to use the onStartup + // event. However, this causes the API to be instantiated directly after the + // add-on has been loaded, not when the API is first used. Depends on what is + // desired. + constructor(extension) { + super(extension); + windowListener = new WindowListener(extension); + } + + getAPI(context) { + console.log("windowEvent API started"); + + return { + // This key must match the class name. + windowEvent2: { + setCloseType: async function (type) { + windowListener.setCloseType(type); + }, + + // An event. Most of this is boilerplate you don't need to worry about, just copy it. + onNewWindow: new ExtensionCommon.EventManager({ + context, + name: "windowEvent2.onNewWindow", + // 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) { + return fire.async(); + } + + windowListener.addOnNewWindow(callback); + return function () { + windowListener.removeOnNewWindow(callback); + }; + }, + }).api(), + + // An event. Most of this is boilerplate you don't need to worry about, just copy it. + onCloseButtonClick: new ExtensionCommon.EventManager({ + context, + name: "windowEvent2.onCloseButtonClick", + // 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) { + return fire.async(); + } + + windowListener.addOnCloseButtonClick(callback,context); + return function () { + windowListener.removeOnCloseButtonClick(callback); + }; + }, + }).api(), + + }, + }; + } + + onShutdown(isAppShutdown) { + // This function is called if the extension is disabled or removed, or Thunderbird closes. + // We usually do not have to do any cleanup, if Thunderbird is shutting down entirely + if (isAppShutdown) { + return; + } + + console.log("windowEvent API closed"); + } + }; + + // Export the api by assigning in to the exports parameter of the anonymous closure + // function, which is the global this. + exports.windowEvent2 = windowEvent2; + +})(this) diff --git a/webext/js/windowHandler.js b/webext/js/windowHandler.js new file mode 100644 index 0000000..59a612d --- /dev/null +++ b/webext/js/windowHandler.js @@ -0,0 +1,88 @@ +/* eslint-disable object-shorthand */ + +"use strict"; + +// Using a closure to not leak anything but the API to the outside world. +(function (exports) { + + // 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"); + + // A helpful class for listening to windows opening and closing. + var { ExtensionSupport } = ChromeUtils.import("resource:///modules/ExtensionSupport.jsm"); + + /** + * 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. + * + * An EventEmitter has the following basic functions: + * + * EventEmitter.on(emitterName, callback) + * Registers a callback for a custom emitter. + * + * EventEmitter.off(emitterName, callback) + * Unregisters a callback for a custom emitter. + * + * EventEmitter.emit(emitterName) + * Emit a custom emitter, all provided parameters will be forwarded to the registered callbacks. + */ + + let windowListener; + + class WindowListener extends ExtensionCommon.EventEmitter { + constructor(extension) { + super(); + this.extension = extension; + this.callbackCount = 0; + } + } + + // This is the important part. It implements the functions and events defined + // in the schema.json. The name must match what you've been using so far, + // "windowEvent2" in this case. + class windowHandler extends ExtensionCommon.ExtensionAPI { + // An alternative to defining a constructor here, is to use the onStartup + // event. However, this causes the API to be instantiated directly after the + // add-on has been loaded, not when the API is first used. Depends on what is + // desired. + constructor(extension) { + super(extension); + windowListener = new WindowListener(extension); + } + + getAPI(context) { + console.log("windowHandler API started"); + + return { + // This key must match the class name. + windowHandler: { + getWindowId: async function (windowId) { + + // Get a real window from a window ID: + let windowObject = context.extension.windowManager.get(windowId); + let realWindow = windowObject.window; + + // Get a window ID from a real window: + let id = context.extension.windowManager.getWrapper(realWindow).id; + + return id; + } + } + }; + } + + close() { + console.log("windowHandler API closed"); + } + }; + + // Export the api by assigning in to the exports parameter of the anonymous closure + // function, which is the global this. + exports.windowHandler = windowHandler; + +})(this) diff --git a/webext/manifest.json b/webext/manifest.json index a538c34..44015c6 100644 --- a/webext/manifest.json +++ b/webext/manifest.json @@ -39,6 +39,22 @@ "paths": [["windowEvent"]], "script": "js/windowEvent.js" } + }, + "windowEvent2": { + "schema": "schema_windowEvent2.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["windowEvent2"]], + "script": "js/windowEvent2.js" + } + }, + "windowHandler": { + "schema": "schema_windowHandler.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["windowHandler"]], + "script": "js/windowHandler.js" + } } } } diff --git a/webext/schema_experimental.json b/webext/schema_experimental.json new file mode 100644 index 0000000..33a1f94 --- /dev/null +++ b/webext/schema_experimental.json @@ -0,0 +1,46 @@ +[ + { + "namespace": "ExampleAPI", + "functions": [ + { + "name": "sayHello", + "type": "function", + "description": "Says hello to the user.", + "async": true, + "parameters": [ + { + "name": "name", + "type": "string", + "description": "Who to say hello to." + } + ] + } + ], + "events": [ + { + "name": "onToolbarClick", + "type": "function", + "description": "Fires when the user clicks the secondary mouse button anywhere on the toolbar in the main window.", + "parameters": [ + { + "name": "toolbarId", + "type": "string", + "description": "The ID of the toolbar the user clicked." + }, + { + "type": "integer", + "name": "x", + "minimum": 0, + "description": "The X position of the mouse when the user clicked." + }, + { + "type": "integer", + "name": "y", + "minimum": 0, + "description": "The Y position of the mouse when the user clicked." + } + ] + } + ] + } +] diff --git a/webext/schema_windowEvent2.json b/webext/schema_windowEvent2.json new file mode 100644 index 0000000..1cf06cc --- /dev/null +++ b/webext/schema_windowEvent2.json @@ -0,0 +1,35 @@ +[ + { + "namespace": "windowEvent2", + "functions": [ + { + "name": "setCloseType", + "type": "function", + "description": "Set the close type.", + "async": true, + "parameters": [ + { + "type": "integer", + "name": "type", + "minimum": 0, + "maximum": 4 + } + ] + } + ], + "events": [ + { + "name": "onNewWindow", + "type": "function", + "description": "Fires when the user opens a new window.", + "parameters": [] + }, + { + "name": "onCloseButtonClick", + "type": "function", + "description": "Fires when the user clicks on the close button of a window.", + "parameters": [] + } + ] + } +] diff --git a/webext/schema_windowHandler.json b/webext/schema_windowHandler.json new file mode 100644 index 0000000..0d8b035 --- /dev/null +++ b/webext/schema_windowHandler.json @@ -0,0 +1,14 @@ +[ + { + "namespace": "windowHandler", + "functions": [ + { + "name": "getWindowId", + "type": "function", + "description": "Get the window Id", + "async": true, + "parameters": [] + } + ] + } +]