Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add global keyboard shortcut support with operating system #214

Merged
merged 1 commit into from
Feb 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;
}