diff --git a/packages/desktopjs-electron/src/electron.ts b/packages/desktopjs-electron/src/electron.ts index e979f8e1..bf82a196 100644 --- a/packages/desktopjs-electron/src/electron.ts +++ b/packages/desktopjs-electron/src/electron.ts @@ -5,7 +5,7 @@ import { registerContainer, ContainerWindow, PersistedWindowLayout, Rectangle, Container, WebContainerBase, ScreenManager, Display, Point, ObjectTransform, PropertyMap, NotificationOptions, ContainerNotification, - TrayIconDetails, MenuItem, Guid, MessageBus, MessageBusSubscription, MessageBusOptions + TrayIconDetails, MenuItem, Guid, MessageBus, MessageBusSubscription, MessageBusOptions, GlobalShortcutManager } from "@morgan-stanley/desktopjs"; registerContainer("Electron", { @@ -363,6 +363,8 @@ export class ElectronContainer extends WebContainerBase { } this.screen = new ElectronDisplayManager(this.electron); + + this.globalShortcut = new ElectronGlobalShortcutManager(this.electron); } protected createMessageBus() : MessageBus { @@ -728,3 +730,40 @@ class ElectronDisplayManager implements ScreenManager { }); } } + +/** @private */ +class ElectronGlobalShortcutManager extends GlobalShortcutManager { + private readonly electron: any; + + public constructor(electron: any) { + super(); + this.electron = electron; + } + + public register(shortcut: string, callback: () => void): Promise { + return new Promise((resolve, reject) => { + this.electron.globalShortcut.register(shortcut, callback); + resolve(); + }); + } + + public isRegistered(shortcut: string): Promise { + return new Promise((resolve, reject) => { + resolve(this.electron.globalShortcut.isRegistered(shortcut)); + }); + } + + public unregister(shortcut: string): Promise { + return new Promise((resolve, reject) => { + this.electron.globalShortcut.unregister(shortcut); + resolve(); + }); + } + + public unregisterAll(): Promise { + return new Promise((resolve, reject) => { + this.electron.globalShortcut.unregisterAll(); + resolve(); + }); + } +} \ No newline at end of file diff --git a/packages/desktopjs-electron/tests/electron.spec.ts b/packages/desktopjs-electron/tests/electron.spec.ts index 065f2eed..8f700229 100644 --- a/packages/desktopjs-electron/tests/electron.spec.ts +++ b/packages/desktopjs-electron/tests/electron.spec.ts @@ -1003,4 +1003,36 @@ describe("ElectronDisplayManager", () => { expect(point).toEqual({ x: 1, y: 2}); }).then(done); }); +}); + +describe("ElectronGlobalShortcutManager", () => { + describe("invokes underlying Electron", () => { + let electron; + let container; + + beforeEach(() => { + electron = { globalShortcut: jasmine.createSpyObj("Electron", ["register", "unregister", "isRegistered", "unregisterAll"]) }; + container = new ElectronContainer(electron); + }); + + it ("register", () => { + container.globalShortcut.register("shortcut", () => {}); + expect(electron.globalShortcut.register).toHaveBeenCalledWith("shortcut", jasmine.any(Function)); + }); + + it ("unregister", () => { + container.globalShortcut.unregister("shortcut"); + expect(electron.globalShortcut.unregister).toHaveBeenCalledWith("shortcut"); + }); + + it ("isRegistered", () => { + container.globalShortcut.isRegistered("shortcut"); + expect(electron.globalShortcut.isRegistered).toHaveBeenCalledWith("shortcut"); + }); + + it ("unregisterAll", () => { + container.globalShortcut.unregisterAll(); + expect(electron.globalShortcut.unregisterAll).toHaveBeenCalledWith(); + }); + }); }); \ No newline at end of file diff --git a/packages/desktopjs-openfin/src/openfin.ts b/packages/desktopjs-openfin/src/openfin.ts index bec937dd..9cc87ce3 100644 --- a/packages/desktopjs-openfin/src/openfin.ts +++ b/packages/desktopjs-openfin/src/openfin.ts @@ -5,7 +5,8 @@ import { registerContainer, ContainerWindow, PersistedWindowLayout, Rectangle, Container, WebContainerBase, ScreenManager, Display, Point, ObjectTransform, PropertyMap, NotificationOptions, ContainerNotification, - TrayIconDetails, MenuItem, Guid, MessageBus, MessageBusSubscription, MessageBusOptions, EventArgs + TrayIconDetails, MenuItem, Guid, MessageBus, MessageBusSubscription, MessageBusOptions, EventArgs, + GlobalShortcutManager } from "@morgan-stanley/desktopjs"; registerContainer("OpenFin", { @@ -416,6 +417,12 @@ export class OpenFinContainer extends WebContainerBase { }); this.screen = new OpenFinDisplayManager(this.desktop); + + if (this.desktop.GlobalHotkey) { + this.globalShortcut = new OpenFinGlobalShortcutManager(this.desktop); + } else { + console.warn("Global shortcuts require minimum OpenFin runtime of 9.61.32.34"); + } } protected createMessageBus(): MessageBus { @@ -728,3 +735,37 @@ class OpenFinDisplayManager implements ScreenManager { }); } } + +/** @private */ +class OpenFinGlobalShortcutManager extends GlobalShortcutManager { + private readonly desktop: any; + + public constructor(desktop: any) { + super(); + this.desktop = desktop; + } + + public register(shortcut: string, callback: () => void): Promise { + return new Promise((resolve, reject) => { + this.desktop.GlobalHotkey.register(shortcut, callback, resolve, reject); + }); + } + + public isRegistered(shortcut: string): Promise { + return new Promise((resolve, reject) => { + this.desktop.GlobalHotkey.isRegistered(shortcut, resolve, reject); + }); + } + + public unregister(shortcut: string): Promise { + return new Promise((resolve, reject) => { + this.desktop.GlobalHotkey.unregister(shortcut, resolve, reject); + }); + } + + public unregisterAll(): Promise { + return new Promise((resolve, reject) => { + this.desktop.GlobalHotkey.unregisterAll(resolve, reject); + }); + } +} \ No newline at end of file diff --git a/packages/desktopjs-openfin/tests/openfin.spec.ts b/packages/desktopjs-openfin/tests/openfin.spec.ts index fac1c512..36925bdb 100644 --- a/packages/desktopjs-openfin/tests/openfin.spec.ts +++ b/packages/desktopjs-openfin/tests/openfin.spec.ts @@ -21,7 +21,7 @@ class MockDesktop { } } } - + GlobalHotkey: any; Window: any = MockWindow; Notification(): any { return {}; } InterApplicationBus: any = new MockInterApplicationBus(); @@ -30,6 +30,7 @@ class MockDesktop { return MockDesktop.application; } } + } class MockInterApplicationBus { @@ -887,4 +888,50 @@ describe("OpenFinDisplayManager", () => { expect(point).toEqual({ x: 1, y: 2}); }).then(done); }); +}); + +describe("OpenfinGlobalShortcutManager", () => { + let desktop; + let container; + + beforeEach(() => { + desktop = new MockDesktop() + }); + + it ("Unavailable in OpenFin is unavailable on container", () => { + delete desktop.GlobalHotkey; + const container = new OpenFinContainer(desktop); + spyOn(console, "warn").and.stub(); + expect(container.globalShortcut).toBeUndefined(); + expect(console.warn).toHaveBeenCalledWith("Global shortcuts require minimum OpenFin runtime of 9.61.32.34"); + }); + + describe("invokes underlying OpenFin", () => { + beforeEach(() => { + desktop.GlobalHotkey = jasmine.createSpyObj("GlobalHotKey", ["register", "unregister", "isRegistered", "unregisterAll"]); + container = new OpenFinContainer(desktop); + }); + + it ("register", () => { + container.globalShortcut.register("shortcut", () => {}); + expect(desktop.GlobalHotkey.register).toHaveBeenCalledWith("shortcut", jasmine.any(Function), jasmine.any(Function), jasmine.any(Function)); + }); + + it ("unregister", () => { + container.globalShortcut.unregister("shortcut"); + expect(desktop.GlobalHotkey.unregister).toHaveBeenCalledWith("shortcut", jasmine.any(Function), jasmine.any(Function)); + }); + + it ("isRegistered", (done) => { + desktop.GlobalHotkey.isRegistered.and.callFake((shortcut, resolve, reject) => resolve(true)); + container.globalShortcut.isRegistered("shortcut").then(() => { + expect(desktop.GlobalHotkey.isRegistered).toHaveBeenCalledWith("shortcut", jasmine.any(Function), jasmine.any(Function)); + }).then(done); + }); + + it ("unregisterAll", () => { + container.globalShortcut.unregisterAll(); + expect(desktop.GlobalHotkey.unregisterAll).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function)); + }); + }); }); \ No newline at end of file diff --git a/packages/desktopjs/src/container.ts b/packages/desktopjs/src/container.ts index f5fe1909..cbff62a1 100644 --- a/packages/desktopjs/src/container.ts +++ b/packages/desktopjs/src/container.ts @@ -10,6 +10,7 @@ import { MessageBus } from "./ipc"; import { TrayIconDetails } from "./tray"; import { MenuItem } from "./menu"; import { Guid } from "./guid"; +import { GlobalShortcutManager } from "./shortcut"; export type ContainerEventType = "window-created" | @@ -104,6 +105,11 @@ export abstract class Container extends EventEmitter implements ContainerWindowM */ public screen: ScreenManager; + /** + * Register/unregister global keyboard shortcut with the operating system. + */ + public globalShortcut: GlobalShortcutManager; + public addListener(eventName: ContainerEventType, listener: (event: ContainerEventArgs) => void): this { // tslint:disable-line return super.addListener(eventName, listener); } diff --git a/packages/desktopjs/src/desktop.ts b/packages/desktopjs/src/desktop.ts index a7481ede..bde6b095 100644 --- a/packages/desktopjs/src/desktop.ts +++ b/packages/desktopjs/src/desktop.ts @@ -15,6 +15,7 @@ export * from "./registry"; export * from "./screen"; export * from "./tray"; export * from "./window"; +export * from "./shortcut"; export * from "./Default/default"; export const version: string = "PACKAGE_VERSION"; diff --git a/packages/desktopjs/src/shortcut.ts b/packages/desktopjs/src/shortcut.ts new file mode 100644 index 00000000..bc43653c --- /dev/null +++ b/packages/desktopjs/src/shortcut.ts @@ -0,0 +1,27 @@ +/** + * @module @morgan-stanley/desktopjs + */ + +/** Register/unregister a glboal keyboard shortcut with the operating system so that you + * can customize the operations for various shortcuts. + */ +export abstract class GlobalShortcutManager { // tslint:disable-line + /** Registers a global shortcut. + * @param shortcut {string} [Accelerator]{@link https://electronjs.org/docs/api/accelerator} + */ + public abstract register(shortcut: string, callback: () => void): Promise; + + /** Checks if a given shortcut has been registered. + * @param shortcut {string} [Accelerator]{@link https://electronjs.org/docs/api/accelerator} + * @returns {Promise} A Promise that resolves to a boolean whether the shortcut is already registered. + */ + public abstract isRegistered(shortcut: string): Promise; + + /** Removes a previously registered shortcut. + * @param shortcut {string} [Accelerator]{@link https://electronjs.org/docs/api/accelerator} + */ + public abstract unregister(shortcut: string): Promise; + + /** Removes all registered shortcuts for the current application. */ + public abstract unregisterAll(): Promise; +} \ No newline at end of file