Add macOS lifecycle hooks for TypeScript apps (#314)
* Add macOS lifecycle hooks for TypeScript apps - Expose hidden-window, fullscreen, Dock-presence, and launch-at-login controls through manifests and TypeScript effects. - Implement AppKit and SMAppService behavior across both macOS hosts with safe cross-platform fallbacks. - Cover the new wire/runtime paths with tests and document the menu-bar app migration workflow. * fix: address platform lifecycle review findings * fix: harden macOS lifecycle hooks * fix: preserve macOS activation behavior * fix: preserve hidden startup window state
This commit is contained in:
@@ -356,6 +356,8 @@ export type CmdData =
|
||||
readonly value: number;
|
||||
}
|
||||
| { readonly op: "window_show"; readonly label: string }
|
||||
| { readonly op: "window_hide"; readonly label: string }
|
||||
| { readonly op: "dock_presence"; readonly visible: boolean }
|
||||
| { readonly op: "quit_app" }
|
||||
| {
|
||||
readonly op: "image_load";
|
||||
@@ -633,6 +635,22 @@ export const Cmd = {
|
||||
return { op: "window_show", label };
|
||||
},
|
||||
|
||||
hideWindow(label: string): CmdData {
|
||||
return { op: "window_hide", label };
|
||||
},
|
||||
|
||||
setDockPresence(visible: boolean): CmdData {
|
||||
return { op: "dock_presence", visible };
|
||||
},
|
||||
|
||||
launchAtLoginStatus(route: { readonly key?: string; readonly ok: string; readonly err: string }): CmdData {
|
||||
return Cmd.request("native-sdk.launch-at-login.status", new Uint8Array(0), route);
|
||||
},
|
||||
|
||||
setLaunchAtLogin(enabled: boolean, route: { readonly key?: string; readonly ok: string; readonly err: string }): CmdData {
|
||||
return Cmd.request("native-sdk.launch-at-login.set", new Uint8Array([enabled ? 1 : 0]), route);
|
||||
},
|
||||
|
||||
quitApp(): CmdData {
|
||||
return { op: "quit_app" };
|
||||
},
|
||||
|
||||
Vendored
+10
@@ -313,6 +313,12 @@ export type Cmd<M extends Msgish> = {
|
||||
} | {
|
||||
readonly op: "window_show";
|
||||
readonly label: string;
|
||||
} | {
|
||||
readonly op: "window_hide";
|
||||
readonly label: string;
|
||||
} | {
|
||||
readonly op: "dock_presence";
|
||||
readonly visible: boolean;
|
||||
} | {
|
||||
readonly op: "quit_app";
|
||||
} | {
|
||||
@@ -405,6 +411,10 @@ export declare const Cmd: {
|
||||
videoSetMuted(key: string, muted: boolean): Cmd<never>;
|
||||
videoSetLoop(key: string, loop: boolean): Cmd<never>;
|
||||
showWindow(label: string): Cmd<never>;
|
||||
hideWindow(label: string): Cmd<never>;
|
||||
setDockPresence(visible: boolean): Cmd<never>;
|
||||
launchAtLoginStatus<M extends Msgish>(route: RequestRoute<M>): Cmd<M>;
|
||||
setLaunchAtLogin<M extends Msgish>(enabled: boolean, route: RequestRoute<M>): Cmd<M>;
|
||||
quitApp(): Cmd<never>;
|
||||
imageLoad<M extends Msgish>(id: number, source: ImageSource, route: ImageRoute<M>): Cmd<M>;
|
||||
imageCancel(id: number): Cmd<never>;
|
||||
|
||||
@@ -229,6 +229,10 @@
|
||||
// "Open" consequence; also restores a
|
||||
// minimized window. An unknown label is a
|
||||
// no-op.
|
||||
// Cmd.hideWindow(label) order a live window out while retaining
|
||||
// its views; showWindow is the inverse.
|
||||
// Cmd.setDockPresence(visible) show or remove the app from the macOS
|
||||
// Dock/app switcher.
|
||||
// Cmd.quitApp() graceful terminate, the tray "Quit"
|
||||
// consequence: the host quits through the
|
||||
// SAME shutdown path a last-window close
|
||||
@@ -1048,6 +1052,8 @@ export type Cmd<M extends Msgish> =
|
||||
readonly value: number;
|
||||
}
|
||||
| { readonly op: "window_show"; readonly label: string }
|
||||
| { readonly op: "window_hide"; readonly label: string }
|
||||
| { readonly op: "dock_presence"; readonly visible: boolean }
|
||||
| { readonly op: "quit_app" }
|
||||
| {
|
||||
readonly op: "image_load";
|
||||
@@ -1435,6 +1441,31 @@ export const Cmd = {
|
||||
return { op: "window_show", label };
|
||||
},
|
||||
|
||||
/// Hide a live window without closing it. Its views and native identity
|
||||
/// remain intact; `showWindow` brings it back. Unknown labels no-op.
|
||||
hideWindow(label: string): Cmd<never> {
|
||||
return { op: "window_hide", label };
|
||||
},
|
||||
|
||||
/// Control whether the app appears in the macOS Dock and app switcher.
|
||||
/// Unsupported hosts ignore the command.
|
||||
setDockPresence(visible: boolean): Cmd<never> {
|
||||
return { op: "dock_presence", visible };
|
||||
},
|
||||
|
||||
/// Query the installed bundle's launch-at-login registration. The ok
|
||||
/// route receives UTF-8 bytes naming `enabled`, `disabled`,
|
||||
/// `requires_approval`, or `not_found`; the err route receives a reason.
|
||||
launchAtLoginStatus<M extends Msgish>(route: RequestRoute<M>): Cmd<M> {
|
||||
return Cmd.request("native-sdk.launch-at-login.status", new Uint8Array(0), route);
|
||||
},
|
||||
|
||||
/// Register or unregister the installed bundle for launch at login. The
|
||||
/// ok and err payloads follow `launchAtLoginStatus`.
|
||||
setLaunchAtLogin<M extends Msgish>(enabled: boolean, route: RequestRoute<M>): Cmd<M> {
|
||||
return Cmd.request("native-sdk.launch-at-login.set", new Uint8Array([enabled ? 1 : 0]), route);
|
||||
},
|
||||
|
||||
/// Quit the app for real — the graceful terminate, and the tray "Quit"
|
||||
/// consequence. The host quits through the SAME shutdown path a
|
||||
/// last-window close takes, so the stop hook runs exactly once and a
|
||||
|
||||
@@ -3785,7 +3785,10 @@ export function update(model: Model, msg: Msg): Model | [Model, Cmd<Msg>] {
|
||||
switch (msg.kind) {
|
||||
case "go":
|
||||
if (msg.which === 0) return [model, Cmd.showWindow("player")];
|
||||
if (msg.which === 1) return [model, Cmd.batch([Cmd.showWindow("player"), Cmd.quitApp()])];
|
||||
if (msg.which === 1) return [model, Cmd.batch([Cmd.hideWindow("player"), Cmd.setDockPresence(false)])];
|
||||
if (msg.which === 2) return [model, Cmd.launchAtLoginStatus({ key: "login-status", ok: "line", err: "failed" })];
|
||||
if (msg.which === 3) return [model, Cmd.setLaunchAtLogin(true, { key: "login-set", ok: "line", err: "failed" })];
|
||||
if (msg.which === 4) return [model, Cmd.batch([Cmd.showWindow("player"), Cmd.quitApp()])];
|
||||
return [model, Cmd.quitApp()];
|
||||
${streamTail}
|
||||
`,
|
||||
|
||||
Reference in New Issue
Block a user