@n1h/sweetview 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSE-0BSD +12 -0
  3. package/README.md +268 -3
  4. package/docs/README.md +26 -0
  5. package/docs/app.md +523 -0
  6. package/docs/build.md +169 -0
  7. package/docs/ipc-ws.md +202 -0
  8. package/docs/ipc.md +323 -0
  9. package/docs/native.md +1066 -0
  10. package/docs/page.md +71 -0
  11. package/docs/settings-store.md +125 -0
  12. package/docs/webview-library.md +104 -0
  13. package/docs/window-chrome.md +221 -0
  14. package/docs/worker.md +72 -0
  15. package/file-imports.d.ts +19 -0
  16. package/package.json +66 -4
  17. package/src/app.ts +350 -0
  18. package/src/build.ts +118 -0
  19. package/src/html.ts +71 -0
  20. package/src/index.ts +8 -0
  21. package/src/ipc/bridge.ts +41 -0
  22. package/src/ipc/frame.ts +16 -0
  23. package/src/ipc/index.ts +16 -0
  24. package/src/ipc/memory.ts +33 -0
  25. package/src/ipc/service.ts +75 -0
  26. package/src/ipc-ws/client.ts +111 -0
  27. package/src/ipc-ws/index.ts +5 -0
  28. package/src/ipc-ws/server.ts +133 -0
  29. package/src/make-app.ts +93 -0
  30. package/src/messages.ts +16 -0
  31. package/src/native/app-icon.ts +23 -0
  32. package/src/native/app-menu.ts +18 -0
  33. package/src/native/darwin/app-icon.ts +10 -0
  34. package/src/native/darwin/app-menu.ts +88 -0
  35. package/src/native/darwin/folder-picker.ts +29 -0
  36. package/src/native/darwin/image.ts +47 -0
  37. package/src/native/darwin/menu.ts +89 -0
  38. package/src/native/darwin/notifier.ts +95 -0
  39. package/src/native/darwin/objc.ts +246 -0
  40. package/src/native/darwin/overlay.ts +352 -0
  41. package/src/native/darwin/tray.ts +129 -0
  42. package/src/native/darwin/window.ts +64 -0
  43. package/src/native/folder-picker.ts +15 -0
  44. package/src/native/hit-regions.ts +162 -0
  45. package/src/native/icon.ts +530 -0
  46. package/src/native/index.ts +38 -0
  47. package/src/native/menu.ts +43 -0
  48. package/src/native/notifications.ts +40 -0
  49. package/src/native/overlay.ts +182 -0
  50. package/src/native/platform.ts +25 -0
  51. package/src/native/tray.ts +41 -0
  52. package/src/native/win32/api.ts +234 -0
  53. package/src/native/win32/app-icon.ts +5 -0
  54. package/src/native/win32/app-menu.ts +4 -0
  55. package/src/native/win32/folder-picker.ts +121 -0
  56. package/src/native/win32/icon.ts +85 -0
  57. package/src/native/win32/menu.ts +107 -0
  58. package/src/native/win32/message-window.ts +72 -0
  59. package/src/native/win32/notifier.ts +28 -0
  60. package/src/native/win32/overlay.ts +499 -0
  61. package/src/native/win32/tray.ts +228 -0
  62. package/src/native/win32/window.ts +77 -0
  63. package/src/native/window.ts +40 -0
  64. package/src/page.ts +48 -0
  65. package/src/ring.ts +110 -0
  66. package/src/settings-store.ts +101 -0
  67. package/src/webview-lib.ts +60 -0
  68. package/src/window-chrome.ts +118 -0
  69. package/src/worker.ts +72 -0
