diff --git a/sdk/generated/host.window.d.ts b/sdk/generated/host.window.d.ts new file mode 100644 index 0000000..71ba7c2 --- /dev/null +++ b/sdk/generated/host.window.d.ts @@ -0,0 +1,310 @@ +// Auto-generated TypeScript definitions for host:window module +// Generated from ext_window/build.rs - do not edit manually + +declare module "host:window" { + // ============================================================================ + // Window Types + // ============================================================================ + + /** Options for creating a new window */ + export interface WindowOptions { + /** URL to load (default: "app://index.html") */ + url?: string; + /** Window width in pixels */ + width?: number; + /** Window height in pixels */ + height?: number; + /** Window title */ + title?: string; + /** Whether window is resizable (default: true) */ + resizable?: boolean; + /** Whether window has decorations (default: true) */ + decorations?: boolean; + /** Whether window is visible on creation (default: true) */ + visible?: boolean; + /** Whether window is transparent */ + transparent?: boolean; + /** Whether window is always on top */ + alwaysOnTop?: boolean; + /** Initial X position */ + x?: number; + /** Initial Y position */ + y?: number; + /** Minimum window width */ + minWidth?: number; + /** Minimum window height */ + minHeight?: number; + /** Maximum window width */ + maxWidth?: number; + /** Maximum window height */ + maxHeight?: number; + /** Channel allowlist for IPC - only these channels can be used */ + channels?: string[]; + } + + /** Window position */ + export interface Position { + x: number; + y: number; + } + + /** Window size */ + export interface Size { + width: number; + height: number; + } + + /** Native window handle (platform-specific) */ + export interface NativeHandle { + /** Platform type: "windows", "macos", "linux-x11", "linux-wayland", or "linux" (placeholder) */ + platform: string; + /** + * Raw handle value (HWND on Windows, NSView* on macOS, X11 window ID on Linux). + * Note: On Linux without X11/Wayland detection, returns 0 as a placeholder. + * Typed as number since Rust u64 serializes to JS number (safe for values up to 2^53). + */ + handle: number; + } + + /** Window system event */ + export interface WindowSystemEvent { + /** The window that emitted the event */ + windowId: string; + /** Event type */ + type: "close" | "focus" | "blur" | "resize" | "move" | "minimize" | "maximize" | "restore"; + /** Event payload (e.g., size for resize, position for move) */ + payload: unknown; + } + + /** Window handle with methods for manipulating the window */ + export interface Window { + /** Unique window identifier */ + readonly id: string; + + // Lifecycle + /** Close this window */ + close(): Promise; + /** Minimize this window */ + minimize(): Promise; + /** Maximize this window */ + maximize(): Promise; + /** Restore from maximized state */ + unmaximize(): Promise; + /** Restore from minimized state */ + restore(): Promise; + /** Focus this window */ + focus(): Promise; + + // Position & Size + /** Get window position */ + getPosition(): Promise; + /** Set window position */ + setPosition(x: number, y: number): Promise; + /** Get window size */ + getSize(): Promise; + /** Set window size */ + setSize(width: number, height: number): Promise; + + // Title + /** Get window title */ + getTitle(): Promise; + /** Set window title */ + setTitle(title: string): Promise; + + // State queries + /** Check if fullscreen */ + isFullscreen(): Promise; + /** Set fullscreen mode */ + setFullscreen(fullscreen: boolean): Promise; + /** Check if focused */ + isFocused(): Promise; + /** Check if maximized */ + isMaximized(): Promise; + /** Check if minimized */ + isMinimized(): Promise; + /** Check if visible */ + isVisible(): Promise; + /** Check if resizable */ + isResizable(): Promise; + /** Check if has decorations */ + hasDecorations(): Promise; + /** Check if always on top */ + isAlwaysOnTop(): Promise; + + // Configuration + /** Set whether window is resizable */ + setResizable(resizable: boolean): Promise; + /** Set whether window has decorations */ + setDecorations(decorations: boolean): Promise; + /** Set whether window is always on top */ + setAlwaysOnTop(alwaysOnTop: boolean): Promise; + /** Set window visibility */ + setVisible(visible: boolean): Promise; + /** Show window (alias for setVisible(true)) */ + show(): Promise; + /** Hide window (alias for setVisible(false)) */ + hide(): Promise; + + // Native + /** Get native window handle */ + getNativeHandle(): Promise; + + // Events + /** Async iterator for events from this window */ + events(): AsyncGenerator; + } + + // ============================================================================ + // Dialog Types + // ============================================================================ + + /** File filter for open/save dialogs */ + export interface FileFilter { + /** Display name for the filter (e.g., "Images") */ + name: string; + /** File extensions without dots (e.g., ["png", "jpg"]) */ + extensions: string[]; + } + + /** Options for file open dialog */ + export interface FileDialogOptions { + /** Dialog title */ + title?: string; + /** Default starting path */ + defaultPath?: string; + /** File type filters */ + filters?: FileFilter[]; + /** Allow selecting multiple files */ + multiple?: boolean; + /** Select directories instead of files */ + directory?: boolean; + } + + /** Options for message dialog */ + export interface MessageDialogOptions { + /** Dialog title */ + title?: string; + /** Message to display */ + message: string; + /** Dialog kind: "info", "warning", or "error" */ + kind?: "info" | "warning" | "error"; + /** Custom button labels */ + buttons?: string[]; + } + + // ============================================================================ + // Menu Types + // ============================================================================ + + /** Menu item definition */ + export interface MenuItem { + /** Unique identifier for this menu item */ + id?: string; + /** Display label */ + label: string; + /** Keyboard accelerator (e.g., "CmdOrCtrl+S") */ + accelerator?: string; + /** Whether the item is enabled (default: true) */ + enabled?: boolean; + /** Whether the item is checked (for checkbox items) */ + checked?: boolean; + /** Submenu items */ + submenu?: MenuItem[]; + /** Item type: "normal", "checkbox", or "separator" */ + type?: "normal" | "checkbox" | "separator"; + } + + /** Event emitted when a menu item is clicked */ + export interface MenuEvent { + /** Source of the event: "app", "context", or "tray" */ + menuId: string; + /** The id of the clicked menu item */ + itemId: string; + /** The label of the clicked menu item */ + label: string; + } + + // ============================================================================ + // Tray Types + // ============================================================================ + + /** Options for creating a system tray icon */ + export interface TrayOptions { + /** Path to icon file */ + icon?: string; + /** Tooltip text shown on hover */ + tooltip?: string; + /** Context menu for the tray icon */ + menu?: MenuItem[]; + } + + /** Tray handle with methods for manipulating the tray */ + export interface TrayHandle { + /** Unique tray identifier */ + readonly id: string; + /** Update tray options */ + update(options: TrayOptions): Promise; + /** Destroy this tray */ + destroy(): Promise; + } + + // ============================================================================ + // Window Functions + // ============================================================================ + + /** Create a new window and return a Window handle */ + export function createWindow(options?: WindowOptions): Promise; + + /** Close a window by ID */ + export function closeWindow(windowId: string): Promise; + + /** Async iterator for window system events from all windows */ + export function windowEvents(): AsyncGenerator; + + // ============================================================================ + // Dialog Namespace + // ============================================================================ + + export const dialog: { + /** Show an open file dialog. Returns null if cancelled. */ + open(options?: FileDialogOptions): Promise; + /** Show a save file dialog. Returns null if cancelled. */ + save(options?: FileDialogOptions): Promise; + /** Show a message dialog. Returns the index of the clicked button. */ + message(options: MessageDialogOptions | string): Promise; + /** Show an alert dialog (convenience wrapper) */ + alert(message: string, title?: string): Promise; + /** Show a confirm dialog (convenience wrapper) */ + confirm(message: string, title?: string): Promise; + /** Show an error dialog (convenience wrapper) */ + error(message: string, title?: string): Promise; + /** Show a warning dialog (convenience wrapper) */ + warning(message: string, title?: string): Promise; + }; + + // ============================================================================ + // Menu Namespace + // ============================================================================ + + export const menu: { + /** Set the application menu bar */ + setAppMenu(items: MenuItem[]): Promise; + /** Show a context menu at the current cursor position */ + showContextMenu(items: MenuItem[], windowId?: string): Promise; + /** Async iterator for menu events */ + events(): AsyncGenerator; + /** Register a callback for menu events. Returns unsubscribe function. */ + onMenu(callback: (event: MenuEvent) => void): () => void; + }; + + // ============================================================================ + // Tray Namespace + // ============================================================================ + + export const tray: { + /** Create a system tray icon */ + create(options?: TrayOptions): Promise; + /** Destroy a tray by ID */ + destroy(trayId: string): Promise; + }; +} diff --git a/sdk/host.d.ts b/sdk/host.d.ts index fcc5c78..3e228b0 100644 --- a/sdk/host.d.ts +++ b/sdk/host.d.ts @@ -488,3 +488,226 @@ declare module "host:wasm" { grow(instanceId: string, pages: number): Promise; }; } + +declare module "host:window" { + /** Options for creating a new window */ + export interface WindowOptions { + url?: string; + width?: number; + height?: number; + title?: string; + resizable?: boolean; + decorations?: boolean; + visible?: boolean; + transparent?: boolean; + alwaysOnTop?: boolean; + x?: number; + y?: number; + minWidth?: number; + minHeight?: number; + maxWidth?: number; + maxHeight?: number; + channels?: string[]; + } + + /** Window position */ + export interface Position { + x: number; + y: number; + } + + /** Window size */ + export interface Size { + width: number; + height: number; + } + + /** Native window handle (platform-specific) */ + export interface NativeHandle { + /** Platform type: "windows", "macos", "linux-x11", "linux-wayland", or "linux" (placeholder) */ + platform: string; + /** + * Raw handle value (HWND on Windows, NSView* on macOS, X11 window ID on Linux). + * Note: On Linux without X11/Wayland detection, returns 0 as a placeholder. + * Typed as number since Rust u64 serializes to JS number (safe for values up to 2^53). + */ + handle: number; + } + + /** Window system event */ + export interface WindowSystemEvent { + windowId: string; + type: "close" | "focus" | "blur" | "resize" | "move" | "minimize" | "maximize" | "restore"; + payload: unknown; + } + + /** File filter for dialogs */ + export interface FileFilter { + name: string; + extensions: string[]; + } + + /** Options for file open/save dialog */ + export interface FileDialogOptions { + title?: string; + defaultPath?: string; + filters?: FileFilter[]; + multiple?: boolean; + directory?: boolean; + } + + /** Options for message dialog */ + export interface MessageDialogOptions { + title?: string; + message: string; + kind?: "info" | "warning" | "error"; + /** + * Button labels to display. + * NOTE: The underlying rfd library only supports preset button configurations + * (Ok, OkCancel, YesNo, etc.). Custom button labels may be ignored or mapped + * to the closest preset. For reliable cross-platform behavior, use convenience + * methods like dialog.alert(), dialog.confirm(), dialog.error(), dialog.warning(). + */ + buttons?: string[]; + } + + /** Menu item definition */ + export interface MenuItem { + id?: string; + label: string; + accelerator?: string; + enabled?: boolean; + checked?: boolean; + submenu?: MenuItem[]; + type?: "normal" | "checkbox" | "separator"; + } + + /** Menu event */ + export interface MenuEvent { + menuId: string; + itemId: string; + label: string; + } + + /** Tray options */ + export interface TrayOptions { + icon?: string; + tooltip?: string; + menu?: MenuItem[]; + } + + /** Tray handle */ + export interface TrayHandle { + readonly id: string; + update(options: TrayOptions): Promise; + destroy(): Promise; + } + + /** Window handle */ + export interface Window { + readonly id: string; + close(): Promise; + minimize(): Promise; + maximize(): Promise; + unmaximize(): Promise; + restore(): Promise; + focus(): Promise; + getPosition(): Promise; + setPosition(x: number, y: number): Promise; + getSize(): Promise; + setSize(width: number, height: number): Promise; + getTitle(): Promise; + setTitle(title: string): Promise; + isFullscreen(): Promise; + setFullscreen(fullscreen: boolean): Promise; + isFocused(): Promise; + isMaximized(): Promise; + isMinimized(): Promise; + isVisible(): Promise; + isResizable(): Promise; + hasDecorations(): Promise; + isAlwaysOnTop(): Promise; + setResizable(resizable: boolean): Promise; + setDecorations(decorations: boolean): Promise; + setAlwaysOnTop(alwaysOnTop: boolean): Promise; + setVisible(visible: boolean): Promise; + show(): Promise; + hide(): Promise; + getNativeHandle(): Promise; + events(): AsyncGenerator; + } + + /** Create a new window */ + export function createWindow(options?: WindowOptions): Promise; + + /** Close a window by ID */ + export function closeWindow(windowId: string): Promise; + + /** Async iterator for window system events */ + export function windowEvents(): AsyncGenerator; + + /** Dialog functions */ + export const dialog: { + open(options?: FileDialogOptions): Promise; + save(options?: FileDialogOptions): Promise; + message(options: MessageDialogOptions | string): Promise; + alert(message: string, title?: string): Promise; + confirm(message: string, title?: string): Promise; + error(message: string, title?: string): Promise; + warning(message: string, title?: string): Promise; + }; + + /** Menu functions */ + export const menu: { + setAppMenu(items: MenuItem[]): Promise; + showContextMenu(items: MenuItem[], windowId?: string): Promise; + events(): AsyncGenerator; + onMenu(callback: (event: MenuEvent) => void): () => void; + }; + + /** Tray functions */ + export const tray: { + create(options?: TrayOptions): Promise; + destroy(trayId: string): Promise; + }; +} + +declare module "host:ipc" { + /** IPC event from renderer */ + export interface IpcEvent { + windowId: string; + channel: string; + payload: unknown; + type?: "close" | "focus" | "blur" | "resize" | "move"; + } + + /** Callback for IPC events */ + export type IpcEventCallback = (event: IpcEvent) => void; + + /** Callback for channel-specific events */ + export type ChannelCallback = (payload: unknown, windowId: string) => void; + + /** Send message to a window's renderer */ + export function sendToWindow(windowId: string, channel: string, payload?: unknown): Promise; + + /** Receive next IPC event */ + export function recvWindowEvent(): Promise; + + /** Async iterator for IPC events */ + export function windowEvents(): AsyncGenerator; + + /** Filter events for a specific window */ + export function windowEventsFor(windowId: string): AsyncGenerator; + + /** Filter events for a specific channel */ + export function channelEvents(channel: string): AsyncGenerator; + + /** Register callback for all IPC events */ + export function onEvent(callback: IpcEventCallback): () => void; + + /** Register callback for specific channel */ + export function onChannel(channel: string, callback: ChannelCallback): () => void; + + /** Broadcast to multiple windows */ + export function broadcast(windowIds: string[], channel: string, payload?: unknown): Promise; +} diff --git a/sdk/host.window.ts b/sdk/host.window.ts new file mode 100644 index 0000000..888b9a0 --- /dev/null +++ b/sdk/host.window.ts @@ -0,0 +1,750 @@ +// host:window module - Deno API for native window operations +// This is the single source of truth for the host:window SDK + +// ============================================================================ +// Type Definitions +// ============================================================================ + +/** Options for creating a new window */ +export interface WindowOptions { + /** URL to load (default: "app://index.html") */ + url?: string; + /** Window width in pixels */ + width?: number; + /** Window height in pixels */ + height?: number; + /** Window title */ + title?: string; + /** Whether window is resizable (default: true) */ + resizable?: boolean; + /** Whether window has decorations (default: true) */ + decorations?: boolean; + /** Whether window is visible on creation (default: true) */ + visible?: boolean; + /** Whether window is transparent */ + transparent?: boolean; + /** Whether window is always on top */ + alwaysOnTop?: boolean; + /** Initial X position */ + x?: number; + /** Initial Y position */ + y?: number; + /** Minimum window width */ + minWidth?: number; + /** Minimum window height */ + minHeight?: number; + /** Maximum window width */ + maxWidth?: number; + /** Maximum window height */ + maxHeight?: number; + /** Channel allowlist for IPC - only these channels can be used */ + channels?: string[]; +} + +/** Window position */ +export interface Position { + x: number; + y: number; +} + +/** Window size */ +export interface Size { + width: number; + height: number; +} + +/** Native window handle (platform-specific) */ +export interface NativeHandle { + /** Platform type: "windows", "macos", "linux-x11", "linux-wayland", or "linux" (placeholder) */ + platform: string; + /** + * Raw handle value (HWND on Windows, NSView* on macOS, X11 window ID on Linux). + * Note: On Linux without X11/Wayland detection, returns 0 as a placeholder. + * Typed as number since Rust u64 serializes to JS number (safe for values up to 2^53). + */ + handle: number; +} + +/** Window system event */ +export interface WindowSystemEvent { + /** The window that emitted the event */ + windowId: string; + /** Event type */ + type: "close" | "focus" | "blur" | "resize" | "move" | "minimize" | "maximize" | "restore"; + /** Event payload (e.g., size for resize, position for move) */ + payload: unknown; +} + +/** File filter for open/save dialogs */ +export interface FileFilter { + /** Display name for the filter (e.g., "Images") */ + name: string; + /** File extensions without dots (e.g., ["png", "jpg"]) */ + extensions: string[]; +} + +/** Options for file open/save dialog */ +export interface FileDialogOptions { + /** Dialog title */ + title?: string; + /** Default starting path */ + defaultPath?: string; + /** File type filters */ + filters?: FileFilter[]; + /** Allow selecting multiple files */ + multiple?: boolean; + /** Select directories instead of files */ + directory?: boolean; +} + +/** Options for message dialog */ +export interface MessageDialogOptions { + /** Dialog title */ + title?: string; + /** Message to display */ + message: string; + /** Dialog kind: "info", "warning", or "error" */ + kind?: "info" | "warning" | "error"; + /** Custom button labels */ + buttons?: string[]; +} + +/** Menu item definition */ +export interface MenuItem { + /** Unique identifier for this menu item */ + id?: string; + /** Display label */ + label: string; + /** Keyboard accelerator (e.g., "CmdOrCtrl+S") */ + accelerator?: string; + /** Whether the item is enabled (default: true) */ + enabled?: boolean; + /** Whether the item is checked (for checkbox items) */ + checked?: boolean; + /** Submenu items */ + submenu?: MenuItem[]; + /** Item type: "normal", "checkbox", or "separator" */ + type?: "normal" | "checkbox" | "separator"; +} + +/** Event emitted when a menu item is clicked */ +export interface MenuEvent { + /** Source of the event: "app", "context", or "tray" */ + menuId: string; + /** The id of the clicked menu item */ + itemId: string; + /** The label of the clicked menu item */ + label: string; +} + +/** Options for creating a system tray icon */ +export interface TrayOptions { + /** Path to icon file */ + icon?: string; + /** Tooltip text shown on hover */ + tooltip?: string; + /** Context menu for the tray icon */ + menu?: MenuItem[]; +} + +/** Tray handle with methods for manipulating the tray */ +export interface TrayHandle { + /** Unique tray identifier */ + readonly id: string; + /** Update tray options */ + update(options: TrayOptions): Promise; + /** Destroy this tray */ + destroy(): Promise; +} + +/** Window handle with methods for manipulating the window */ +export interface Window { + /** Unique window identifier */ + readonly id: string; + // Lifecycle + close(): Promise; + minimize(): Promise; + maximize(): Promise; + unmaximize(): Promise; + restore(): Promise; + focus(): Promise; + // Position & Size + getPosition(): Promise; + setPosition(x: number, y: number): Promise; + getSize(): Promise; + setSize(width: number, height: number): Promise; + // Title + getTitle(): Promise; + setTitle(title: string): Promise; + // State + isFullscreen(): Promise; + setFullscreen(fullscreen: boolean): Promise; + isFocused(): Promise; + isMaximized(): Promise; + isMinimized(): Promise; + isVisible(): Promise; + isResizable(): Promise; + hasDecorations(): Promise; + isAlwaysOnTop(): Promise; + // Configuration + setResizable(resizable: boolean): Promise; + setDecorations(decorations: boolean): Promise; + setAlwaysOnTop(alwaysOnTop: boolean): Promise; + setVisible(visible: boolean): Promise; + show(): Promise; + hide(): Promise; + // Native + getNativeHandle(): Promise; + // Events + events(): AsyncGenerator; +} + +/** Callback function for menu event handlers */ +export type MenuCallback = (event: MenuEvent) => void; + +// ============================================================================ +// Deno Core Ops Declaration +// ============================================================================ + +declare const Deno: { + core: { + ops: { + // Window Lifecycle (10 ops) + op_window_create(opts: WindowOptions): Promise; + op_window_close(windowId: string): Promise; + op_window_minimize(windowId: string): Promise; + op_window_maximize(windowId: string): Promise; + op_window_unmaximize(windowId: string): Promise; + op_window_restore(windowId: string): Promise; + op_window_set_fullscreen(windowId: string, fullscreen: boolean): Promise; + op_window_is_fullscreen(windowId: string): Promise; + op_window_focus(windowId: string): Promise; + op_window_is_focused(windowId: string): Promise; + // Window Properties (16 ops) + op_window_get_position(windowId: string): Promise; + op_window_set_position(windowId: string, x: number, y: number): Promise; + op_window_get_size(windowId: string): Promise; + op_window_set_size(windowId: string, width: number, height: number): Promise; + op_window_get_title(windowId: string): Promise; + op_window_set_title(windowId: string, title: string): Promise; + op_window_set_resizable(windowId: string, resizable: boolean): Promise; + op_window_is_resizable(windowId: string): Promise; + op_window_set_decorations(windowId: string, decorations: boolean): Promise; + op_window_has_decorations(windowId: string): Promise; + op_window_set_always_on_top(windowId: string, alwaysOnTop: boolean): Promise; + op_window_is_always_on_top(windowId: string): Promise; + op_window_set_visible(windowId: string, visible: boolean): Promise; + op_window_is_visible(windowId: string): Promise; + op_window_is_maximized(windowId: string): Promise; + op_window_is_minimized(windowId: string): Promise; + // Dialogs (3 ops) + op_window_dialog_open(opts: FileDialogOptions): Promise; + op_window_dialog_save(opts: FileDialogOptions): Promise; + op_window_dialog_message(opts: MessageDialogOptions): Promise; + // Menus (3 ops) + op_window_set_app_menu(items: MenuItem[]): Promise; + op_window_show_context_menu(windowId: string | null, items: MenuItem[]): Promise; + op_window_menu_recv(): Promise; + // Tray (3 ops) + op_window_create_tray(opts: TrayOptions): Promise; + op_window_update_tray(trayId: string, opts: TrayOptions): Promise; + op_window_destroy_tray(trayId: string): Promise; + // Events & Native (2 ops) + op_window_events_recv(): Promise; + op_window_get_native_handle(windowId: string): Promise; + }; + }; +}; + +const core = Deno.core; + +// ============================================================================ +// Window Functions +// ============================================================================ + +/** + * Create a new window and return a Window handle. + * + * @param opts - Window options + * @returns A Window handle for controlling the window + * + * @example + * ```ts + * import { createWindow } from "host:window"; + * + * const win = await createWindow({ + * url: "app://index.html", + * width: 1200, + * height: 800, + * title: "My App" + * }); + * + * // Later, manipulate the window + * await win.setTitle("Updated Title"); + * const { width, height } = await win.getSize(); + * ``` + */ +export async function createWindow(opts: WindowOptions = {}): Promise { + const windowId = await core.ops.op_window_create(opts); + + const handle: Window = { + id: windowId, + + // Lifecycle + async close(): Promise { + return await core.ops.op_window_close(windowId); + }, + async minimize(): Promise { + return await core.ops.op_window_minimize(windowId); + }, + async maximize(): Promise { + return await core.ops.op_window_maximize(windowId); + }, + async unmaximize(): Promise { + return await core.ops.op_window_unmaximize(windowId); + }, + async restore(): Promise { + return await core.ops.op_window_restore(windowId); + }, + async focus(): Promise { + return await core.ops.op_window_focus(windowId); + }, + + // Position & Size + async getPosition(): Promise { + return await core.ops.op_window_get_position(windowId); + }, + async setPosition(x: number, y: number): Promise { + return await core.ops.op_window_set_position(windowId, x, y); + }, + async getSize(): Promise { + return await core.ops.op_window_get_size(windowId); + }, + async setSize(width: number, height: number): Promise { + return await core.ops.op_window_set_size(windowId, width, height); + }, + + // Title + async getTitle(): Promise { + return await core.ops.op_window_get_title(windowId); + }, + async setTitle(title: string): Promise { + return await core.ops.op_window_set_title(windowId, title); + }, + + // State + async isFullscreen(): Promise { + return await core.ops.op_window_is_fullscreen(windowId); + }, + async setFullscreen(fullscreen: boolean): Promise { + return await core.ops.op_window_set_fullscreen(windowId, fullscreen); + }, + async isFocused(): Promise { + return await core.ops.op_window_is_focused(windowId); + }, + async isMaximized(): Promise { + return await core.ops.op_window_is_maximized(windowId); + }, + async isMinimized(): Promise { + return await core.ops.op_window_is_minimized(windowId); + }, + async isVisible(): Promise { + return await core.ops.op_window_is_visible(windowId); + }, + async isResizable(): Promise { + return await core.ops.op_window_is_resizable(windowId); + }, + async hasDecorations(): Promise { + return await core.ops.op_window_has_decorations(windowId); + }, + async isAlwaysOnTop(): Promise { + return await core.ops.op_window_is_always_on_top(windowId); + }, + + // Configuration + async setResizable(resizable: boolean): Promise { + return await core.ops.op_window_set_resizable(windowId, resizable); + }, + async setDecorations(decorations: boolean): Promise { + return await core.ops.op_window_set_decorations(windowId, decorations); + }, + async setAlwaysOnTop(alwaysOnTop: boolean): Promise { + return await core.ops.op_window_set_always_on_top(windowId, alwaysOnTop); + }, + async setVisible(visible: boolean): Promise { + return await core.ops.op_window_set_visible(windowId, visible); + }, + async show(): Promise { + return await core.ops.op_window_set_visible(windowId, true); + }, + async hide(): Promise { + return await core.ops.op_window_set_visible(windowId, false); + }, + + // Native + async getNativeHandle(): Promise { + return await core.ops.op_window_get_native_handle(windowId); + }, + + // Events - filter for this window + async *events(): AsyncGenerator { + for await (const event of windowEvents()) { + if (event.windowId === windowId) { + yield event; + } + } + } + }; + + return handle; +} + +/** + * Close a window by ID. + * + * @param windowId - The window ID to close + * @returns true if closed successfully + * + * @example + * ```ts + * import { closeWindow } from "host:window"; + * + * await closeWindow("main-window"); + * ``` + */ +export async function closeWindow(windowId: string): Promise { + return await core.ops.op_window_close(windowId); +} + +/** + * Receive the next window system event (blocking). + * Returns null when no more events are available. + * + * @returns The next window event or null + */ +async function recvWindowEvent(): Promise { + return await core.ops.op_window_events_recv(); +} + +/** + * Async generator for window system events from all windows. + * Use in a for-await loop to process events as they arrive. + * + * @example + * ```ts + * import { windowEvents } from "host:window"; + * + * for await (const event of windowEvents()) { + * console.log(`Window ${event.windowId}: ${event.type}`); + * if (event.type === "close") { + * console.log("Window closed"); + * } + * } + * ``` + */ +export async function* windowEvents(): AsyncGenerator { + while (true) { + const event = await recvWindowEvent(); + if (event === null) break; + yield event; + } +} + +// ============================================================================ +// Dialog Namespace +// ============================================================================ + +/** + * Dialog functions for file and message dialogs. + * + * @example + * ```ts + * import { dialog } from "host:window"; + * + * // Open file dialog + * const files = await dialog.open({ + * title: "Select Images", + * filters: [{ name: "Images", extensions: ["png", "jpg"] }], + * multiple: true + * }); + * + * // Show confirmation + * const confirmed = await dialog.confirm("Are you sure?"); + * ``` + */ +export const dialog = { + /** + * Show an open file dialog. + * + * @param opts - Dialog options + * @returns Array of selected file paths, or null if cancelled + */ + async open(opts: FileDialogOptions = {}): Promise { + return await core.ops.op_window_dialog_open(opts); + }, + + /** + * Show a save file dialog. + * + * @param opts - Dialog options + * @returns Selected file path, or null if cancelled + */ + async save(opts: FileDialogOptions = {}): Promise { + return await core.ops.op_window_dialog_save(opts); + }, + + /** + * Show a message dialog. + * + * @param opts - Dialog options or message string + * @returns Index of the clicked button + */ + async message(opts: MessageDialogOptions | string): Promise { + const options: MessageDialogOptions = typeof opts === "string" ? { message: opts } : opts; + return await core.ops.op_window_dialog_message(options); + }, + + /** + * Show an alert dialog. + * + * @param message - Message to display + * @param title - Dialog title + * @returns Index of clicked button (always 0 for OK) + */ + async alert(message: string, title: string = "Alert"): Promise { + return await core.ops.op_window_dialog_message({ + title, + message, + kind: "info", + buttons: ["OK"] + }); + }, + + /** + * Show a confirmation dialog. + * + * @param message - Message to display + * @param title - Dialog title + * @returns true if OK clicked, false if cancelled + */ + async confirm(message: string, title: string = "Confirm"): Promise { + const result = await core.ops.op_window_dialog_message({ + title, + message, + kind: "info", + buttons: ["Cancel", "OK"] + }); + return result === 1; + }, + + /** + * Show an error dialog. + * + * @param message - Error message to display + * @param title - Dialog title + */ + async error(message: string, title: string = "Error"): Promise { + return await core.ops.op_window_dialog_message({ + title, + message, + kind: "error", + buttons: ["OK"] + }); + }, + + /** + * Show a warning dialog. + * + * @param message - Warning message to display + * @param title - Dialog title + */ + async warning(message: string, title: string = "Warning"): Promise { + return await core.ops.op_window_dialog_message({ + title, + message, + kind: "warning", + buttons: ["OK"] + }); + } +}; + +// ============================================================================ +// Menu Namespace +// ============================================================================ + +/** + * Receive the next menu event (blocking). + * Returns null when no more events are available. + */ +async function recvMenuEvent(): Promise { + return await core.ops.op_window_menu_recv(); +} + +/** + * Async generator for menu events. + */ +async function* menuEvents(): AsyncGenerator { + while (true) { + const event = await recvMenuEvent(); + if (event === null) break; + yield event; + } +} + +let menuListenerActive = false; +const menuCallbacks: MenuCallback[] = []; + +/** + * Register a callback for menu events. + * + * @param callback - Function called for each menu event + * @returns Unsubscribe function + */ +function onMenu(callback: MenuCallback): () => void { + menuCallbacks.push(callback); + + if (!menuListenerActive) { + menuListenerActive = true; + (async () => { + for await (const event of menuEvents()) { + for (const cb of menuCallbacks) { + try { + cb(event); + } catch (e) { + console.error("Error in menu callback:", e); + } + } + } + menuListenerActive = false; + })(); + } + + return () => { + const index = menuCallbacks.indexOf(callback); + if (index !== -1) { + menuCallbacks.splice(index, 1); + } + }; +} + +/** + * Menu functions for application and context menus. + * + * @example + * ```ts + * import { menu } from "host:window"; + * + * // Set application menu + * await menu.setAppMenu([ + * { + * label: "File", + * submenu: [ + * { id: "open", label: "Open", accelerator: "CmdOrCtrl+O" }, + * { id: "save", label: "Save", accelerator: "CmdOrCtrl+S" }, + * { type: "separator", label: "" }, + * { id: "quit", label: "Quit", accelerator: "CmdOrCtrl+Q" } + * ] + * } + * ]); + * + * // Listen for menu events + * const unsub = menu.onMenu((event) => { + * console.log("Menu clicked:", event.itemId); + * }); + * ``` + */ +export const menu = { + /** + * Set the application menu bar. + * + * @param items - Menu items to display + * @returns true if successful + */ + async setAppMenu(items: MenuItem[]): Promise { + return await core.ops.op_window_set_app_menu(items); + }, + + /** + * Show a context menu at the current cursor position. + * + * @param items - Menu items to display + * @param windowId - Optional window to show menu in + * @returns ID of clicked item, or null if cancelled + */ + async showContextMenu(items: MenuItem[], windowId?: string): Promise { + const result = await core.ops.op_window_show_context_menu(windowId ?? null, items); + return result === "" ? null : result; + }, + + /** Async generator for menu events */ + events: menuEvents, + + /** Register a callback for menu events */ + onMenu +}; + +// ============================================================================ +// Tray Namespace +// ============================================================================ + +/** + * Create a system tray icon. + * + * @param opts - Tray options + * @returns TrayHandle for controlling the tray + */ +async function createTray(opts: TrayOptions = {}): Promise { + const trayId = await core.ops.op_window_create_tray(opts); + + return { + id: trayId, + + async update(newOpts: TrayOptions): Promise { + return await core.ops.op_window_update_tray(trayId, newOpts); + }, + + async destroy(): Promise { + return await core.ops.op_window_destroy_tray(trayId); + } + }; +} + +/** + * Destroy a tray by ID. + * + * @param trayId - The tray ID to destroy + * @returns true if destroyed successfully + */ +async function destroyTray(trayId: string): Promise { + return await core.ops.op_window_destroy_tray(trayId); +} + +/** + * System tray functions. + * + * @example + * ```ts + * import { tray } from "host:window"; + * + * const myTray = await tray.create({ + * icon: "assets/icon.png", + * tooltip: "My App", + * menu: [ + * { id: "show", label: "Show Window" }, + * { type: "separator", label: "" }, + * { id: "quit", label: "Quit" } + * ] + * }); + * + * // Later, update or destroy + * await myTray.update({ tooltip: "Updated!" }); + * await myTray.destroy(); + * ``` + */ +export const tray = { + /** Create a system tray icon */ + create: createTray, + + /** Destroy a tray by ID */ + destroy: destroyTray +};