Skip to content

Commit

Permalink
Add global keyboard shortcut support with operating system (#214)
Browse files Browse the repository at this point in the history
  • Loading branch information
bingenito authored Feb 4, 2019
1 parent d4ee23d commit 59b3a11
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 3 deletions.
41 changes: 40 additions & 1 deletion packages/desktopjs-electron/src/electron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down Expand Up @@ -363,6 +363,8 @@ export class ElectronContainer extends WebContainerBase {
}

this.screen = new ElectronDisplayManager(this.electron);

this.globalShortcut = new ElectronGlobalShortcutManager(this.electron);
}

protected createMessageBus() : MessageBus {
Expand Down Expand Up @@ -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<void> {
return new Promise<void>((resolve, reject) => {
this.electron.globalShortcut.register(shortcut, callback);
resolve();
});
}

public isRegistered(shortcut: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
resolve(this.electron.globalShortcut.isRegistered(shortcut));
});
}

public unregister(shortcut: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.electron.globalShortcut.unregister(shortcut);
resolve();
});
}

public unregisterAll(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.electron.globalShortcut.unregisterAll();
resolve();
});
}
}
32 changes: 32 additions & 0 deletions packages/desktopjs-electron/tests/electron.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
43 changes: 42 additions & 1 deletion packages/desktopjs-openfin/src/openfin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<void> {
return new Promise<void>((resolve, reject) => {
this.desktop.GlobalHotkey.register(shortcut, callback, resolve, reject);
});
}

public isRegistered(shortcut: string): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
this.desktop.GlobalHotkey.isRegistered(shortcut, resolve, reject);
});
}

public unregister(shortcut: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.desktop.GlobalHotkey.unregister(shortcut, resolve, reject);
});
}

public unregisterAll(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.desktop.GlobalHotkey.unregisterAll(resolve, reject);
});
}
}
49 changes: 48 additions & 1 deletion packages/desktopjs-openfin/tests/openfin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MockDesktop {
}
}
}

GlobalHotkey: any;
Window: any = MockWindow;
Notification(): any { return {}; }
InterApplicationBus: any = new MockInterApplicationBus();
Expand All @@ -30,6 +30,7 @@ class MockDesktop {
return MockDesktop.application;
}
}

}

class MockInterApplicationBus {
Expand Down Expand Up @@ -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));
});
});
});
6 changes: 6 additions & 0 deletions packages/desktopjs/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" |
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions packages/desktopjs/src/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
27 changes: 27 additions & 0 deletions packages/desktopjs/src/shortcut.ts
Original file line number Diff line number Diff line change
@@ -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<void>;

/** Checks if a given shortcut has been registered.
* @param shortcut {string} [Accelerator]{@link https://electronjs.org/docs/api/accelerator}
* @returns {Promise<boolean>} A Promise that resolves to a boolean whether the shortcut is already registered.
*/
public abstract isRegistered(shortcut: string): Promise<boolean>;

/** Removes a previously registered shortcut.
* @param shortcut {string} [Accelerator]{@link https://electronjs.org/docs/api/accelerator}
*/
public abstract unregister(shortcut: string): Promise<void>;

/** Removes all registered shortcuts for the current application. */
public abstract unregisterAll(): Promise<void>;
}

0 comments on commit 59b3a11

Please sign in to comment.