diff --git a/docs/sdk/desktop/README.md b/docs/sdk/desktop/README.md index b718951d..400a5971 100644 --- a/docs/sdk/desktop/README.md +++ b/docs/sdk/desktop/README.md @@ -12,7 +12,7 @@ The underlying raw api object, this property is exposed but shouldn't be needed. A property that returns true if the application is running in the desktop host. -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; const hosted = desktop.isHostedInDesktop; @@ -24,7 +24,7 @@ const hosted = desktop.isHostedInDesktop; Gets the application name. -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; const appName = await desktop.getAppName(); @@ -36,7 +36,7 @@ const appName = await desktop.getAppName(); Gets the application version. -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; const appVersion = await desktop.getAppVersion(); @@ -48,19 +48,51 @@ const appVersion = await desktop.getAppVersion(); Versions of chrome, node, v8 etc... -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; const versions = await desktop.getVersions(); ``` +## off + +### `off(event: SystemEvents, listener: () => void): void` + +Removes a listener from [System Events](./#system-events). + +```ts +import { desktop } from "@reactivemarkets/desktop-sdk"; + +const listener = () => { + // trigger an action +}; + +desktop.off("lock-screen", listener); +``` + +## on + +### `on(event: SystemEvents, listener: () => void): void` + +Adds a listener to [System Events](./#system-events). + +```ts +import { desktop } from "@reactivemarkets/desktop-sdk"; + +const listener = () => { + // trigger an action +}; + +desktop.on("lock-screen", listener); +``` + ## quit ### `quit(): Promise` Quits the application giving all applications time to close. -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; await desktop.quit(); @@ -72,8 +104,20 @@ await desktop.quit(); Shows the desktop about panel. -```js +```ts import { desktop } from "@reactivemarkets/desktop-sdk"; await desktop.showAboutPanel(); ``` + +## System Events + +| Event | Description | +| ------------- | -------------------------------------------------------- | +| lock-screen | Emitted when the system is about to lock the screen. | +| on-ac | Emitted when the system changes to AC power. | +| on-battery | Emitted when system changes to battery power. | +| resume | Emitted when system is resuming. | +| shutdown | Emitted when the system is about to reboot or shut down. | +| suspend | Emitted when the system is suspending. | +| unlock-screen | Emitted as soon as the systems screen is unlocked. | diff --git a/docs/sdk/globalshortcut/README.md b/docs/sdk/globalshortcut/README.md index 04075301..5990bb71 100644 --- a/docs/sdk/globalshortcut/README.md +++ b/docs/sdk/globalshortcut/README.md @@ -6,7 +6,7 @@ Whether this application has registered the `accelerator`. -```js +```ts import { globalShortcut } from "@reactivemarkets/desktop-sdk"; const isRegistered = await globalShortcut.isRegistered("CommandOrControl+X"); @@ -20,7 +20,7 @@ Registers a global shortcut of `accelerator`. If the accelerator is already registered by another application outside of the desktop, the listener will sliently fail. This is the behavior of operating systems. -```js +```ts import { globalShortcut } from "@reactivemarkets/desktop-sdk"; await globalShortcut.register("CommandOrControl+X", () => { @@ -34,7 +34,7 @@ await globalShortcut.register("CommandOrControl+X", () => { Unregisters the global shortcut of `accelerator`. -```js +```ts import { globalShortcut } from "@reactivemarkets/desktop-sdk"; const listener = () => console.info("CommandOrControl+X invoked"); @@ -49,7 +49,7 @@ await globalShortcut.unregister("CommandOrControl+X", listener); Unregisters all global shortcuts for this application. -```js +```ts import { globalShortcut } from "@reactivemarkets/desktop-sdk"; await globalShortcut.unregisterAll(); diff --git a/docs/sdk/launcher/README.md b/docs/sdk/launcher/README.md index 20464e6d..f33be330 100644 --- a/docs/sdk/launcher/README.md +++ b/docs/sdk/launcher/README.md @@ -6,7 +6,7 @@ Launch the given `configuration`. -```js +```ts import { launcher } from "@reactivemarkets/desktop-sdk"; const configuration = await launcher.launch({ diff --git a/docs/sdk/logger/README.md b/docs/sdk/logger/README.md index 9ebbdacf..254d3ba1 100644 --- a/docs/sdk/logger/README.md +++ b/docs/sdk/logger/README.md @@ -6,7 +6,7 @@ Log at debug level. -```js +```ts import { logger } from "@reactivemarkets/desktop-sdk"; logger.debug("Logs to a structured logger", { @@ -20,7 +20,7 @@ logger.debug("Logs to a structured logger", { Log at error level. -```js +```ts import { logger } from "@reactivemarkets/desktop-sdk"; logger.error("Logs to a structured logger", { @@ -34,7 +34,7 @@ logger.error("Logs to a structured logger", { Log at info level. -```js +```ts import { logger } from "@reactivemarkets/desktop-sdk"; logger.info("Logs to a structured logger", { @@ -48,7 +48,7 @@ logger.info("Logs to a structured logger", { Log at verbose level. -```js +```ts import { logger } from "@reactivemarkets/desktop-sdk"; logger.verbose("Logs to a structured logger", { @@ -62,7 +62,7 @@ logger.verbose("Logs to a structured logger", { Log at warn level. -```js +```ts import { logger } from "@reactivemarkets/desktop-sdk"; logger.warn("Logs to a structured logger", { diff --git a/docs/sdk/registry/README.md b/docs/sdk/registry/README.md index fe6a4a15..1864548d 100644 --- a/docs/sdk/registry/README.md +++ b/docs/sdk/registry/README.md @@ -6,7 +6,7 @@ List all registered applications in the given namespace. -```js +```ts import { registry } from "@reactivemarkets/desktop-sdk"; const applications = await registry.listApplications(); @@ -18,7 +18,7 @@ const applications = await registry.listApplications(); Register a new `configuration` object. -```js +```ts import { registry } from "@reactivemarkets/desktop-sdk"; await registry.register({ @@ -39,7 +39,7 @@ await registry.register({ Unregister a previously registered configuation object. This will not stop anything currently running. -```js +```ts import { registry } from "@reactivemarkets/desktop-sdk"; const applications = await registry.listApplications(); diff --git a/docs/sdk/router/README.md b/docs/sdk/router/README.md index 6a706e0a..4a9b8be5 100644 --- a/docs/sdk/router/README.md +++ b/docs/sdk/router/README.md @@ -6,7 +6,7 @@ Publish a message on a channel. -```js +```ts import { router } from "@reactivemarkets/desktop-sdk"; router.publish("my_channel", { @@ -20,7 +20,7 @@ router.publish("my_channel", { Subscribe to a channel. -```js +```ts import { router } from "@reactivemarkets/desktop-sdk"; router.subscribe("my_channel", (payload) => { @@ -34,7 +34,7 @@ router.subscribe("my_channel", (payload) => { Unsubscribe from a channel. -```js +```ts import { router } from "@reactivemarkets/desktop-sdk"; const listener = (data: string) => { diff --git a/docs/sdk/window/README.md b/docs/sdk/window/README.md index f69eddf4..90d2a7f4 100644 --- a/docs/sdk/window/README.md +++ b/docs/sdk/window/README.md @@ -6,7 +6,7 @@ Removes focus from the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().blur(); @@ -18,7 +18,7 @@ await window.current().blur(); Moves window to the center of the screen. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().center(); @@ -30,7 +30,7 @@ await window.current().center(); Try to close the window. This has the same effect as a user manually clicking the close button of the window. The web page may cancel the close though. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().close(); @@ -42,7 +42,7 @@ await window.current().close(); Starts or stops flashing the window to attract user's attention. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().flashFrame(true); @@ -54,7 +54,7 @@ await window.current().flashFrame(true); Focuses on the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().focus(); @@ -66,7 +66,7 @@ await window.current().focus(); Retrieves the bounds of the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const { x, y, width, height } = await window.current().getBounds(); @@ -78,7 +78,7 @@ const { x, y, width, height } = await window.current().getBounds(); Retrieves the bounds of the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const [width, height] = await window.current().getMinimumSize(); @@ -90,7 +90,7 @@ const [width, height] = await window.current().getMinimumSize(); Hides the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().hide(); @@ -102,7 +102,7 @@ await window.current().hide(); Whether the window is always on top of other windows. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const onTop = await window.current().isAlwaysOnTop(); @@ -114,7 +114,7 @@ const onTop = await window.current().isAlwaysOnTop(); Whether the window can be manually closed by user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const closeable = await window.current().isCloseable(); @@ -126,7 +126,7 @@ const closeable = await window.current().isCloseable(); Whether the window is enabled. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const enabled = await window.current().isEnabled(); @@ -138,7 +138,7 @@ const enabled = await window.current().isEnabled(); Whether the window is focused. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const focused = await window.current().isFocused(); @@ -150,7 +150,7 @@ const focused = await window.current().isFocused(); Whether the window is in fullscreen mode. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const fullscreen = await window.current().isFullscreen(); @@ -162,7 +162,7 @@ const fullscreen = await window.current().isFullscreen(); Whether the maximize/zoom window button toggles fullscreen mode or maximizes the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const fullscreenable = await window.current().isFullscreenable(); @@ -174,7 +174,7 @@ const fullscreenable = await window.current().isFullscreenable(); Whether the window is in kiosk mode. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const kiosk = await window.current().isKiosk(); @@ -186,7 +186,7 @@ const kiosk = await window.current().isKiosk(); Whether the window can be manually maximized by user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const maximizable = await window.current().isMaximizable(); @@ -198,7 +198,7 @@ const maximizable = await window.current().isMaximizable(); Whether the window is maximized. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const maximized = await window.current().isMaximized(); @@ -210,7 +210,7 @@ const maximized = await window.current().isMaximized(); Whether menu bar automatically hides itself. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const autoHide = await window.current().isMenuBarAutoHide(); @@ -222,7 +222,7 @@ const autoHide = await window.current().isMenuBarAutoHide(); Whether the menu bar is visible. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const visible = await window.current().isMenuBarVisible(); @@ -234,7 +234,7 @@ const visible = await window.current().isMenuBarVisible(); Whether the window can be manually minimized by the user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const minimizable = await window.current().isMinimizable(); @@ -246,7 +246,7 @@ const minimizable = await window.current().isMinimizable(); Whether the window is minimized. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const minimized = await window.current().isMinimized(); @@ -258,7 +258,7 @@ const minimized = await window.current().isMinimized(); Whether current window is a modal window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const modal = await window.current().isModal(); @@ -270,7 +270,7 @@ const modal = await window.current().isModal(); Whether the window can be moved by user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const movable = await window.current().isMovable(); @@ -282,7 +282,7 @@ const movable = await window.current().isMovable(); Whether the window can be manually resized by the user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const resizable = await window.current().isResizable(); @@ -294,7 +294,7 @@ const resizable = await window.current().isResizable(); Whether the window is visible to the user. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; const resizable = await window.current().isVisible(); @@ -306,7 +306,7 @@ const resizable = await window.current().isVisible(); Maximizes the window. This will also show (but not focus) the window if it isn't being displayed already. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().maximize(); @@ -318,7 +318,7 @@ await window.current().maximize(); Minimizes the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().minimize(); @@ -330,19 +330,51 @@ await window.current().minimize(); Moves window to top(z-order) regardless of focus. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().moveTop(); ``` +## off + +### `off(event: WindowEvents, listener: () => void): void` + +Removes a listener to [Window Events](./#window-events) from the window. + +```ts +import { window } from "@reactivemarkets/desktop-sdk"; + +const listener = () => { + // trigger an action +}; + +window.current().off("blur", listener); +``` + +## on + +### `on(event: WindowEvents, listener: () => void): void` + +Adds a listener to [Window Events](./#window-events) from the window. + +```ts +import { window } from "@reactivemarkets/desktop-sdk"; + +const listener = () => { + // trigger an action +}; + +window.current().on("blur", listener); +``` + ## reload ### `reload(): Promise` Reloads the current web page. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().reload(); @@ -354,7 +386,7 @@ await window.current().reload(); Restores the window from minimized state to its previous state. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().restore(); @@ -366,7 +398,7 @@ await window.current().restore(); Sets whether the window should show always on top of other windows. After setting this, the window is still a normal window, not a toolbox window which can not be focused on. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().setAlwaysOnTop(true); @@ -378,7 +410,7 @@ await window.current().setAlwaysOnTop(true); Resizes and moves the window to the supplied bounds. Any properties that are not supplied will default to their current values. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().setBounds({ @@ -392,7 +424,7 @@ await window.current().setBounds({ Sets whether the window should be in fullscreen mode. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().setFullScreen(true); @@ -404,7 +436,7 @@ await window.current().setFullScreen(true); Enters or leaves kiosk mode. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().setKiosk(true); @@ -416,7 +448,7 @@ await window.current().setKiosk(true); Shows and gives focus to the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().show(); @@ -428,8 +460,33 @@ await window.current().show(); Unmaximizes the window. -```js +```ts import { window } from "@reactivemarkets/desktop-sdk"; await window.current().unmaximize(); ``` + +## Window Events + +| Event | Description | +| ---------------------- | ------------------------------------------------------------------------------- | +| always-on-top-changed | Emitted when the window is set or unset to show always on top of other windows. | +| blur | Emitted when the window loses focus. | +| close | Emitted when the window is going to be closed. | +| enter-full-screen | Emitted when the window enters a full-screen state. | +| enter-html-full-screen | Emitted when the window enters a full-screen state triggered by HTML API. | +| focus | Emitted when the window gains focus. | +| hide | Emitted when the window is hidden. | +| leave-full-screen | Emitted when the window leaves a full-screen state. | +| leave-html-full-screen | Emitted when the window leaves a full-screen state triggered by HTML API. | +| maximize | Emitted when window is maximized. | +| minimize | Emitted when the window is minimized. | +| move | Emitted when the window is being moved to a new position. | +| moved | Emitted once when the window is moved to a new position. | +| page-title-updated | Emitted when the document changed its title. | +| resize | Emitted after the window has been resized. | +| restore | Emitted when the window is restored from a minimized state. | +| show | Emitted when the window is shown. | +| unmaximize | Emitted when the window exits from a maximized state. | +| will-move | Emitted before the window is moved. | +| will-resize | Emitted before the window is resized. | diff --git a/packages/desktop-core/src/common/transports/reservedChannels.ts b/packages/desktop-core/src/common/transports/reservedChannels.ts index 71c023fb..2d389b97 100644 --- a/packages/desktop-core/src/common/transports/reservedChannels.ts +++ b/packages/desktop-core/src/common/transports/reservedChannels.ts @@ -30,10 +30,13 @@ export enum ReservedChannels { router_subscribe = "@desktop:router/subscribe", router_unsubscribe = "@desktop:router/unsubscribe", router_unsubscribeAll = "@desktop:router/unsubscribeAll", + system_events = "@desktop:system/events", system_getAppName = "@desktop:system/getAppName", system_getAppVersion = "@desktop:system/getAppVersion", system_getVersions = "@desktop:system/getVersions", system_information = "@desktop:system/information", + system_off = "@desktop:system/off", + system_on = "@desktop:system/on", system_quit = "@desktop:system/quit", system_showAboutPanel = "@desktop:system/showAboutPanel", window_blur = "@desktop:window/blur", diff --git a/packages/desktop-core/src/main/api/systemIpcEvents.ts b/packages/desktop-core/src/main/api/systemIpcEvents.ts index 6770c0d6..97331835 100644 --- a/packages/desktop-core/src/main/api/systemIpcEvents.ts +++ b/packages/desktop-core/src/main/api/systemIpcEvents.ts @@ -1,7 +1,9 @@ -import { ipcMain, app } from "electron"; +import { ipcMain, app, powerMonitor } from "electron"; import { ReservedChannels } from "../../common"; export const systemIpcEvents = () => { + const listenerMap = new Map void>(); + ipcMain.handle(ReservedChannels.system_getAppName, () => { return app.getName(); }); @@ -23,6 +25,24 @@ export const systemIpcEvents = () => { sandboxed: process.sandboxed, }; }); + ipcMain.on(ReservedChannels.system_off, (event, systemEvent) => { + const key = `${event.sender.id}/${systemEvent}`; + const listener = listenerMap.get(key); + if (listener !== undefined) { + listenerMap.delete(key); + powerMonitor.removeListener(systemEvent, listener); + } + }); + ipcMain.on(ReservedChannels.system_on, (event, systemEvent) => { + const listener = () => { + event.sender.send(ReservedChannels.system_events, systemEvent); + }; + + const key = `${event.sender.id}/${systemEvent}`; + listenerMap.set(key, listener); + + powerMonitor.addListener(systemEvent, listener); + }); ipcMain.handle(ReservedChannels.system_quit, () => { app.quit(); }); diff --git a/packages/desktop-core/src/renderer/preload/system.ts b/packages/desktop-core/src/renderer/preload/system.ts index 63a35950..0386a405 100644 --- a/packages/desktop-core/src/renderer/preload/system.ts +++ b/packages/desktop-core/src/renderer/preload/system.ts @@ -1,7 +1,16 @@ import { ipcRenderer } from "electron"; +import { TypedEmitter } from "tiny-typed-emitter"; import { ReservedChannels } from "../../common"; export class System { + #emitter = new TypedEmitter(); + + public constructor() { + ipcRenderer.on(ReservedChannels.system_events, (_, event) => { + this.#emitter.emit(event); + }); + } + public getAppName = () => { return ipcRenderer.invoke(ReservedChannels.system_getAppName); }; @@ -14,6 +23,24 @@ export class System { return ipcRenderer.invoke(ReservedChannels.system_getVersions); }; + public off = (event: string, listener: () => void) => { + this.#emitter.removeListener(event, listener); + + const listenerCount = this.#emitter.listenerCount(event); + if (listenerCount === 0) { + ipcRenderer.send(ReservedChannels.system_off, event); + } + }; + + public on = (event: string, listener: () => void) => { + const listenerCount = this.#emitter.listenerCount(event); + this.#emitter.addListener(event, listener); + + if (listenerCount === 0) { + ipcRenderer.send(ReservedChannels.system_on, event); + } + }; + public quit = () => { return ipcRenderer.invoke(ReservedChannels.system_quit); }; diff --git a/packages/desktop-sdk/src/desktopClient.ts b/packages/desktop-sdk/src/desktopClient.ts index 0db55f5e..5923d566 100644 --- a/packages/desktop-sdk/src/desktopClient.ts +++ b/packages/desktop-sdk/src/desktopClient.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { IDesktop } from "./iDesktop"; +import { SystemEvents } from "./systemEvents"; export class DesktopClient implements IDesktop { public get api() { @@ -24,6 +25,14 @@ export class DesktopClient implements IDesktop { return this.api.system.getVersions(); } + public off(event: SystemEvents, listener: () => void) { + this.api.system.off(event, listener); + } + + public on(event: SystemEvents, listener: () => void) { + this.api.system.on(event, listener); + } + public quit() { return this.api.system.quit(); } diff --git a/packages/desktop-sdk/src/iDesktop.ts b/packages/desktop-sdk/src/iDesktop.ts index ab55b6aa..f61642bf 100644 --- a/packages/desktop-sdk/src/iDesktop.ts +++ b/packages/desktop-sdk/src/iDesktop.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { IDesktopVersions } from "./iDesktopVersions"; +import { SystemEvents } from "./systemEvents"; export interface IDesktop { /** @@ -28,6 +29,20 @@ export interface IDesktop { */ getVersions(): Promise; + /** + * Remove listener from the @see SystemEvents + * @param event `SystemEvents` + * @param listener + */ + off(event: SystemEvents, listener: () => void): void; + + /** + * Listen to @see SystemEvents + * @param event `SystemEvents` + * @param listener + */ + on(event: SystemEvents, listener: () => void): void; + /** * Quits the application giving all windows time to close. */ diff --git a/packages/desktop-sdk/src/systemEvents.ts b/packages/desktop-sdk/src/systemEvents.ts new file mode 100644 index 00000000..922b18fd --- /dev/null +++ b/packages/desktop-sdk/src/systemEvents.ts @@ -0,0 +1 @@ +export type SystemEvents = "lock-screen" | "on-ac" | "on-battery" | "resume" | "shutdown" | "suspend" | "unlock-screen"; diff --git a/packages/desktop-sdk/src/windowEvents.ts b/packages/desktop-sdk/src/windowEvents.ts index 3309633f..6549b3da 100644 --- a/packages/desktop-sdk/src/windowEvents.ts +++ b/packages/desktop-sdk/src/windowEvents.ts @@ -12,19 +12,12 @@ export type WindowEvents = | "minimize" | "move" | "moved" - | "new-window-for-tab" | "page-title-updated" | "resize" + | "resized" | "restore" - | "rotate-gesture" - | "scroll-touch-begin" - | "scroll-touch-edge" - | "scroll-touch-end" | "session-end" - | "sheet-begin" - | "sheet-end" | "show" - | "swipe" | "unmaximize" | "will-move" | "will-resize";