@zenbu-labs/pixel 0.0.0 → 0.0.12

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 (153) hide show
  1. package/dist/bin.d.ts +2 -0
  2. package/dist/bin.js +166 -0
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +75 -0
  5. package/dist/context-menu.d.ts +20 -0
  6. package/dist/context-menu.js +57 -0
  7. package/dist/debug.d.ts +1 -0
  8. package/dist/debug.js +9 -0
  9. package/dist/devtools-layout.d.ts +21 -0
  10. package/dist/devtools-layout.js +45 -0
  11. package/dist/devtools.d.ts +33 -0
  12. package/dist/devtools.js +125 -0
  13. package/dist/electron/index.d.ts +3 -0
  14. package/dist/electron/index.js +4 -0
  15. package/dist/host/guest.d.ts +5 -0
  16. package/dist/host/guest.js +178 -0
  17. package/dist/host/server.d.ts +91 -0
  18. package/dist/host/server.js +296 -0
  19. package/dist/host/shell.d.ts +35 -0
  20. package/dist/host/shell.js +113 -0
  21. package/dist/host/strip.d.ts +6 -0
  22. package/dist/host/strip.js +63 -0
  23. package/dist/icons.d.ts +30 -0
  24. package/dist/icons.js +58 -0
  25. package/dist/index.d.ts +17 -0
  26. package/dist/index.js +39 -0
  27. package/dist/instances.d.ts +30 -0
  28. package/dist/instances.js +177 -0
  29. package/dist/layout.d.ts +5 -0
  30. package/dist/layout.js +34 -0
  31. package/dist/popup.d.ts +21 -0
  32. package/dist/popup.js +47 -0
  33. package/dist/preload/index.d.ts +15 -0
  34. package/dist/preload/index.js +3 -0
  35. package/dist/react/colors.d.ts +3 -0
  36. package/dist/react/colors.js +25 -0
  37. package/dist/react/components.d.ts +21 -0
  38. package/dist/react/components.js +45 -0
  39. package/dist/react/devtools/app.d.ts +3 -0
  40. package/dist/react/devtools/app.js +89 -0
  41. package/dist/react/devtools/console-capture.d.ts +1 -0
  42. package/dist/react/devtools/console-capture.js +41 -0
  43. package/dist/react/devtools/controller.d.ts +45 -0
  44. package/dist/react/devtools/controller.js +252 -0
  45. package/dist/react/devtools/elements.d.ts +4 -0
  46. package/dist/react/devtools/elements.js +152 -0
  47. package/dist/react/devtools/export-profile.d.ts +2 -0
  48. package/dist/react/devtools/export-profile.js +225 -0
  49. package/dist/react/devtools/fiber-hook.d.ts +1 -0
  50. package/dist/react/devtools/fiber-hook.js +89 -0
  51. package/dist/react/devtools/log-panel.d.ts +4 -0
  52. package/dist/react/devtools/log-panel.js +113 -0
  53. package/dist/react/devtools/profiler.d.ts +11 -0
  54. package/dist/react/devtools/profiler.js +374 -0
  55. package/dist/react/devtools/store.d.ts +27 -0
  56. package/dist/react/devtools/store.js +61 -0
  57. package/dist/react/devtools/stores.d.ts +92 -0
  58. package/dist/react/devtools/stores.js +41 -0
  59. package/dist/react/devtools/theme.d.ts +35 -0
  60. package/dist/react/devtools/theme.js +77 -0
  61. package/dist/react/devtools/ui.d.ts +24 -0
  62. package/dist/react/devtools/ui.js +59 -0
  63. package/dist/react/index.d.ts +110 -0
  64. package/dist/react/index.js +538 -0
  65. package/dist/react/markdown.d.ts +25 -0
  66. package/dist/react/markdown.js +270 -0
  67. package/dist/react/native.d.ts +116 -0
  68. package/dist/react/native.js +56 -0
  69. package/dist/react/reconciler-config.d.ts +241 -0
  70. package/dist/react/reconciler-config.js +412 -0
  71. package/dist/react/styles.d.ts +88 -0
  72. package/dist/react/styles.js +85 -0
  73. package/dist/react/surface.d.ts +55 -0
  74. package/dist/react/surface.js +74 -0
  75. package/dist/registry.d.ts +53 -0
  76. package/dist/registry.js +193 -0
  77. package/dist/root.d.ts +19 -0
  78. package/dist/root.js +342 -0
  79. package/dist/sandbox.d.ts +4 -0
  80. package/dist/sandbox.js +84 -0
  81. package/dist/scale.d.ts +7 -0
  82. package/dist/scale.js +31 -0
  83. package/dist/ssh/index.d.ts +58 -0
  84. package/dist/ssh/index.js +445 -0
  85. package/dist/terminal/applescript.d.ts +2 -0
  86. package/dist/terminal/applescript.js +20 -0
  87. package/dist/terminal/detect.d.ts +10 -0
  88. package/dist/terminal/detect.js +34 -0
  89. package/dist/terminal/graphics.d.ts +9 -0
  90. package/dist/terminal/graphics.js +142 -0
  91. package/dist/terminal/index.d.ts +12 -0
  92. package/dist/terminal/index.js +23 -0
  93. package/dist/terminal/run.d.ts +2 -0
  94. package/dist/terminal/run.js +15 -0
  95. package/dist/terminal/shared.d.ts +20 -0
  96. package/dist/terminal/shared.js +88 -0
  97. package/dist/terminal/terminal.d.ts +66 -0
  98. package/dist/terminal/terminal.js +6 -0
  99. package/dist/terminal/terminals/cmux.d.ts +2 -0
  100. package/dist/terminal/terminals/cmux.js +98 -0
  101. package/dist/terminal/terminals/ghostty-script.d.ts +1 -0
  102. package/dist/terminal/terminals/ghostty-script.js +179 -0
  103. package/dist/terminal/terminals/ghostty.d.ts +2 -0
  104. package/dist/terminal/terminals/ghostty.js +336 -0
  105. package/dist/terminal/terminals/herdr.d.ts +2 -0
  106. package/dist/terminal/terminals/herdr.js +141 -0
  107. package/dist/terminal/terminals/index.d.ts +5 -0
  108. package/dist/terminal/terminals/index.js +26 -0
  109. package/dist/terminal/terminals/kitty.d.ts +2 -0
  110. package/dist/terminal/terminals/kitty.js +209 -0
  111. package/dist/terminal/terminals/supacode.d.ts +2 -0
  112. package/dist/terminal/terminals/supacode.js +79 -0
  113. package/dist/terminal/terminals/tmux.d.ts +2 -0
  114. package/dist/terminal/terminals/tmux.js +132 -0
  115. package/dist/terminal/terminals/tty7.d.ts +2 -0
  116. package/dist/terminal/terminals/tty7.js +58 -0
  117. package/dist/terminal/terminals/vscode.d.ts +2 -0
  118. package/dist/terminal/terminals/vscode.js +13 -0
  119. package/dist/terminal/terminals/wezterm.d.ts +2 -0
  120. package/dist/terminal/terminals/wezterm.js +59 -0
  121. package/dist/theme.d.ts +22 -0
  122. package/dist/theme.js +40 -0
  123. package/dist/web/browser-session.d.ts +13 -0
  124. package/dist/web/browser-session.js +186 -0
  125. package/dist/web/cursor.d.ts +1 -0
  126. package/dist/web/cursor.js +24 -0
  127. package/dist/web/devtools.d.ts +39 -0
  128. package/dist/web/devtools.js +300 -0
  129. package/dist/web/favicon.d.ts +7 -0
  130. package/dist/web/favicon.js +73 -0
  131. package/dist/web/frame-rate.d.ts +1 -0
  132. package/dist/web/frame-rate.js +12 -0
  133. package/dist/web/host.d.ts +123 -0
  134. package/dist/web/host.js +573 -0
  135. package/dist/web/input.d.ts +49 -0
  136. package/dist/web/input.js +468 -0
  137. package/dist/web/offscreen.d.ts +2 -0
  138. package/dist/web/offscreen.js +23 -0
  139. package/dist/web/paint.d.ts +11 -0
  140. package/dist/web/paint.js +128 -0
  141. package/dist/web/popup.d.ts +40 -0
  142. package/dist/web/popup.js +144 -0
  143. package/dist/web/session.d.ts +20 -0
  144. package/dist/web/session.js +110 -0
  145. package/dist/web/types.d.ts +38 -0
  146. package/dist/web/types.js +39 -0
  147. package/dist/web/zoom.d.ts +5 -0
  148. package/dist/web/zoom.js +36 -0
  149. package/dist/webview.d.ts +55 -0
  150. package/dist/webview.js +481 -0
  151. package/package.json +69 -3
  152. package/scripts/apparmor.sh +71 -0
  153. package/scripts/postinstall.mjs +150 -0