@@ -0,0 +1,38 @@
1
+ // One module per feature. Import what you use.
2
+ export { createTray, type Tray, type TrayIcon, type TrayOptions } from "./tray";
3
+ export { createNotifier, type NotificationOptions, type Notifier, type NotifierOptions } from "./notifications";
4
+ export { nativeWindow, type NativeWindow, type WindowHandle } from "./window";
5
+ export {
6
+ createOverlayWindow,
7
+ makeOverlay,
8
+ type Overlay,
9
+ type OverlayOptions,
10
+ type OverlayWindowOptions,
11
+ type HitRefiner,
12
+ type Point,
13
+ type Rect,
14
+ type ResizeEdge,
15
+ } from "./overlay";
16
+ export { convexHull, createHitRelay, flattenRegions, hitRegions, packRegions, unpackRegions, type HitPolygon, type HitProbe, type HitRelay } from "./hit-regions";
17
+ export { pickFolder } from "./folder-picker";
18
+ export { installAppMenu, type AppMenuOptions } from "./app-menu";
19
+ export { setAppIcon } from "./app-icon";
20
+ export { resolveMenu, type MenuItem, type MenuSource, type MenuTemplate } from "./menu";
21
+ export {
22
+ decodePng,
23
+ dotIcns,
24
+ dotIcon,
25
+ dotPng,
26
+ encodeIcns,
27
+ encodeIco,
28
+ encodePng,
29
+ icnsFromPng,
30
+ icnsFromRgba,
31
+ icoFromPng,
32
+ icoFromRgba,
33
+ renderDot,
34
+ trimTransparent,
35
+ type DecodedPng,
36
+ type DotIconOptions,
37
+ type Icon,
38
+ } from "./icon";
@@ -0,0 +1,43 @@
1
+ // Menus as data. A template describes items; each platform renders it with
2
+ // its own menu system when the menu opens.
3
+
4
+ import type { Icon } from "./icon";
5
+
6
+ export type MenuItem =
7
+ | {
8
+ type?: "item";
9
+ label: string;
10
+ /** Greyed out and unclickable when false. Default true. */
11
+ enabled?: boolean;
12
+ /** Shows a check mark. */
13
+ checked?: boolean;
14
+ /** An image before the label: 16px from the .ico on Windows, a 16pt template image on macOS. */
15
+ icon?: Icon;
16
+ click?: () => void;
17
+ }
18
+ | {
19
+ type?: "submenu";
20
+ label: string;
21
+ enabled?: boolean;
22
+ icon?: Icon;
23
+ submenu: MenuTemplate;
24
+ }
25
+ | { type: "separator" };
26
+
27
+ export type MenuTemplate = MenuItem[];
28
+
29
+ /**
30
+ * A template, or a function that produces one. The function form is called
31
+ * every time the menu is about to open, so labels, check marks and enabled
32
+ * state can reflect current app state without any bookkeeping.
33
+ */
34
+ export type MenuSource = MenuTemplate | (() => MenuTemplate);
35
+
36
+ /** A template from a source: calls the function form fresh each time. */
37
+ export const resolveMenu = (source: MenuSource | undefined): MenuTemplate =>
38
+ source === undefined ? [] : typeof source === "function" ? source() : source;
39
+
40
+ export const isSeparator = (item: MenuItem): item is { type: "separator" } => item.type === "separator";
41
+
42
+ export const isSubmenu = (item: MenuItem): item is Extract<MenuItem, { submenu: MenuTemplate }> =>
43
+ !isSeparator(item) && "submenu" in item;
@@ -0,0 +1,40 @@
1
+ // System notifications.
2
+ //
3
+ // Windows: the tray icon's balloon, which Windows 10/11 shows as a toast with
4
+ // our icon, so create the tray first. macOS: UNUserNotificationCenter, which
5
+ // asks the user for permission on the first notification and needs a bundle
6
+ // identifier, so run the .app rather than the bare binary.
7
+
8
+ import { backend } from "./platform";
9
+
10
+ export type NotificationOptions = {
11
+ title: string;
12
+ body?: string;
13
+ /** No sound. Default false. */
14
+ silent?: boolean;
15
+ };
16
+
17
+ export type NotifierOptions = {
18
+ /** The user clicked a notification. */
19
+ onClick?: () => void;
20
+ };
21
+
22
+ export interface Notifier {
23
+ /**
24
+ * Shows a notification. Returns false when the platform cannot show one
25
+ * right now (macOS without a bundle id; Windows after the tray is gone), in
26
+ * which case nothing is displayed.
27
+ */
28
+ notify(notification: NotificationOptions): boolean;
29
+ dispose(): void;
30
+ }
31
+
32
+ type Backend = { createNotifier(options?: NotifierOptions): Notifier };
33
+ const impl = backend<Backend>(
34
+ () => require("./win32/notifier"),
35
+ () => require("./darwin/notifier"),
36
+ );
37
+
38
+ export function createNotifier(options: NotifierOptions = {}): Notifier {
39
+ return impl().createNotifier(options);
40
+ }
@@ -0,0 +1,182 @@
1
+ // Turn a window someone else created (the webview's) into an overlay: no
2
+ // frame, above everything, off the taskbar, never takes focus, and see-through
3
+ // where the page paints the colour key. Then move it around.
4
+ //
5
+ // Units: physical pixels on Windows (the process is per-monitor DPI aware),
6
+ // points on macOS; `scale()` says how many of them one CSS pixel of the page
7
+ // is. Origin top-left of the primary screen on both.
8
+ //
9
+ // The colour key (Windows only) is 1-bit transparency; `transparent` is real
10
+ // alpha on both platforms.
11
+
12
+ import type { HitPolygon } from "./hit-regions";
13
+ import type { Icon } from "./icon";
14
+ import type { WindowHandle } from "./window";
15
+ import { backend } from "./platform";
16
+
17
+ export type Rect = { x: number; y: number; width: number; height: number };
18
+ export type Point = { x: number; y: number };
19
+
20
+ /**
21
+ * Asked while the cursor is inside a hit polygon, with the cursor in
22
+ * window-local units; whoever knows the exact shape (the page, with a
23
+ * triangle pick) calls `answer` with whether that point is really solid.
24
+ * Until the answer arrives the polygon's own answer stands; an answer that
25
+ * arrives after the cursor has moved on is dropped.
26
+ */
27
+ export type HitRefiner = (local: Point, answer: (solid: boolean) => void) => void;
28
+
29
+ /** An edge or corner of the window, for startResize. */
30
+ export type ResizeEdge = "left" | "right" | "top" | "bottom" | "top-left" | "top-right" | "bottom-left" | "bottom-right";
31
+
32
+ export type OverlayOptions = {
33
+ /**
34
+ * Honour the page's alpha: wherever the page is transparent, the desktop
35
+ * shows through, anti-aliased edges and all. Needs the browser to paint a
36
+ * transparent background too (sweetview's `transparent: true`). Clicks on
37
+ * transparent pixels still land on the window; use setClickThrough for
38
+ * whole-window pass-through.
39
+ */
40
+ transparent?: boolean;
41
+ /** `#rrggbb`; pixels of exactly this colour are transparent and click-through. 1-bit; not needed with `transparent`. */
42
+ colorKey?: string;
43
+ /** Whole-window opacity, 0..1. Combines with the colour key. */
44
+ opacity?: number;
45
+ /** Above every normal window. Default true. */
46
+ topmost?: boolean;
47
+ /** No taskbar button, no Alt+Tab entry. Default true. */
48
+ hideFromTaskbar?: boolean;
49
+ /**
50
+ * Clicking it never takes focus from the app the user is in. Default true.
51
+ * `false` makes a frameless window that takes focus and keyboard input (a
52
+ * settings window drawing its own chrome); on macOS that needs the window
53
+ * created by `createOverlayWindow`, which gives it a class that can become key.
54
+ */
55
+ noActivate?: boolean;
56
+ };
57
+
58
+ export interface Overlay {
59
+ /** The HWND / NSWindow*, for handing to sweetview as the host window. */
60
+ readonly handle: WindowHandle;
61
+ /** Shows without taking focus. */
62
+ show(): void;
63
+ hide(): void;
64
+ /**
65
+ * Windows: the title bar, taskbar and Alt+Tab icon from `icon.windows.ico`;
66
+ * undefined restores the stock one. Only a framed window shows it. macOS:
67
+ * no-op, a window has no icon of its own there; `setAppIcon` sets the Dock's.
68
+ */
69
+ setIcon(icon: Icon | undefined): void;
70
+ /** Position and size in window units (see `scale()`). */
71
+ bounds(): Rect;
72
+ moveTo(x: number, y: number): void;
73
+ resize(width: number, height: number): void;
74
+ setBounds(rect: Rect): void;
75
+ /** Whole-window: when on, every click falls through to what is underneath. */
76
+ setClickThrough(on: boolean): void;
77
+ /**
78
+ * Which parts of the window take clicks: polygons in window-local units
79
+ * (see `scale()`); a click anywhere else falls through to whatever is
80
+ * underneath. `null` means the whole window takes clicks again. Meant to
81
+ * be resent as things move, tens of times a second if need be: nothing is
82
+ * repainted for it. The page computes the polygons from what it draws
83
+ * (the convex hull of each mesh's projected bounding-box corners is its
84
+ * exact silhouette for box-shaped meshes; `convexHull` in
85
+ * `@n1h/sweetview/native/hit-regions` is page-safe) and this side answers the
86
+ * cursor itself, so the page is never in the click path.
87
+ *
88
+ * How: a 10 ms timer, and every update, test the cursor against the
89
+ * polygons and flip the window's pass-through state (Windows:
90
+ * WS_EX_TRANSPARENT with WS_EX_LAYERED, the only thing the window manager
91
+ * honours across processes short of a window region, which forces a
92
+ * repaint of the browser each time it changes; macOS: ignoresMouseEvents).
93
+ * Nothing changes while a mouse button is down, so a drag survives leaving
94
+ * the polygons. While the cursor is outside the window the window passes
95
+ * clicks through, so entering onto empty space is right from the first
96
+ * pixel; entering onto a polygon takes one tick.
97
+ *
98
+ * The polygons are the coarse answer: a mesh's bounding-box hull includes
99
+ * the gap between a cat's ears. With `refine`, every time the cursor is
100
+ * inside a polygon (and whenever the polygons change under a still cursor)
101
+ * the refiner is asked for the exact answer and the pass-through state
102
+ * follows its reply, typically a few milliseconds later; outside every
103
+ * polygon nobody is asked. A refiner that never answers leaves polygon
104
+ * precision, so a stalled page degrades, never breaks.
105
+ */
106
+ setHitRegions(polygons: HitPolygon[] | null, refine?: HitRefiner): void;
107
+ setTopmost(on: boolean): void;
108
+ /**
109
+ * Hand the mouse gesture in progress to the OS as a window move, as a
110
+ * title bar would: called from a UI-thread method on the page's
111
+ * pointer-down in its own title bar, the OS then moves the window with the
112
+ * cursor, with snapping and all, until the button is released. A page
113
+ * draws its own chrome this way in a frameless window.
114
+ */
115
+ startDrag(): void;
116
+ /** The same for a resize from an edge or corner (the page's own resize handles). macOS: no-op; a frameless macOS overlay has no resizable mask, so it is not resized by the user at all. */
117
+ startResize(edge: ResizeEdge): void;
118
+ minimize(): void;
119
+ /** Between the work area and the previous bounds (Windows); zoom (macOS). */
120
+ toggleMaximize(): void;
121
+ maximized(): boolean;
122
+ /** Cursor position on the screen, window units. */
123
+ cursor(): Point;
124
+ /** The whole primary monitor, window units. The overlay draws over the taskbar or Dock there. */
125
+ screen(): Rect;
126
+ /** The whole monitor under a point, window units; the nearest one when the point is on none. A window dropped on another monitor finds its new bounds with this. */
127
+ screenAt(point: Point): Rect;
128
+ /** The primary monitor minus the taskbar or Dock, window units. */
129
+ workArea(): Rect;
130
+ /** Every monitor together, window units. */
131
+ virtualScreen(): Rect;
132
+ /** Window units per CSS pixel of the page: the DPI scale on Windows, 1 on macOS. */
133
+ scale(): number;
134
+ }
135
+
136
+ export type OverlayWindowOptions = OverlayOptions & {
137
+ title?: string;
138
+ /** Initial bounds, window units. Default: 320x240 at the work area's centre. */
139
+ bounds?: Rect;
140
+ /**
141
+ * A normal window instead of an overlay: title bar and close button, a
142
+ * taskbar or Dock presence, activates when clicked, not above other
143
+ * windows, opaque. Still created hidden, so `show()` is the first time it
144
+ * appears, and its close button hides it rather than ending the app. For
145
+ * a settings window next to an overlay, hosted by the same process and OS
146
+ * loop. On macOS the overlay options are ignored; on Windows `frame` only
147
+ * changes their defaults (not topmost, in the taskbar, activating, opaque),
148
+ * and an option given explicitly still applies.
149
+ */
150
+ frame?: boolean;
151
+ /** Framed windows: dark title bar and frame (Windows 11's immersive dark mode; the dark appearance on macOS), for a dark page. */
152
+ dark?: boolean;
153
+ /** Windows: the title bar, taskbar and Alt+Tab icon (see `Overlay.setIcon`). macOS: ignored. */
154
+ icon?: Icon;
155
+ };
156
+
157
+ type Backend = {
158
+ makeOverlay(handle: WindowHandle, options: OverlayOptions): Overlay;
159
+ createOverlayWindow(options: OverlayWindowOptions): Overlay;
160
+ };
161
+ const impl = backend<Backend>(
162
+ () => require("./win32/overlay"),
163
+ () => require("./darwin/overlay"),
164
+ );
165
+
166
+ /** Restyle an existing window (the webview's) into an overlay. */
167
+ export function makeOverlay(handle: WindowHandle, options: OverlayOptions = {}): Overlay {
168
+ return impl().makeOverlay(handle, options);
169
+ }
170
+
171
+ /**
172
+ * Create a hidden overlay window of our own, to hand to sweetview as the host
173
+ * (`createWebviewApp({ window: overlay.handle })`). Transparency set up before
174
+ * the browser is created is what WebView2 needs for a see-through page; it
175
+ * ignores a host that turned transparent later. Call `show()` once the page
176
+ * is set. On Windows the window's procedure ends the run loop when the
177
+ * window is destroyed and keeps the browser sized to the client area; on
178
+ * macOS neither happens here.
179
+ */
180
+ export function createOverlayWindow(options: OverlayWindowOptions = {}): Overlay {
181
+ return impl().createOverlayWindow(options);
182
+ }
@@ -0,0 +1,25 @@
1
+ // Internal. Picks the implementation folder for the running OS, once, lazily.
2
+ //
3
+ // `require` rather than `import()` so every public function stays synchronous:
4
+ // the UI thread cannot await while the webview's run loop owns it, and the
5
+ // bundler still embeds both folders (the strings are static), evaluating only
6
+ // the one the process is on.
7
+
8
+ export function backend<T>(win32: () => T, darwin: () => T): () => T {
9
+ let loaded: T | undefined;
10
+ return () => {
11
+ if (loaded === undefined) {
12
+ switch (process.platform) {
13
+ case "win32":
14
+ loaded = win32();
15
+ break;
16
+ case "darwin":
17
+ loaded = darwin();
18
+ break;
19
+ default:
20
+ throw new Error(`sweetnative: ${process.platform} is not supported (win32 and darwin are)`);
21
+ }
22
+ }
23
+ return loaded;
24
+ };
25
+ }
@@ -0,0 +1,41 @@
1
+ // Tray icon (Windows notification area) / menu bar status item (macOS).
2
+ //
3
+ // Runs on the UI thread. Callbacks fire on that thread from inside the OS
4
+ // loop: return quickly, and do not await anything that needs Bun's event loop
5
+ // (it is blocked while webview.run() is on the stack).
6
+
7
+ import type { Icon } from "./icon";
8
+ import type { MenuSource } from "./menu";
9
+ import { backend } from "./platform";
10
+
11
+ /** The tray's icon: an `Icon`. Windows picks the 16px image for the tray and 32px for toasts; macOS shows the PNG at 18x18 points (36px for Retina). */
12
+ export type TrayIcon = Icon;
13
+
14
+ export type TrayOptions = {
15
+ tooltip?: string;
16
+ icon?: TrayIcon;
17
+ /** Windows: opened by right click. macOS: by any click, as menu bar items do. */
18
+ menu?: MenuSource;
19
+ /** Left single click. Fires only when the click did not turn into a double click. macOS: only when no menu is set. */
20
+ onClick?: () => void;
21
+ /** Left double click. macOS: only when no menu is set. */
22
+ onDoubleClick?: () => void;
23
+ };
24
+
25
+ export interface Tray {
26
+ setTooltip(text: string): void;
27
+ setIcon(icon: TrayIcon): void;
28
+ setMenu(menu: MenuSource | undefined): void;
29
+ /** Removes the icon. Safe to call twice. */
30
+ dispose(): void;
31
+ }
32
+
33
+ type Backend = { createTray(options: TrayOptions): Tray };
34
+ const impl = backend<Backend>(
35
+ () => require("./win32/tray"),
36
+ () => require("./darwin/tray"),
37
+ );
38
+
39
+ export function createTray(options: TrayOptions): Tray {
40
+ return impl().createTray(options);
41
+ }
@@ -0,0 +1,234 @@
1
+ // The Win32 symbols this library needs, dlopened once. Handles travel as
2
+ // bigint (FFIType.u64) so a NULL handle is 0n, never a JS null.
3
+
4
+ import { FFIType, dlopen, type Pointer } from "bun:ffi";
5
+
6
+ export const user32 = dlopen("user32.dll", {
7
+ // (const WNDCLASSEXW*) -> ATOM
8
+ RegisterClassExW: { args: [FFIType.ptr], returns: FFIType.u16 },
9
+ // (DWORD exStyle, LPCWSTR class, LPCWSTR title, DWORD style, int x, int y, int w, int h,
10
+ // HWND parent, HMENU menu, HINSTANCE, LPVOID param) -> HWND
11
+ CreateWindowExW: {
12
+ args: [
13
+ FFIType.u32,
14
+ FFIType.ptr,
15
+ FFIType.ptr,
16
+ FFIType.u32,
17
+ FFIType.i32,
18
+ FFIType.i32,
19
+ FFIType.i32,
20
+ FFIType.i32,
21
+ FFIType.u64,
22
+ FFIType.u64,
23
+ FFIType.u64,
24
+ FFIType.ptr,
25
+ ],
26
+ returns: FFIType.u64,
27
+ },
28
+ DestroyWindow: { args: [FFIType.u64], returns: FFIType.i32 },
29
+ // (HWND, UINT, WPARAM, LPARAM) -> LRESULT
30
+ DefWindowProcW: { args: [FFIType.u64, FFIType.u32, FFIType.u64, FFIType.i64], returns: FFIType.i64 },
31
+ PostMessageW: { args: [FFIType.u64, FFIType.u32, FFIType.u64, FFIType.i64], returns: FFIType.i32 },
32
+ RegisterWindowMessageW: { args: [FFIType.ptr], returns: FFIType.u32 },
33
+
34
+ ShowWindow: { args: [FFIType.u64, FFIType.i32], returns: FFIType.i32 },
35
+ IsWindowVisible: { args: [FFIType.u64], returns: FFIType.i32 },
36
+ IsIconic: { args: [FFIType.u64], returns: FFIType.i32 },
37
+ IsZoomed: { args: [FFIType.u64], returns: FFIType.i32 },
38
+ ReleaseCapture: { args: [], returns: FFIType.i32 },
39
+ SendMessageW: { args: [FFIType.u64, FFIType.u32, FFIType.u64, FFIType.i64], returns: FFIType.i64 },
40
+ SetForegroundWindow: { args: [FFIType.u64], returns: FFIType.i32 },
41
+ GetCursorPos: { args: [FFIType.ptr], returns: FFIType.i32 },
42
+ GetAsyncKeyState: { args: [FFIType.i32], returns: FFIType.i16 },
43
+ GetSystemMetrics: { args: [FFIType.i32], returns: FFIType.i32 },
44
+ // (const RECT*, DWORD flags) -> HMONITOR; the monitor with the largest overlap, or the nearest one with MONITOR_DEFAULTTONEAREST
45
+ MonitorFromRect: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.u64 },
46
+ // (HMONITOR, MONITORINFO*) -> BOOL; cbSize set by the caller
47
+ GetMonitorInfoW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
48
+ GetDoubleClickTime: { args: [], returns: FFIType.u32 },
49
+ // (HWND, UINT_PTR id, UINT ms, TIMERPROC) -> UINT_PTR
50
+ SetTimer: { args: [FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr], returns: FFIType.u64 },
51
+ KillTimer: { args: [FFIType.u64, FFIType.u64], returns: FFIType.i32 },
52
+
53
+ CreatePopupMenu: { args: [], returns: FFIType.u64 },
54
+ // (HMENU, UINT flags, UINT_PTR idOrSubmenu, LPCWSTR text) -> BOOL
55
+ AppendMenuW: { args: [FFIType.u64, FFIType.u32, FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
56
+ DestroyMenu: { args: [FFIType.u64], returns: FFIType.i32 },
57
+ // (HMENU, UINT item, BOOL byPosition, const MENUITEMINFOW*) -> BOOL
58
+ SetMenuItemInfoW: { args: [FFIType.u64, FFIType.u32, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
59
+ // (HMENU, UINT flags, int x, int y, int reserved, HWND owner, const RECT*) -> int
60
+ TrackPopupMenu: {
61
+ args: [FFIType.u64, FFIType.u32, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.u64, FFIType.ptr],
62
+ returns: FFIType.i32,
63
+ },
64
+
65
+ // (PBYTE bits, DWORD size, BOOL icon, DWORD version, int cx, int cy, UINT flags) -> HICON
66
+ CreateIconFromResourceEx: {
67
+ args: [FFIType.ptr, FFIType.u32, FFIType.i32, FFIType.u32, FFIType.i32, FFIType.i32, FFIType.u32],
68
+ returns: FFIType.u64,
69
+ },
70
+ LoadIconW: { args: [FFIType.u64, FFIType.u64], returns: FFIType.u64 },
71
+ DestroyIcon: { args: [FFIType.u64], returns: FFIType.i32 },
72
+ // Overlay windows: styles, placement, layered transparency, screen geometry
73
+ GetWindowLongPtrW: { args: [FFIType.u64, FFIType.i32], returns: FFIType.i64 },
74
+ SetWindowLongPtrW: { args: [FFIType.u64, FFIType.i32, FFIType.i64], returns: FFIType.i64 },
75
+ // (HWND, HWND insertAfter, x, y, cx, cy, flags) -> BOOL
76
+ SetWindowPos: { args: [FFIType.u64, FFIType.i64, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.u32], returns: FFIType.i32 },
77
+ GetWindowRect: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
78
+ // (HWND, COLORREF key, BYTE alpha, DWORD flags) -> BOOL
79
+ SetLayeredWindowAttributes: { args: [FFIType.u64, FFIType.u32, FFIType.u8, FFIType.u32], returns: FFIType.i32 },
80
+ SystemParametersInfoW: { args: [FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
81
+ PostQuitMessage: { args: [FFIType.i32], returns: FFIType.void },
82
+ // (DPI_AWARENESS_CONTEXT) -> BOOL; the webview library does this only for windows it creates itself
83
+ SetProcessDpiAwarenessContext: { args: [FFIType.i64], returns: FFIType.i32 },
84
+ GetDpiForWindow: { args: [FFIType.u64], returns: FFIType.u32 },
85
+ // (HWND, UINT cmd) -> HWND
86
+ GetWindow: { args: [FFIType.u64, FFIType.u32], returns: FFIType.u64 },
87
+ GetClientRect: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
88
+ MoveWindow: { args: [FFIType.u64, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32], returns: FFIType.i32 },
89
+ // (HWND, HRGN, BOOL redraw) -> int; the system owns the region afterwards
90
+ SetWindowRgn: { args: [FFIType.u64, FFIType.u64, FFIType.i32], returns: FFIType.i32 },
91
+ }).symbols;
92
+
93
+ export const kernel32 = dlopen("kernel32.dll", {
94
+ GetModuleHandleW: { args: [FFIType.ptr], returns: FFIType.u64 },
95
+ GetLastError: { args: [], returns: FFIType.u32 },
96
+ }).symbols;
97
+
98
+ export const comctl32 = dlopen("comctl32.dll", {
99
+ // (HWND, SUBCLASSPROC, UINT_PTR id, DWORD_PTR refData) -> BOOL
100
+ SetWindowSubclass: { args: [FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.u64], returns: FFIType.i32 },
101
+ RemoveWindowSubclass: { args: [FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.i32 },
102
+ // (HWND, UINT, WPARAM, LPARAM) -> LRESULT : the next procedure in the subclass chain
103
+ DefSubclassProc: { args: [FFIType.u64, FFIType.u32, FFIType.u64, FFIType.i64], returns: FFIType.i64 },
104
+ }).symbols;
105
+
106
+ export const ole32 = dlopen("ole32.dll", {
107
+ // (LPVOID reserved, DWORD coinit) -> HRESULT (S_OK, S_FALSE if already initialised, RPC_E_CHANGED_MODE if the other model)
108
+ CoInitializeEx: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
109
+ }).symbols;
110
+
111
+ export const dwmapi = dlopen("dwmapi.dll", {
112
+ // (HWND, const DWM_BLURBEHIND*) -> HRESULT
113
+ DwmEnableBlurBehindWindow: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.i32 },
114
+ DwmSetWindowAttribute: { args: [FFIType.u64, FFIType.u32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
115
+ }).symbols;
116
+
117
+ export const gdi32 = dlopen("gdi32.dll", {
118
+ CreateRectRgn: { args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.i32], returns: FFIType.u64 },
119
+ // (const XFORM*, DWORD bytes, const RGNDATA*) -> HRGN
120
+ ExtCreateRegion: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr], returns: FFIType.u64 },
121
+ DeleteObject: { args: [FFIType.u64], returns: FFIType.i32 },
122
+ // (HDC, const BITMAPINFO*, UINT usage, void** bits, HANDLE section, DWORD offset) -> HBITMAP
123
+ CreateDIBSection: { args: [FFIType.u64, FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.u64, FFIType.u32], returns: FFIType.u64 },
124
+ }).symbols;
125
+
126
+ export const shell32 = dlopen("shell32.dll", {
127
+ // (DWORD message, PNOTIFYICONDATAW) -> BOOL
128
+ Shell_NotifyIconW: { args: [FFIType.u32, FFIType.ptr], returns: FFIType.i32 },
129
+ }).symbols;
130
+
131
+ /**
132
+ * NUL-terminated UTF-16LE for LPCWSTR arguments. Pass the array itself to an
133
+ * FFIType.ptr parameter (never `ptr(wstr(..))`): Bun keeps a TypedArray
134
+ * argument alive for the duration of the call, while a bare pointer can be
135
+ * collected by a GC triggered while the remaining arguments are evaluated.
136
+ */
137
+ export const wstr = (s: string): Uint8Array => new Uint8Array(Buffer.from(s + "\0", "utf16le"));
138
+
139
+ /** A non-null pointer from a bigint/number out-parameter; throws on NULL. */
140
+ export const asPointer = (v: bigint | number | null): Pointer => {
141
+ if (v === null || v === 0 || v === 0n) throw new Error("sweetnative: unexpected NULL pointer");
142
+ return Number(v) as Pointer;
143
+ };
144
+
145
+ /** A nullable pointer for a Win32 handle argument (HWND etc.) given as bigint, number or null. */
146
+ export const toPointer = (v: bigint | number | null): Pointer | null => (v === null || v === 0 || v === 0n ? null : (Number(v) as Pointer));
147
+
148
+ export const hInstance = (): bigint => kernel32.GetModuleHandleW(null);
149
+
150
+ export const lastError = () => kernel32.GetLastError();
151
+
152
+ // Window messages
153
+ export const WM_CLOSE = 0x0010;
154
+ export const WM_NCDESTROY = 0x0082;
155
+ export const WM_TIMER = 0x0113;
156
+ export const WM_LBUTTONUP = 0x0202;
157
+ export const WM_LBUTTONDBLCLK = 0x0203;
158
+ export const WM_RBUTTONUP = 0x0205;
159
+ export const WM_NULL = 0x0000;
160
+ export const WM_APP = 0x8000;
161
+ export const WM_SETICON = 0x0080;
162
+ export const ICON_SMALL = 0n;
163
+ export const ICON_BIG = 1n;
164
+
165
+ // ShowWindow
166
+ export const SW_HIDE = 0;
167
+ export const SW_SHOWNOACTIVATE = 4;
168
+ export const SW_SHOW = 5;
169
+ export const SW_RESTORE = 9;
170
+ export const SW_MINIMIZE = 6;
171
+ /** Non-client mouse down: with a hit-test code it starts the OS's own move (HTCAPTION) or size loop. */
172
+ export const WM_NCLBUTTONDOWN = 0x00a1;
173
+ export const HTCAPTION = 2;
174
+
175
+ // More window messages
176
+ export const WM_DESTROY = 0x0002;
177
+ export const WM_SIZE = 0x0005;
178
+
179
+ // GetWindow
180
+ export const GW_CHILD = 5;
181
+
182
+ // GetSystemMetrics
183
+ export const SM_CXICON = 11;
184
+ export const SM_CXSMICON = 49;
185
+ export const SM_CXSCREEN = 0;
186
+ export const SM_CYSCREEN = 1;
187
+ export const SM_XVIRTUALSCREEN = 76;
188
+ export const SM_YVIRTUALSCREEN = 77;
189
+ export const SM_CXVIRTUALSCREEN = 78;
190
+ export const SM_CYVIRTUALSCREEN = 79;
191
+
192
+ // MonitorFromRect
193
+ export const MONITOR_DEFAULTTONEAREST = 2;
194
+ /** MONITORINFO: cbSize, rcMonitor, rcWork, dwFlags; 40 bytes. */
195
+ export const MONITORINFO_SIZE = 40;
196
+
197
+ // Get/SetWindowLongPtr
198
+ export const GWL_STYLE = -16;
199
+ export const GWL_EXSTYLE = -20;
200
+ export const WS_POPUP = 0x80000000;
201
+ export const WS_CLIPCHILDREN = 0x02000000;
202
+ export const WS_OVERLAPPEDWINDOW = 0x00cf0000; // caption, sysmenu, thickframe, min/max boxes
203
+ export const WS_EX_TRANSPARENT = 0x00000020;
204
+ export const WS_EX_TOOLWINDOW = 0x00000080;
205
+ export const WS_EX_APPWINDOW = 0x00040000;
206
+ export const WS_EX_LAYERED = 0x00080000;
207
+ export const WS_EX_NOACTIVATE = 0x08000000;
208
+
209
+ // SetWindowPos
210
+ export const HWND_TOPMOST = -1n;
211
+ export const HWND_NOTOPMOST = -2n;
212
+ export const SWP_NOSIZE = 0x0001;
213
+ export const SWP_NOMOVE = 0x0002;
214
+ export const SWP_NOZORDER = 0x0004;
215
+ export const SWP_NOACTIVATE = 0x0010;
216
+ export const SWP_FRAMECHANGED = 0x0020;
217
+
218
+ // SetLayeredWindowAttributes
219
+ export const LWA_COLORKEY = 0x1;
220
+ export const LWA_ALPHA = 0x2;
221
+
222
+ // SystemParametersInfo
223
+ export const SPI_GETWORKAREA = 0x0030;
224
+
225
+ // CoInitializeEx
226
+ export const COINIT_APARTMENTTHREADED = 0x2;
227
+ export const RPC_E_CHANGED_MODE = -2147417850; // 0x80010106
228
+
229
+ // SetProcessDpiAwarenessContext
230
+ export const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4n;
231
+
232
+ // DWM_BLURBEHIND.dwFlags
233
+ export const DWM_BB_ENABLE = 0x1;
234
+ export const DWM_BB_BLURREGION = 0x2;
@@ -0,0 +1,5 @@
1
+ // Windows has no run-time app icon: see ../app-icon.ts.
2
+
3
+ import type { Icon } from "../icon";
4
+
5
+ export function setAppIcon(_icon: Icon): void {}
@@ -0,0 +1,4 @@
1
+ import type { AppMenuOptions } from "../app-menu";
2
+
3
+ /** Windows has no application menu bar here; the tray menu is the app's menu. */
4
+ export function installAppMenu(_options: AppMenuOptions): void {}