@@ -0,0 +1,481 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WebView = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const electron_1 = require("electron");
10
+ const react_1 = require("react");
11
+ const context_menu_1 = require("./context-menu");
12
+ const debug_1 = require("./debug");
13
+ const devtools_1 = require("./devtools");
14
+ const devtools_layout_1 = require("./devtools-layout");
15
+ const layout_1 = require("./layout");
16
+ const popup_1 = require("./popup");
17
+ const react_2 = require("./react");
18
+ const registry_1 = require("./registry");
19
+ const theme_1 = require("./theme");
20
+ const host_1 = require("./web/host");
21
+ const browser_session_1 = require("./web/browser-session");
22
+ const types_1 = require("./web/types");
23
+ const DRAG_RESIZE_MS = 50;
24
+ exports.WebView = (0, react_1.forwardRef)(function WebView(props, ref) {
25
+ const registry = (0, react_1.useContext)(registry_1.RootContext);
26
+ if (!registry) {
27
+ throw new Error("<WebView> has to be rendered by a root from pixel's createRoot()");
28
+ }
29
+ const colors = (0, registry_1.useRegistryColors)(registry);
30
+ const theme = (0, react_1.useMemo)(() => (0, theme_1.makeTheme)(colors), [colors]);
31
+ const hidden = !!props.hidden;
32
+ const rem = registry.root.info.basePx;
33
+ const scale = registry.displayScale;
34
+ const boxRef = (0, react_1.useRef)(null);
35
+ const rect = (0, layout_1.useNodeRect)(boxRef, registry);
36
+ const hostRef = (0, react_1.useRef)(null);
37
+ const surfaces = (0, react_1.useRef)(null);
38
+ const stateRef = (0, react_1.useRef)((0, types_1.initialWebViewState)(props.src));
39
+ const propsRef = (0, react_1.useRef)(props);
40
+ propsRef.current = props;
41
+ const hover = (0, react_1.useRef)({ page: false, popup: false, devtools: false, divider: false });
42
+ const dragging = (0, react_1.useRef)(false);
43
+ const lastDragResize = (0, react_1.useRef)(0);
44
+ const pendingInspect = (0, react_1.useRef)(null);
45
+ const frameListeners = (0, react_1.useRef)(new Set());
46
+ const changeListeners = (0, react_1.useRef)(new Set());
47
+ const [popup, setPopup] = (0, react_1.useState)(null);
48
+ const [devtoolsOpen, setDevtoolsOpen] = (0, react_1.useState)(false);
49
+ const [dock, setDock] = (0, react_1.useState)(typeof props.devtools === "string" ? props.devtools : "right");
50
+ const dockRef = (0, react_1.useRef)(dock);
51
+ dockRef.current = dock;
52
+ const [fraction, setFraction] = (0, react_1.useState)(0.4);
53
+ const [dividerEngaged, setDividerEngaged] = (0, react_1.useState)(false);
54
+ const [menu, setMenu] = (0, react_1.useState)(null);
55
+ const menuRef = (0, react_1.useRef)(menu);
56
+ menuRef.current = menu;
57
+ const devtoolsEnabled = props.devtools === undefined ? process.env.NODE_ENV !== "production" : props.devtools !== false;
58
+ const entry = (0, react_1.useMemo)(() => ({
59
+ host: null,
60
+ handle: null,
61
+ devtoolsEnabled: false,
62
+ externalDevtools: false,
63
+ externalDevtoolsAction: null,
64
+ toggleDevtools: () => { },
65
+ handleKey: () => false,
66
+ }), []);
67
+ entry.devtoolsEnabled = devtoolsEnabled;
68
+ entry.toggleDevtools = () => {
69
+ if (hostRef.current?.devtools)
70
+ hostRef.current.closeDevtools();
71
+ else
72
+ setDevtoolsOpen(true);
73
+ };
74
+ entry.handleKey = (event) => {
75
+ if (!menuRef.current || event.kind === "release")
76
+ return false;
77
+ setMenu(null);
78
+ return event.key === "escape";
79
+ };
80
+ const inner = rect ? { x: 0, y: 0, width: rect.width, height: rect.height } : null;
81
+ const dockOpen = devtoolsOpen && !entry.externalDevtools;
82
+ const gap = Math.max(2, Math.round(rem * 0.25));
83
+ const split = inner ? (0, devtools_layout_1.splitForDevtools)(inner, dockOpen ? { dock, fraction } : null, gap) : null;
84
+ const page = split
85
+ ? { ...split.page, ...(0, types_1.snapToCssGrid)(split.page.width, split.page.height, scale) }
86
+ : null;
87
+ const devtoolsRect = split?.devtools
88
+ ? { ...split.devtools, ...(0, types_1.snapToCssGrid)(split.devtools.width, split.devtools.height, scale) }
89
+ : null;
90
+ const keepFrame = () => dragging.current || propsRef.current.keepFrame !== false;
91
+ const syncCursor = () => {
92
+ const host = hostRef.current;
93
+ const h = hover.current;
94
+ const shape = h.divider
95
+ ? dockRef.current === "bottom"
96
+ ? "row-resize"
97
+ : "col-resize"
98
+ : h.devtools
99
+ ? host?.devtools?.cursorShape ?? "default"
100
+ : host?.popup
101
+ ? h.popup
102
+ ? host.popup.cursorShape
103
+ : "default"
104
+ : h.page
105
+ ? host?.cursorShape ?? "default"
106
+ : "default";
107
+ registry.setPointerShape(shape);
108
+ };
109
+ const readPopup = (host) => {
110
+ const top = host.popup;
111
+ if (!top)
112
+ return null;
113
+ let hostName = "";
114
+ try {
115
+ hostName = new URL(top.state.url).host;
116
+ }
117
+ catch { }
118
+ return {
119
+ title: top.state.title,
120
+ host: hostName,
121
+ loading: top.state.loading,
122
+ width: top.state.width,
123
+ height: top.state.height,
124
+ };
125
+ };
126
+ (0, react_1.useLayoutEffect)(() => {
127
+ const created = {
128
+ page: registry.root.createSurface(),
129
+ popup: registry.root.createSurface(),
130
+ devtools: registry.root.createSurface(),
131
+ };
132
+ surfaces.current = created;
133
+ registry.register(entry);
134
+ const initial = propsRef.current;
135
+ const passthrough = initial.browserWindowOptions ?? {};
136
+ const browserWindowOptions = {
137
+ ...passthrough,
138
+ webPreferences: {
139
+ ...passthrough.webPreferences,
140
+ ...(initial.partition ? { partition: (0, browser_session_1.persistentPartition)(initial.partition) } : {}),
141
+ ...(initial.preload ? { preload: node_path_1.default.resolve(initial.preload) } : {}),
142
+ },
143
+ };
144
+ const info = registry.root.info;
145
+ const host = new host_1.PageHost(created.page, created.popup, { x: 0, y: 0, width: info.width, height: info.height, scale }, {
146
+ url: initial.src,
147
+ background: registry.background(),
148
+ clipboardRead: !!initial.clipboardRead,
149
+ proxy: initial.proxy ?? null,
150
+ browserWindowOptions,
151
+ }, (state) => {
152
+ (0, debug_1.debug)("state", state);
153
+ stateRef.current = state;
154
+ propsRef.current.onChange?.(state);
155
+ for (const listener of changeListeners.current)
156
+ listener(state);
157
+ });
158
+ hostRef.current = host;
159
+ entry.host = host;
160
+ (0, debug_1.debug)("host created", { info: { width: info.width, height: info.height }, scale, url: initial.src });
161
+ host.onCursorChange = () => syncCursor();
162
+ host.onPopupChange = () => {
163
+ setPopup(readPopup(host));
164
+ syncCursor();
165
+ };
166
+ host.onDevtoolsChange = () => {
167
+ const open = !!host.devtools;
168
+ if (!open)
169
+ entry.externalDevtools = false;
170
+ setDevtoolsOpen(open);
171
+ syncCursor();
172
+ };
173
+ host.onDevtoolsAction = (action) => {
174
+ if (entry.externalDevtools) {
175
+ entry.externalDevtoolsAction?.(action);
176
+ return;
177
+ }
178
+ if (action === "close")
179
+ host.closeDevtools();
180
+ else
181
+ setDock(action === "dock-bottom" ? "bottom" : "right");
182
+ };
183
+ host.onContextMenu = (params) => {
184
+ const custom = propsRef.current.onContextMenu;
185
+ if (custom)
186
+ custom(params);
187
+ else
188
+ openMenu(params);
189
+ };
190
+ host.onOpenWindow = (details) => (0, host_1.decideOpenWindow)(propsRef.current.onOpenWindow, details);
191
+ host.onDownload = (progress) => propsRef.current.onDownload?.(progress);
192
+ host.onQuit = () => registry.quit(entry);
193
+ host.onFrameSubmitted = () => {
194
+ for (const listener of frameListeners.current)
195
+ listener();
196
+ };
197
+ host.setVisible(!initial.hidden);
198
+ if (!initial.hidden && (initial.autoFocus ?? registry.views.size === 1))
199
+ registry.focus(entry);
200
+ return () => {
201
+ registry.unregister(entry);
202
+ host.stop();
203
+ hostRef.current = null;
204
+ entry.host = null;
205
+ created.popup.close();
206
+ created.devtools.close();
207
+ surfaces.current = null;
208
+ };
209
+ // eslint-disable-next-line react-hooks/exhaustive-deps
210
+ }, []);
211
+ // A zero rect means an ancestor is hidden; keep the page at its last size.
212
+ const collapsed = rect != null && (rect.width <= 0 || rect.height <= 0);
213
+ (0, react_1.useEffect)(() => {
214
+ hostRef.current?.setVisible(!hidden && !collapsed);
215
+ if ((hidden || collapsed) && registry.focused === entry)
216
+ registry.blur(entry);
217
+ }, [hidden, collapsed]);
218
+ (0, react_1.useEffect)(() => {
219
+ const host = hostRef.current;
220
+ if (!host || !page || hidden || collapsed)
221
+ return;
222
+ const layout = { ...page, scale };
223
+ (0, debug_1.debug)("page resize", layout);
224
+ host.resize(layout, { keepFrame: keepFrame() });
225
+ }, [page?.x, page?.y, page?.width, page?.height, scale, hidden]);
226
+ (0, react_1.useEffect)(() => {
227
+ const host = hostRef.current;
228
+ if (!host || !dockOpen || !devtoolsRect || hidden)
229
+ return;
230
+ const layout = { ...devtoolsRect, scale };
231
+ if (!host.devtools) {
232
+ host.openDevtools(surfaces.current.devtools, layout, dock);
233
+ host.focusDevtools();
234
+ const inspect = pendingInspect.current;
235
+ if (inspect) {
236
+ pendingInspect.current = null;
237
+ host.inspect(inspect.x, inspect.y);
238
+ }
239
+ return;
240
+ }
241
+ host.devtools.resize(layout, { keepFrame: keepFrame() });
242
+ }, [dockOpen, devtoolsRect?.x, devtoolsRect?.y, devtoolsRect?.width, devtoolsRect?.height, scale]);
243
+ (0, react_1.useEffect)(() => {
244
+ hostRef.current?.devtools?.setDock(dock);
245
+ }, [dock]);
246
+ const firstSrc = (0, react_1.useRef)(true);
247
+ (0, react_1.useEffect)(() => {
248
+ if (firstSrc.current) {
249
+ firstSrc.current = false;
250
+ return;
251
+ }
252
+ hostRef.current?.navigate(props.src);
253
+ }, [props.src]);
254
+ const openMenu = async (params) => {
255
+ const host = hostRef.current;
256
+ if (!host || host.popup)
257
+ return;
258
+ const canPaste = params.isEditable && (await electron_1.clipboard.readText()).length > 0;
259
+ const state = stateRef.current;
260
+ const selectionText = params.selectionText.trim();
261
+ const mac = process.platform === "darwin";
262
+ const items = [
263
+ { id: "back", label: "back", enabled: state.canGoBack, shortcut: "" },
264
+ { id: "forward", label: "forward", enabled: state.canGoForward, shortcut: "" },
265
+ { id: "reload", label: "reload", enabled: true, shortcut: "" },
266
+ ];
267
+ if (selectionText) {
268
+ items.push({ id: "copy", label: "copy", enabled: true, shortcut: mac ? "cmd+c" : "ctrl+c" });
269
+ }
270
+ if (params.isEditable) {
271
+ items.push({
272
+ id: "paste",
273
+ label: "paste",
274
+ enabled: canPaste,
275
+ shortcut: mac ? "cmd+v" : "ctrl+v",
276
+ });
277
+ }
278
+ if (params.linkURL) {
279
+ items.push({ id: "open-link", label: "open link", enabled: true, shortcut: "" });
280
+ items.push({ id: "copy-link", label: "copy link address", enabled: true, shortcut: "" });
281
+ }
282
+ if (devtoolsEnabled) {
283
+ items.push({ id: "inspect", label: "inspect", enabled: true, shortcut: "f12" });
284
+ items.push({ id: "engine-devtools", label: "pixel devtools", enabled: true, shortcut: "" });
285
+ }
286
+ const pageRect = page ?? { x: 0, y: 0 };
287
+ setMenu({
288
+ x: pageRect.x + params.x * scale,
289
+ y: pageRect.y + params.y * scale,
290
+ pageX: params.x,
291
+ pageY: params.y,
292
+ items,
293
+ linkURL: params.linkURL,
294
+ selectionText,
295
+ });
296
+ };
297
+ const runMenu = (id) => {
298
+ const current = menuRef.current;
299
+ setMenu(null);
300
+ const host = hostRef.current;
301
+ if (!current || !host)
302
+ return;
303
+ switch (id) {
304
+ case "back":
305
+ return host.back();
306
+ case "forward":
307
+ return host.forward();
308
+ case "reload":
309
+ return host.reload();
310
+ case "copy":
311
+ return registry.root.setClipboard(current.selectionText);
312
+ case "paste":
313
+ return electron_1.clipboard.readText().then((text) => host.paste(text));
314
+ case "open-link":
315
+ return host.navigate(current.linkURL);
316
+ case "copy-link":
317
+ return registry.root.setClipboard(current.linkURL);
318
+ case "engine-devtools":
319
+ return registry.root.openDevtools();
320
+ case "inspect":
321
+ pendingInspect.current = { x: current.pageX, y: current.pageY };
322
+ if (host.devtools) {
323
+ pendingInspect.current = null;
324
+ host.focusDevtools();
325
+ host.inspect(current.pageX, current.pageY);
326
+ }
327
+ else {
328
+ setDevtoolsOpen(true);
329
+ }
330
+ return;
331
+ }
332
+ };
333
+ const onDividerDrag = (event) => {
334
+ if (!rect || !page || !devtoolsRect)
335
+ return;
336
+ if (event.phase === "start") {
337
+ dragging.current = true;
338
+ setDividerEngaged(true);
339
+ return;
340
+ }
341
+ if (event.phase === "end") {
342
+ dragging.current = false;
343
+ setDividerEngaged(false);
344
+ return;
345
+ }
346
+ const now = Date.now();
347
+ if (now - lastDragResize.current < DRAG_RESIZE_MS)
348
+ return;
349
+ lastDragResize.current = now;
350
+ const absPage = { x: rect.x + page.x, y: rect.y + page.y, width: page.width, height: page.height };
351
+ const absDevtools = {
352
+ x: rect.x + devtoolsRect.x,
353
+ y: rect.y + devtoolsRect.y,
354
+ width: devtoolsRect.width,
355
+ height: devtoolsRect.height,
356
+ dock,
357
+ };
358
+ setFraction((0, devtools_layout_1.dividerFraction)(absPage, absDevtools, event.x, event.y));
359
+ };
360
+ (0, react_1.useImperativeHandle)(ref, () => {
361
+ const host = () => {
362
+ const current = hostRef.current;
363
+ if (!current) {
364
+ throw new Error("[placeholder copy: this WebView has already been unmounted]");
365
+ }
366
+ return current;
367
+ };
368
+ const handle = {
369
+ get webContents() {
370
+ return host().webContents;
371
+ },
372
+ get state() {
373
+ return stateRef.current;
374
+ },
375
+ onChange: (listener) => {
376
+ changeListeners.current.add(listener);
377
+ return () => {
378
+ changeListeners.current.delete(listener);
379
+ };
380
+ },
381
+ loadURL: (url) => host().navigate(url),
382
+ focus: () => registry.focus(entry),
383
+ blur: () => registry.blur(entry),
384
+ back: () => host().back(),
385
+ forward: () => host().forward(),
386
+ reload: () => host().reload(),
387
+ zoom: (direction) => host().zoom(direction),
388
+ find: (text) => host().find(text),
389
+ findNext: (forward) => host().findNext(forward),
390
+ stopFind: () => host().stopFind(),
391
+ cdp: (method, params) => host().cdp(method, params),
392
+ openDevtools: () => setDevtoolsOpen(true),
393
+ closeDevtools: () => hostRef.current?.closeDevtools(),
394
+ closePopup: () => hostRef.current?.popup?.close(),
395
+ recording: {
396
+ start: (dir) => host().surface.startCapture(dir),
397
+ onFrame: (listener) => {
398
+ frameListeners.current.add(listener);
399
+ return () => {
400
+ frameListeners.current.delete(listener);
401
+ };
402
+ },
403
+ frameSize: () => hostRef.current?.frameSize() ?? null,
404
+ pinFrameRate: (pinned) => host().pinFrameRate(pinned),
405
+ invalidate: () => hostRef.current?.invalidate(),
406
+ },
407
+ };
408
+ registry_1.handleEntries.set(handle, entry);
409
+ entry.handle = handle;
410
+ return handle;
411
+ }, [registry, entry]);
412
+ const popupView = (() => {
413
+ if (!popup || !page)
414
+ return null;
415
+ const headerPx = Math.round(rem * 1.7);
416
+ const maxW = Math.round(page.width * 0.94);
417
+ const maxH = Math.round(page.height * 0.94) - headerPx;
418
+ return {
419
+ ...popup,
420
+ width: Math.max(60, Math.min(Math.round(popup.width * scale), maxW)),
421
+ height: Math.max(60, Math.min(Math.round(popup.height * scale), maxH)),
422
+ };
423
+ })();
424
+ if (hidden) {
425
+ return (0, jsx_runtime_1.jsx)(react_2.Box, { style: { position: "absolute", inset: { top: 0, left: 0 }, width: 0, height: 0 } });
426
+ }
427
+ return ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: boxRef, style: { ...props.style, overflow: "hidden" }, children: [page && ((0, jsx_runtime_1.jsx)(react_2.Box, { surface: surfaces.current?.page, style: {
428
+ position: "absolute",
429
+ inset: { top: page.y, left: page.x },
430
+ width: page.width,
431
+ height: page.height,
432
+ cornerRadius: props.style?.cornerRadius,
433
+ background: theme.bg,
434
+ }, onPointer: (event) => {
435
+ if (menuRef.current) {
436
+ if (event.kind === "down")
437
+ setMenu(null);
438
+ if (event.kind !== "up")
439
+ return;
440
+ }
441
+ registry.focus(entry);
442
+ hostRef.current?.pointer(event);
443
+ propsRef.current.onPointer?.(event);
444
+ }, onWheel: (event) => {
445
+ registry.focus(entry);
446
+ hostRef.current?.wheel(event);
447
+ }, onMouseEnter: () => {
448
+ hover.current.page = true;
449
+ syncCursor();
450
+ }, onMouseLeave: () => {
451
+ hover.current.page = false;
452
+ syncCursor();
453
+ } })), devtoolsRect && surfaces.current && ((0, jsx_runtime_1.jsx)(devtools_1.DevtoolsDockPane, { rect: devtoolsRect, dock: dock, rem: rem, theme: theme, surface: surfaces.current.devtools, dividerEngaged: dividerEngaged, onPointer: (event) => {
454
+ const host = hostRef.current;
455
+ if (!host?.devtools)
456
+ return;
457
+ registry.focus(entry, "devtools");
458
+ host.devtools.input.pointer(event);
459
+ }, onWheel: (event) => {
460
+ const host = hostRef.current;
461
+ if (!host?.devtools)
462
+ return;
463
+ registry.focus(entry, "devtools");
464
+ host.devtools.input.wheel(event);
465
+ }, onHover: (hovering) => {
466
+ hover.current.devtools = hovering;
467
+ syncCursor();
468
+ }, onDividerDrag: onDividerDrag, onDividerHover: (hovering) => {
469
+ hover.current.divider = hovering;
470
+ syncCursor();
471
+ } })), popupView && page && surfaces.current && ((0, jsx_runtime_1.jsx)(popup_1.PopupCard, { view: popupView, page: page, rem: rem, theme: theme, surface: surfaces.current.popup, onPointer: (event) => {
472
+ registry.focus(entry, "popup");
473
+ hostRef.current?.popup?.input.pointer(event);
474
+ }, onWheel: (event) => {
475
+ registry.focus(entry, "popup");
476
+ hostRef.current?.popup?.input.wheel(event);
477
+ }, onHover: (hovering) => {
478
+ hover.current.popup = hovering;
479
+ syncCursor();
480
+ }, onClose: () => hostRef.current?.popup?.close() })), menu && rect && ((0, jsx_runtime_1.jsx)(context_menu_1.ContextMenu, { x: menu.x, y: menu.y, bounds: { width: rect.width, height: rect.height }, items: menu.items, rem: rem, theme: theme, onAction: runMenu, onClose: () => setMenu(null) }))] }));
481
+ });
package/package.json CHANGED
@@ -1,7 +1,73 @@
1
1
  {
2
2
  "name": "@zenbu-labs/pixel",
3
- "version": "0.0.0",
4
- "description": "[placeholder copy: reserves the name; real releases are published from github.com/zenbu-labs/pixel]",
3
+ "version": "0.0.12",
4
+ "description": "[placeholder copy: Electron apps that render into your terminal pane]",
5
5
  "license": "MIT",
6
- "repository": { "type": "git", "url": "git+https://github.com/zenbu-labs/pixel.git" }
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zenbu-labs/pixel.git",
9
+ "directory": "packages/pixel"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "bin": {
14
+ "pixel": "dist/bin.js"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./preload": {
22
+ "types": "./dist/preload/index.d.ts",
23
+ "default": "./dist/preload/index.js"
24
+ },
25
+ "./electron": {
26
+ "types": "./dist/electron/index.d.ts",
27
+ "default": "./dist/electron/index.js"
28
+ },
29
+ "./terminal": {
30
+ "types": "./dist/terminal/index.d.ts",
31
+ "default": "./dist/terminal/index.js"
32
+ },
33
+ "./ssh": {
34
+ "types": "./dist/ssh/index.d.ts",
35
+ "default": "./dist/ssh/index.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "scripts/postinstall.mjs",
42
+ "scripts/apparmor.sh"
43
+ ],
44
+ "config": {
45
+ "electron": "44.2.0"
46
+ },
47
+ "scripts": {
48
+ "postinstall": "node scripts/postinstall.mjs",
49
+ "build": "tsc -p tsconfig.json",
50
+ "build:native": "node scripts/build-native.mjs",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "npm run build && node --test \"test/*.test.js\""
53
+ },
54
+ "dependencies": {
55
+ "react-reconciler": "^0.29.2"
56
+ },
57
+ "peerDependencies": {
58
+ "react": "^18.3.1"
59
+ },
60
+ "optionalDependencies": {
61
+ "@zenbu-labs/pixel-native-darwin-arm64": "0.0.12",
62
+ "@zenbu-labs/pixel-native-darwin-x64": "0.0.12",
63
+ "@zenbu-labs/pixel-native-linux-arm64": "0.0.12",
64
+ "@zenbu-labs/pixel-native-linux-x64": "0.0.12"
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.0.0",
68
+ "@types/react": "^18.3.0",
69
+ "@types/react-reconciler": "^0.28.8",
70
+ "react": "^18.3.1",
71
+ "typescript": "^5.5.0"
72
+ }
7
73
  }
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ BINARY="${1:-$(dirname "$0")/../electron/electron}"
5
+
6
+ [ "$(uname -s)" = Linux ] || exit 0
7
+ [ -z "${PIXEL_SKIP_APPARMOR:-}" ] || exit 0
8
+ [ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns 2>/dev/null || true)" = 1 ] || exit 0
9
+
10
+ if [ ! -x "$BINARY" ]; then
11
+ echo "no electron binary at $BINARY" >&2
12
+ exit 1
13
+ fi
14
+ BINARY="$(readlink -f "$BINARY")"
15
+
16
+ # When a setuid chrome-sandbox binary sits next to electron, chromium builds its
17
+ # sandbox with that root helper instead of user namespaces, so the profile this
18
+ # script writes would change nothing.
19
+ [ ! -u "$(dirname "$BINARY")/chrome-sandbox" ] || exit 0
20
+
21
+ # The kernel matches a profile against the path it actually executes, and one profile
22
+ # per binary lets a checkout and an installed copy each keep their own.
23
+ NAME="pixel-$(printf '%s' "$BINARY" | sha256sum | cut -c1-12)"
24
+ PROFILE="/etc/apparmor.d/$NAME"
25
+
26
+ # Ubuntu 24.04 ships AppArmor 4.x (abi/4.0 only). Newer releases may have abi/5.0.
27
+ if [ -f /etc/apparmor.d/abi/5.0 ]; then
28
+ ABI=5.0
29
+ elif [ -f /etc/apparmor.d/abi/4.0 ]; then
30
+ ABI=4.0
31
+ else
32
+ echo "no supported AppArmor abi under /etc/apparmor.d/abi/ - cannot install profile" >&2
33
+ exit 1
34
+ fi
35
+
36
+ WANTED="abi <abi/${ABI}>,
37
+
38
+ include <tunables/global>
39
+
40
+ @{exec_path} = $BINARY
41
+ profile $NAME @{exec_path} flags=(unconfined) {
42
+ userns,
43
+ @{exec_path} mr,
44
+
45
+ include if exists <local/$NAME>
46
+ }"
47
+
48
+ [ "$(cat "$PROFILE" 2>/dev/null || true)" != "$WANTED" ] || exit 0
49
+
50
+ echo "chromium needs unprivileged user namespaces, which this system grants only to"
51
+ echo "programs with an AppArmor profile. writing one at $PROFILE"
52
+
53
+ TMP="$(mktemp)"
54
+ trap 'rm -f "$TMP"' EXIT
55
+ printf '%s\n' "$WANTED" > "$TMP"
56
+
57
+ SUDO=sudo
58
+ [ "$(id -u)" != 0 ] || SUDO=""
59
+ if [ -n "$SUDO" ] && ! command -v sudo >/dev/null 2>&1; then
60
+ echo "no sudo here — copy $TMP to $PROFILE as root and run: apparmor_parser -r $PROFILE" >&2
61
+ trap - EXIT
62
+ exit 1
63
+ fi
64
+
65
+ $SUDO install -m 0644 -o root -g root "$TMP" "$PROFILE"
66
+ if ! $SUDO apparmor_parser -r "$PROFILE"; then
67
+ $SUDO rm -f "$PROFILE"
68
+ echo "the kernel refused the profile, so it is gone and the next run will retry" >&2
69
+ exit 1
70
+ fi
71
+ echo "AppArmor profile installed for $BINARY"