buninu-browser 0.7.0 → 0.9.0

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 (142) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +90 -1
  3. package/buninu-browser.js +1 -1
  4. package/frontends/wayland/README.md +35 -2
  5. package/frontends/wayland/client.js +8 -6
  6. package/frontends/wayland/clipboard.js +1 -1
  7. package/frontends/wayland/index.js +1 -1
  8. package/frontends/wayland/input.js +1 -1
  9. package/frontends/wayland/resource-io.js +6 -0
  10. package/frontends/wayland/transport-posix.js +120 -0
  11. package/frontends/wayland/transport-win32.js +9 -0
  12. package/frontends/wayland/transport.js +3 -120
  13. package/frontends/wayland/win32-resources.js +80 -0
  14. package/frontends/wayland/win32-wire.js +62 -0
  15. package/lib/audio/biquad.js +25 -0
  16. package/lib/audio/piano.js +6 -3
  17. package/lib/audio/playback-buffer.js +5 -5
  18. package/lib/audio/pulse-client.js +24 -5
  19. package/lib/audio/web-audio.js +570 -0
  20. package/lib/browser-dialog.js +116 -0
  21. package/lib/cdp-server.js +43 -11
  22. package/lib/file-picker.js +48 -0
  23. package/lib/headless-shell.js +34 -12
  24. package/lib/layout/text-layout.js +112 -19
  25. package/lib/media/mp4-frame-times.js +93 -0
  26. package/lib/paint/direct-canvas.ts +39 -0
  27. package/lib/paint/display-list.js +37 -12
  28. package/lib/paint/raster-readback.js +16 -7
  29. package/lib/renderer/animation-frames.js +10 -5
  30. package/lib/renderer/bindings.js +69 -16
  31. package/lib/renderer/canvas-2d.js +265 -15
  32. package/lib/renderer/canvas-round-rect.js +36 -0
  33. package/lib/renderer/css-animations.js +173 -0
  34. package/lib/renderer/dialog-client.js +30 -0
  35. package/lib/renderer/dialog-worker.js +35 -0
  36. package/lib/renderer/eval-environment.js +28 -0
  37. package/lib/renderer/file-input.js +49 -0
  38. package/lib/renderer/host.js +33 -4
  39. package/lib/renderer/html-drag.js +77 -0
  40. package/lib/renderer/image-loading.js +45 -2
  41. package/lib/renderer/main.js +4 -0
  42. package/lib/renderer/module-transform.js +5 -3
  43. package/lib/renderer/page-builtins.js +59 -0
  44. package/lib/renderer/page-realm.js +177 -40
  45. package/lib/renderer/page-renderer.js +397 -49
  46. package/lib/renderer/piano-input.js +3 -1
  47. package/lib/renderer/raster-surface.js +18 -0
  48. package/lib/renderer/script-rewrite.js +165 -9
  49. package/lib/renderer/video-presentation.js +5 -4
  50. package/lib/renderer/webgl/clip.js +24 -0
  51. package/lib/renderer/webgl/context.js +158 -0
  52. package/lib/renderer/webgl/cpu-fragment.js +211 -0
  53. package/lib/renderer/webgl/draw-cache.js +22 -0
  54. package/lib/renderer/webgl/pipeline.js +70 -0
  55. package/lib/renderer/webgl/shaders.js +58 -0
  56. package/lib/renderer/webgl/textures.js +43 -0
  57. package/lib/renderer/window-named-properties.js +46 -0
  58. package/lib/screenshot-cli.js +4 -7
  59. package/lib/style/animation-values.js +29 -0
  60. package/lib/style/computed-style.js +109 -13
  61. package/lib/style/transform.js +44 -0
  62. package/lib/window-size.js +10 -0
  63. package/package.json +1 -1
  64. package/patches/happy-dom/0007-scope-content-models-to-html-namespace.patch +29 -0
  65. package/patches/happy-dom/README.md +16 -0
  66. package/test/animation-frames.test.js +15 -0
  67. package/test/canvas-2d.test.js +252 -4
  68. package/test/canvas-frame-pacing.test.js +7 -0
  69. package/test/cdp-viewers.test.js +36 -0
  70. package/test/classic-games-patches/0001-fix-game-initialization.patch +177 -0
  71. package/test/classic-games-patches/README.md +156 -0
  72. package/test/classic-games-scenarios.js +133 -0
  73. package/test/clipboard.test.js +16 -0
  74. package/test/computed-style.test.js +18 -0
  75. package/test/css-animation-lifecycle.test.js +28 -0
  76. package/test/css-animations.test.js +83 -0
  77. package/test/direct-canvas.test.js +39 -0
  78. package/test/dynamic-inline-handlers.test.js +25 -0
  79. package/test/eval-declarations.test.js +22 -0
  80. package/test/file-picker.test.js +26 -0
  81. package/test/fixtures/canvas-damage.js +23 -0
  82. package/test/fixtures/canvas-frame-pacing.js +35 -0
  83. package/test/fixtures/direct-canvas.js +96 -0
  84. package/test/fixtures/javascript-dialogs.html +8 -0
  85. package/test/fixtures/media-audio-lifetime.js +65 -0
  86. package/test/fixtures/renderer-memory.js +7 -5
  87. package/test/fixtures/video-black-replay.js +8 -6
  88. package/test/fixtures/video-slow-end.js +39 -0
  89. package/test/fixtures/web-audio-renderer.js +45 -0
  90. package/test/flex.test.js +26 -0
  91. package/test/grid.test.js +13 -0
  92. package/test/headless-shell.test.js +9 -0
  93. package/test/headless-window-size.test.js +28 -0
  94. package/test/html-rcdata.test.js +35 -0
  95. package/test/image-decode.test.js +18 -0
  96. package/test/javascript-dialog.test.js +83 -0
  97. package/test/manual-breakout-paddle.js +42 -0
  98. package/test/manual-breakout-static.js +35 -0
  99. package/test/manual-classic-games.js +41 -0
  100. package/test/manual-clumsy-bird-three-pipes.js +81 -0
  101. package/test/manual-clumsy-bird.js +52 -0
  102. package/test/manual-hextris.js +30 -0
  103. package/test/manual-numbered-music.js +24 -0
  104. package/test/manual-t-rex-runner.js +49 -0
  105. package/test/manual-threejs-scene.js +64 -0
  106. package/test/manual-webgl-benchmark.js +27 -0
  107. package/test/manual-webgl-cpu.html +26 -0
  108. package/test/media-audio-lifetime.test.js +8 -0
  109. package/test/mp4-frame-times.test.js +33 -0
  110. package/test/page-scripts.test.js +96 -1
  111. package/test/piano-input.test.js +15 -2
  112. package/test/piano.test.js +11 -2
  113. package/test/playback-buffer.test.js +11 -0
  114. package/test/pointer-drag.test.js +33 -0
  115. package/test/positioning.test.js +82 -0
  116. package/test/pulse-client.test.js +51 -2
  117. package/test/raster-readback.test.js +14 -0
  118. package/test/script-rewrite.test.js +61 -3
  119. package/test/video-slow-end.test.js +6 -0
  120. package/test/viewport-keyboard-overflow.test.js +25 -0
  121. package/test/wayland-win32-resources.test.js +47 -0
  122. package/test/wayland-win32-wire.test.js +59 -0
  123. package/test/web-audio-renderer.test.js +8 -0
  124. package/test/web-audio.test.js +264 -0
  125. package/test/webgl-clip.test.js +9 -0
  126. package/test/webgl-cpu-fragment.test.js +43 -0
  127. package/test/window-named-properties.test.js +41 -0
  128. package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfig.ts +2 -2
  129. package/vendor/happy-dom/packages/happy-dom/src/config/HTMLElementConfigContentModelEnum.ts +1 -0
  130. package/vendor/happy-dom/packages/happy-dom/src/html-parser/HTMLParser.ts +23 -3
  131. package/vendor/happy-dom/packages/happy-dom/src/query-selector/SelectorParser.ts +1 -1
  132. package/vendor/land-renderer/LICENSE +21 -0
  133. package/vendor/land-renderer/README.md +14 -0
  134. package/vendor/land-renderer/direct-compositor.ts +443 -0
  135. package/vendor/land-renderer/frame.js +88 -0
  136. package/vendor/land-renderer/png-fast.js +85 -0
  137. package/vendor/land-renderer/raw-frames.js +66 -0
  138. package/vendor/land-renderer/render-types.ts +7 -0
  139. package/vendor/land-renderer/renderer.ts +134 -0
  140. package/vendor/land-renderer/snapshot.js +37 -0
  141. package/vendor/land-renderer/viewport.js +18 -0
  142. package/vendor/ses/dist/ses.mjs +1 -1
package/AGENTS.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # AGENTS
2
2
 
3
3
  ## General
4
+ - Never EVER EVER EVER EVER use hardlinks.
5
+ - Hardlinks are not allowed.
6
+ - Using hardlinks is utterly harmful.
7
+ - .
4
8
  - This project only uses pure JavaScript +/- WASM.
5
9
  - Native bindings are generally not allowed in the headless shell itself
6
10
  * In the future when it is wrapped into a full browser on each platform, the frontend should stay as thin wrappers just enough to draw rendered content from the headlesss shell
@@ -14,6 +18,7 @@
14
18
  ## Implementing the browser
15
19
  - Do not treat any website specially when refining browser features like layout css js. Fix the actual problem that all websites can benefit from.
16
20
  - If you don't know how to fix a specific bug, look at ../chromium implementation but don't copy LGPL code.
21
+ - The graphics rendering of this browser should ensure CPU runs first. GPU acceleration must only be optional.
17
22
 
18
23
  ## Viewing files
19
24
  - No file is unviewable: every response the browser navigates to must show something, never a "cannot be shown" page
package/README.md CHANGED
@@ -50,13 +50,17 @@ npx buninu-browser github.com \
50
50
  # The engine alone, as a CDP
51
51
  # endpoint for any client
52
52
  npx buninu-browser --headless \
53
- --remote-debugging-port=9222
53
+ --remote-debugging-port=9222 --window-size=1280,800
54
54
 
55
55
  # capture a page and exit
56
56
  npx buninu-browser --headless --screenshot=page.png \
57
57
  --window-size=1280,800 https://example.com
58
58
  ```
59
59
 
60
+ `--window-size=WIDTH,HEIGHT` sets the initial viewport for both headless CDP
61
+ and screenshot modes (default 800×600 CSS pixels, DPR 1). CDP clients can
62
+ subsequently change it with `Emulation.setDeviceMetricsOverride`.
63
+
60
64
  Screenshot mode waits for the page's `load` event before capture. For content
61
65
  that appears later, add `--screenshot-wait=1000` (real milliseconds). The output
62
66
  format follows the filename extension: PNG, JPEG, or WebP. Chromium's
@@ -108,6 +112,27 @@ the rest is passed on:
108
112
  casty's and the engine's options:
109
113
  `--casty --help`, `--headless --help`.
110
114
 
115
+ ### JavaScript dialogs
116
+
117
+ `alert()`, `confirm()` and `prompt()` show a browser-owned modal dialog in the
118
+ page image, including through Casty and the Wayland frontend. For example,
119
+ enter `javascript:alert(1+2)` in the address bar. Alert has an OK button;
120
+ confirm and prompt also have Cancel. Enter accepts, Escape dismisses, and
121
+ prompt initially selects its default text. Long messages scroll and their
122
+ text can be selected and copied. Page scripts wait synchronously for the
123
+ answer; confirm returns a boolean and a cancelled prompt returns `null`.
124
+ The dialog lives in a separate renderer, outside the page's DOM and realm.
125
+
126
+ CDP exposes `Page.javascriptDialogOpening`, `Page.javascriptDialogClosed`
127
+ and `Page.handleJavaScriptDialog` for automation. Ordinary pointer and
128
+ keyboard input can also operate the displayed dialog.
129
+
130
+ File inputs also use a browser-owned chooser: click folders or enter a folder
131
+ path, select files, then Open (or Cancel/Escape). Multiple selection and
132
+ asynchronous `FileReader` are supported. Only selected file contents and names
133
+ reach the page, not their host paths. The current chooser accepts at most
134
+ 32 MiB per selection; it does not yet filter the listing by `accept`.
135
+
111
136
  ### Websites with self-signed certificates
112
137
 
113
138
  To browse a website with an untrusted self-signed HTTPS certificate, set
@@ -360,6 +385,36 @@ Documents load in stages:
360
385
  are reported over CDP when the page reaches them. Every visible change
361
386
  pushes a screencast frame.
362
387
 
388
+ ### Experimental CPU WebGL
389
+
390
+ `canvas.getContext('webgl')` has a software-only triangle rendering subset for
391
+ basic Three.js scenes. It uses the bundled CanvasKit shader compiler, with a
392
+ bounded numeric JavaScript shader path for supported expressions. Unsupported
393
+ numeric syntax keeps the CanvasKit path. It does not require a GPU or a new
394
+ native dependency. Depth testing, perspective varyings and basic 2D image/canvas
395
+ textures are supported; this is not a conformant or complete WebGL implementation
396
+ (framebuffers, cube maps, mipmaps and general blending remain incomplete).
397
+
398
+ The renderer defers unobserved draws, caches exact geometry/shading results while
399
+ reapplying current depth, and skips polygon clipping for meshes wholly inside
400
+ the view. CSS rotation, canvas-local pointer coordinates, HTML drag events and
401
+ detached image loading use the ordinary browser paths rather than Three.js
402
+ special cases.
403
+
404
+ The interactive reference is the [Three.js stereo scene](https://drjohnjstest.blogspot.com/2023/11/3dsbsthreejsdemo.html).
405
+ Run `bun test/manual-threejs-scene.js` for its buttons, prompt, orientation and
406
+ drag checks. Pass a folder containing `floor.jpg` and `wall.jpg` to also exercise
407
+ the file chooser and room textures; optional `小凱L.png` / `小凱R.png` exercise
408
+ stereo-image import. A second argument sets the output directory. This network
409
+ check is separate from `bun test`; Copy was also checked through the existing
410
+ browser clipboard callback without replacing the user's clipboard.
411
+
412
+ For completed-frame timings, run
413
+ `bun test/manual-webgl-benchmark.js buninu /path/to/assets`; replace `buninu`
414
+ with `chromium` for a reference (select a software Vulkan ICD when comparing CPU
415
+ rendering). It records both canvas sizes, frame times and a screenshot. These
416
+ timings exclude CDP screenshot encoding and terminal presentation.
417
+
363
418
  ### Mobile presentation
364
419
 
365
420
  Pages see a desktop browser unless `--mobile` is given. With it, requests
@@ -439,6 +494,40 @@ additional unreported downstream latency, such as Bluetooth output latency.
439
494
  With `BUNINU_LOG`, `media sync` records reported audio progress, the latest
440
495
  rendered video position, and the output queue/latency for diagnosis.
441
496
 
497
+ ### Web Audio synthesis and sample playback
498
+
499
+ The initial [Web Audio](https://www.w3.org/TR/webaudio-1.1/) subset provides
500
+ `AudioContext` (`webkitAudioContext` is an alias), `OscillatorNode`, `GainNode`
501
+ and `AudioParam` scheduling. Sine, square, sawtooth and triangle oscillators
502
+ are synthesized in JavaScript in 128-sample blocks. Connected voices share
503
+ one stereo PCM stream per context through the existing audio output above;
504
+ no GPU or additional native library is needed. Suspend, close and navigation
505
+ release the context's output. `AudioBuffer`, `createBuffer()`,
506
+ `decodeAudioData()` and `AudioBufferSourceNode` support decoded sample playback,
507
+ offsets, duration, looping, rate/detune and ended events. Compressed audio uses
508
+ the existing WASM decoder and is resampled to the context rate; the current
509
+ software graph downmixes buffer channels before stereo output. Offline contexts,
510
+ filters and AudioWorklets are not implemented yet.
511
+
512
+ [Clumsy Bird](https://ellisonleao.github.io/clumsy-bird/) is an interactive
513
+ reference for sample playback, Canvas image patterns, detached image dimensions
514
+ and page-local primitive prototype extensions. Start with a click or Space;
515
+ Space also makes the bird fly. The page remains under SES and the renderer's
516
+ seccomp policy; shared host prototypes are not unfrozen. Prototype extension
517
+ methods belong to the page and are discarded on navigation. The live check
518
+ `bun test/manual-clumsy-bird.js buninu /tmp/clumsy-bird` records menu and gameplay
519
+ screenshots and verifies pointer/Space input with the ordinary headless backend.
520
+
521
+ The interactive reference is the [text-to-numbered musical notation page](https://drjohnjstest.blogspot.com/2023/10/text-to-numbered-musical-notation.html).
522
+ Its Play button and two-part playback use changing oscillator frequencies.
523
+ To run the live reference test, including a screenshot:
524
+
525
+ ```sh
526
+ bun test/manual-numbered-music.js buninu /tmp/numbered-music
527
+ # Optional comparison; requires /usr/local/bin/chromium-headless-shell.
528
+ bun test/manual-numbered-music.js chromium /tmp/numbered-music-chromium
529
+ ```
530
+
442
531
  ### Render scheduling
443
532
 
444
533
  Set `BUNINU_RENDER_MIN_INTERVAL` to adjust the minimum delay before coalesced
package/buninu-browser.js CHANGED
@@ -50,7 +50,7 @@ Options:
50
50
  --chromium Use the previous desktop Chromium compatibility identity
51
51
  Last desktop identity flag wins; --mobile takes priority
52
52
  --screenshot[=FILE] Capture one PNG and exit (default screenshot.png)
53
- --window-size=WIDTH,HEIGHT Viewport size for the screenshot (default 800,600)
53
+ --window-size=WIDTH,HEIGHT Initial viewport size in CSS pixels (default 800,600)
54
54
  --screenshot-wait=MILLISECONDS Wait after page load before capturing
55
55
  --version, -V Print the version
56
56
  --help, -h Print this help
@@ -8,8 +8,8 @@ bun index.js --wayland https://example.com --window-size=800,600
8
8
  ```
9
9
 
10
10
  Uses the existing browser controller and renderer. No Qt, libwayland, native addon,
11
- new dependency, PNG encoding, or base64. Only libc is loaded, because Bun sockets
12
- cannot send/receive SCM_RIGHTS ancillary file descriptors. Linux/Android 64-bit only.
11
+ new dependency, PNG encoding, or base64. Linux/Android 64-bit uses libc for
12
+ SCM_RIGHTS ancillary file descriptors. Windows uses the named mapping transport below.
13
13
  The default launcher remains Casty. External integration blocks in index.js are
14
14
  marked `wayland handle`; the frontend lives entirely in this directory.
15
15
 
@@ -150,3 +150,36 @@ The page includes vertical/horizontal/two-axis scroll boxes, button and double-c
150
150
  counters, a drag canvas, text controls and bounded event logs. It reports received
151
151
  modifiers without swapping wheel axes itself. 頁面不自行轉換滾輪方向,可從紀錄
152
152
  確認外層是否真的送來水平軸與修飾鍵;預設 Casty 路徑可省略 `--wayland`。
153
+
154
+ ## Windows named mappings / Windows 具名共享記憶體 (unstable)
155
+
156
+ **unstable.** Experimental Windows-to-Windows transport; native Windows verification is still
157
+ required. Start Land with `bun index.js --win32` in the Land directory, then run
158
+ `bun index.js --wayland URL` here with `WAYLAND_DISPLAY=wayland-buninu` (or the
159
+ socket name printed by Land). Both processes must use the same Windows session
160
+ and runtime directory: `XDG_RUNTIME_DIR`, then `TMPDIR`, then system temp.
161
+ For example, in PowerShell before starting each process:
162
+
163
+ ```powershell
164
+ $env:XDG_RUNTIME_DIR = $env:TEMP
165
+ $env:WAYLAND_DISPLAY = "wayland-buninu"
166
+ ```
167
+
168
+ Only `process.platform === 'win32'` imports the Windows backend and loads
169
+ `kernel32.dll`. `CreateFileMappingW` / `OpenFileMappingW` / `MapViewOfFile` provide
170
+ pagefile-backed `Local\buninu-wayland-UUID` resources. No per-frame pixel file,
171
+ PNG, base64 or pixel socket upload is used. Wayland bytes and FD argument order
172
+ stay unchanged inside a versioned socket envelope carrying mapping descriptors.
173
+ An ACK retains the sender's mapping handle until the receiver has opened it.
174
+ Existing commit/release synchronization protects reusable pixel buffers. Keymaps
175
+ use byte mappings; bounded clipboard pipes use mappings with an atomic length
176
+ and EOF header. Disconnection releases pending handles and queued resources.
177
+ This envelope is private to these two Windows implementations; it is not the
178
+ standard SCM_RIGHTS wire transport. Linux/Android continue to use that original
179
+ transport, and Windows does not advertise DMA-BUF support.
180
+
181
+ Windows 驗證:在本專案執行 `bun test test/wayland-win32-resources.test.js`,
182
+ 測試跨程序共享像素、剪貼板 pipe 的 EOF,以及原本 socket 路徑上的具名資源傳遞。
183
+ 其他平台會略過這些原生測試;封裝、分段收送、ACK 和清理測試可在各平台執行:
184
+ `bun test test/wayland-win32-wire.test.js`。PowerShell 剪貼板沿用原有 jsmdcui
185
+ 函式,仍優先嘗試 xclip,失敗才 fallback。
@@ -1,13 +1,14 @@
1
1
  import {join,isAbsolute} from 'node:path';
2
2
  import {tmpdir} from 'node:os';
3
- import {openSync,closeSync,ftruncateSync,unlinkSync,writeSync} from 'node:fs';
4
- import {connectUnix} from './transport.js';
3
+ import {openSync,ftruncateSync,unlinkSync} from 'node:fs';
4
+ import {closeSync,writeSync} from './resource-io.js';
5
+ import {connectUnix,libc} from './transport.js';
5
6
  import {interfaces,decode,encodeRequest} from './protocol.js';
6
7
 
7
8
  export class WaylandClient {
8
9
  constructor(path) {
9
10
  const display=process.env.WAYLAND_DISPLAY || 'wayland-0';
10
- this.connection=connectUnix(path || (isAbsolute(display)?display:join(process.env.XDG_RUNTIME_DIR||tmpdir(),display)));
11
+ this.connection=connectUnix(path || (isAbsolute(display)?display:join(process.env.XDG_RUNTIME_DIR||process.env.TMPDIR||tmpdir(),display)));
11
12
  this.objects=new Map();this.nextId=2;this.globals=new Map();this.waiters=new Set();
12
13
  this.objects.set(1,{iface:'wl_display',event:(name,args)=>{
13
14
  if(name==='error')throw Error(`Wayland error ${args.join(': ')}`);
@@ -70,9 +71,10 @@ export class WaylandWindow {
70
71
  for(const b of [...this.buffers])if(!b.busy&&(b.width!==width||b.height!==height))this.disposeBuffer(b);
71
72
  let b=this.buffers.find(b=>!b.busy);
72
73
  if(!b){
73
- const path=join(process.env.XDG_RUNTIME_DIR||tmpdir(),`buninu-shm-${process.pid}-${crypto.randomUUID()}`);
74
- const fd=openSync(path,'wx+',0o600);unlinkSync(path);
75
- try{ftruncateSync(fd,size);
74
+ const path=join(process.env.XDG_RUNTIME_DIR||process.env.TMPDIR||tmpdir(),`buninu-shm-${process.pid}-${crypto.randomUUID()}`);
75
+ const fd=process.platform==='win32'?libc.createMapping(size):openSync(path,'wx+',0o600);
76
+ if(process.platform!=='win32')unlinkSync(path);
77
+ try{if(process.platform!=='win32')ftruncateSync(fd,size);
76
78
  b={fd,width,height,busy:false};const pool=c.object('wl_shm_pool');c.request(c.shm,'create_pool',pool,fd,size);
77
79
  b.id=c.object('wl_buffer',(event)=>{if(event==='release'){b.busy=false;this.onReady?.();}});
78
80
  c.request(pool,'create_buffer',b.id,0,width,height,width*4,1);c.request(pool,'destroy');this.buffers.push(b);
@@ -1,5 +1,5 @@
1
1
  // A standard wl_data_device text client. Only explicit copy/paste transfers bytes.
2
- import {readSync, writeSync} from 'node:fs';
2
+ import {readSync, writeSync} from './resource-io.js';
3
3
  import {libc} from './transport.js';
4
4
  const mimeTypes=['text/plain;charset=utf-8','text/plain','UTF8_STRING','STRING'];
5
5
  const limit=1024*1024;
@@ -121,7 +121,7 @@ export async function launch({url='about:blank',width=800,height=600,socket,chro
121
121
  }
122
122
  if(import.meta.main){
123
123
  const args=Bun.argv.slice(2).filter(a=>a!=='--');
124
- if(args.some(a=>['-h','--help'].includes(a))){console.log('Usage: buninu-browser --wayland [URL or file] [--window-size=800,600]\nUses WAYLAND_DISPLAY and XDG_RUNTIME_DIR. Experimental wl_shm frontend.');process.exit(0);}
124
+ if(args.some(a=>['-h','--help'].includes(a))){console.log('Usage: buninu-browser --wayland [URL or file] [--window-size=800,600]\nUses WAYLAND_DISPLAY; socket directory: XDG_RUNTIME_DIR, TMPDIR, system temp. Experimental wl_shm frontend.');process.exit(0);}
125
125
  let width=800,height=600,url='about:blank';
126
126
  for(const arg of args){if(arg.startsWith('--window-size=')){[width,height]=arg.slice(14).split(',').map(Number);}else if(arg.startsWith('-'))throw Error(`Unknown Wayland option: ${arg}`);else url=arg;}
127
127
  if(!Number.isInteger(width)||!Number.isInteger(height)||width<1||height<1||width>8192||height>8192)throw Error('Invalid window size');
@@ -1,4 +1,4 @@
1
- import {readSync} from 'node:fs';
1
+ import {readSync} from './resource-io.js';
2
2
  import {libc} from './transport.js';
3
3
 
4
4
  // A deliberately bounded XKB subset, not a replacement for libxkbcommon.
@@ -0,0 +1,6 @@
1
+ // Native fs descriptors on POSIX; named-mapping resource IDs on Windows.
2
+ import * as fs from 'node:fs';
3
+ const mapping = process.platform === 'win32' ? await import('./win32-resources.js') : null;
4
+ export const readSync = mapping?.readResource ?? fs.readSync;
5
+ export const writeSync = mapping?.writeResource ?? fs.writeSync;
6
+ export const closeSync = mapping?.closeResource ?? fs.closeSync;
@@ -0,0 +1,120 @@
1
+ // Adapted from buninu-land/lib/transport.js (MIT; LICENSE-land).
2
+ // Linux 64-bit ABI. Only libc is loaded. recvmsg is required for SCM_RIGHTS;
3
+ // Bun's stream sockets do not expose ancillary file descriptors.
4
+ import { dlopen, ptr } from 'bun:ffi';
5
+
6
+ if (!['arm64', 'x64'].includes(process.arch) || !['linux', 'android'].includes(process.platform)) throw Error('Requires 64-bit Linux or Android');
7
+ const signature = (args, returns = 'i32') => ({ args, returns });
8
+ let library;
9
+ let errnoSymbol;
10
+ // Same architecture-specific libc names as buninu-linux's /lib/dlopen.js.
11
+ const muslLibc = `libc.musl-${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}.so.1`;
12
+ const candidates = process.platform === 'android'
13
+ ? [['libc.so', '__errno']]
14
+ : [
15
+ ['libc.so.6', '__errno_location'],
16
+ [muslLibc, '__errno_location'],
17
+ [`/lib/${muslLibc}`, '__errno_location'],
18
+ ['libc.so', '__errno_location'],
19
+ ['libc.so', '__errno'],
20
+ ];
21
+ const failures = [];
22
+ for (const [name, symbol] of candidates) {
23
+ try { library = dlopen(name, {
24
+ connect: signature(['i32','ptr','u32']), socket: signature(['i32','i32','i32']),
25
+ recvmsg: signature(['i32','ptr','i32'], 'i64'), sendmsg: signature(['i32','ptr','i32'], 'i64'),
26
+ dup: signature(['i32']), send: signature(['i32','ptr','u64','i32'], 'i64'), close: signature(['i32']),
27
+
28
+ // Standard Wayland clipboard FD transfer.
29
+ pipe2: signature(['ptr','i32']), fcntl: signature(['i32','i32','i32']),
30
+
31
+ [symbol]: signature([], 'ptr'),
32
+ }); errnoSymbol = symbol; break; }
33
+ catch (error) { failures.push(`${name} (${symbol}): ${error.message}`); }
34
+ }
35
+ if (!library) throw Error(`Cannot load libc on ${process.platform}:\n${failures.join('\n')}`);
36
+ export const libc = library.symbols;
37
+ import { toArrayBuffer } from 'bun:ffi';
38
+ export const errno = () => new Int32Array(toArrayBuffer(libc[errnoSymbol](), 0, 4))[0];
39
+ const retry = () => [4,11].includes(errno());
40
+ export class Connection {
41
+ constructor(fd, removed) {
42
+ this.fd = fd; this.removed = removed; this.fds = []; this.input = Buffer.alloc(0); this.output = []; this.queuedBytes = 0;
43
+ this.data = Buffer.alloc(65536); this.control = Buffer.alloc(4096); this.iov = Buffer.alloc(16); this.msg = Buffer.alloc(56);
44
+ this.iov.writeBigUInt64LE(BigInt(ptr(this.data))); this.iov.writeBigUInt64LE(BigInt(this.data.length), 8);
45
+ this.msg.writeBigUInt64LE(BigInt(ptr(this.iov)), 16); this.msg.writeBigUInt64LE(1n, 24);
46
+ this.msg.writeBigUInt64LE(BigInt(ptr(this.control)), 32);
47
+ }
48
+ send(bytes, fds = []) {
49
+ if (this.closed) return;
50
+ const owned = [];
51
+ try { for (const fd of fds) { const copy = libc.dup(fd); if (copy < 0) throw Error('Cannot duplicate outgoing FD'); owned.push(copy); } }
52
+ catch (e) { for (const fd of owned) libc.close(fd); throw e; }
53
+ this.output.push({ bytes, fds: owned }); this.queuedBytes += bytes.length;
54
+ if (this.queuedBytes > 4*1024*1024) this.close();
55
+ }
56
+ flush() {
57
+ for (let round = 0; round < 64 && this.output.length; round++) {
58
+ const item = this.output[0];
59
+ let n;
60
+ if (item.fds.length) {
61
+ const iov = Buffer.alloc(16), msg = Buffer.alloc(56);
62
+ const size = 16 + item.fds.length * 4, control = Buffer.alloc(Math.ceil(size / 8) * 8);
63
+ iov.writeBigUInt64LE(BigInt(ptr(item.bytes))); iov.writeBigUInt64LE(BigInt(item.bytes.length), 8);
64
+ control.writeBigUInt64LE(BigInt(size)); control.writeInt32LE(1,8); control.writeInt32LE(1,12);
65
+ item.fds.forEach((fd,i) => control.writeInt32LE(fd,16+i*4));
66
+ msg.writeBigUInt64LE(BigInt(ptr(iov)),16); msg.writeBigUInt64LE(1n,24);
67
+ msg.writeBigUInt64LE(BigInt(ptr(control)),32); msg.writeBigUInt64LE(BigInt(control.length),40);
68
+ n = Number(libc.sendmsg(this.fd,msg,0x4000));
69
+ } else n = Number(libc.send(this.fd,item.bytes,item.bytes.length,0x4000));
70
+ if (n < 0) { if (!retry()) this.close(); return; }
71
+ if (!n) return this.close();
72
+ // SCM_RIGHTS is delivered with the first successfully sent byte, even on partial writes.
73
+ for (const fd of item.fds) libc.close(fd); item.fds = [];
74
+ this.queuedBytes -= n;
75
+ if (n === item.bytes.length) this.output.shift(); else item.bytes = item.bytes.subarray(n);
76
+ }
77
+ }
78
+ poll() {
79
+ if (this.closed) return;
80
+ try {
81
+ for (let round = 0; round < 16; round++) {
82
+ this.msg.writeBigUInt64LE(BigInt(this.control.length), 40); this.msg.writeUInt32LE(0, 48);
83
+ const n = Number(libc.recvmsg(this.fd, this.msg, 0x40000000));
84
+ if (n < 0) { if (retry()) break; return this.close(); }
85
+ if (!n) return this.close();
86
+ const end = Number(this.msg.readBigUInt64LE(40));
87
+ for (let p = 0; p + 16 <= end;) {
88
+ const size = Number(this.control.readBigUInt64LE(p)); if (size < 16 || p + size > end) throw Error('Invalid ancillary data');
89
+ if (this.control.readInt32LE(p+8) === 1 && this.control.readInt32LE(p+12) === 1)
90
+ for (let q = p + 16; q + 4 <= p + size; q += 4) this.fds.push(this.control.readInt32LE(q));
91
+ p += Math.ceil(size / 8) * 8;
92
+ }
93
+ if (this.msg.readUInt32LE(48) & 8) throw Error('Truncated ancillary data');
94
+ this.input = Buffer.concat([this.input, this.data.subarray(0,n)]);
95
+ if (this.input.length > 1024*1024 || this.fds.length > 256) throw Error('Client queue limit');
96
+ this.onData();
97
+ if (this.closed) return;
98
+ }
99
+ this.flush();
100
+ } catch (e) { console.error(`Wayland client: ${e.message}`);
101
+ // Flush queued requests before disconnecting.
102
+ this.flush();
103
+ this.close(); }
104
+ }
105
+ close() {
106
+ if (this.closed) return; this.closed = true; libc.close(this.fd);
107
+ for (const item of this.output) for (const fd of item.fds) libc.close(fd);
108
+ this.output = []; this.queuedBytes = 0;
109
+ for (const fd of this.fds) libc.close(fd); this.fds = []; this.onClose?.(); this.removed();
110
+ }
111
+ }
112
+
113
+ export function connectUnix(path) {
114
+ if (Buffer.byteLength(path)>107 || path.includes('\0')) throw Error('Invalid Wayland socket path');
115
+ const fd=libc.socket(1,1|0x800|0x80000,0);
116
+ if(fd<0)throw Error(`socket: errno ${errno()}`);
117
+ const address=Buffer.alloc(110); address.writeUInt16LE(1);address.write(path,2);
118
+ if(libc.connect(fd,address,Buffer.byteLength(path)+3)<0){const code=errno();libc.close(fd);throw Error(`connect ${path}: errno ${code}`);}
119
+ return new Connection(fd,()=>{});
120
+ }
@@ -0,0 +1,9 @@
1
+ import * as resources from './win32-resources.js';
2
+ import {MappingConnection} from './win32-wire.js';
3
+ export const {libc,errno}=resources;
4
+ export const Connection=MappingConnection;
5
+ export function connectUnix(path){
6
+ const c=new MappingConnection(resources);
7
+ Bun.connect({unix:path,socket:{open:s=>c.attach(s),data:(_,b)=>c.receive(b),drain:()=>c.flush(),close:()=>c.close(),error:(_,e)=>{c.error=e;c.close()}}}).catch(e=>{c.error=e;c.close()});
8
+ return c;
9
+ }
@@ -1,120 +1,3 @@
1
- // Adapted from buninu-land/lib/transport.js (MIT; LICENSE-land).
2
- // Linux 64-bit ABI. Only libc is loaded. recvmsg is required for SCM_RIGHTS;
3
- // Bun's stream sockets do not expose ancillary file descriptors.
4
- import { dlopen, ptr } from 'bun:ffi';
5
-
6
- if (!['arm64', 'x64'].includes(process.arch) || !['linux', 'android'].includes(process.platform)) throw Error('Requires 64-bit Linux or Android');
7
- const signature = (args, returns = 'i32') => ({ args, returns });
8
- let library;
9
- let errnoSymbol;
10
- // Same architecture-specific libc names as buninu-linux's /lib/dlopen.js.
11
- const muslLibc = `libc.musl-${process.arch === 'arm64' ? 'aarch64' : 'x86_64'}.so.1`;
12
- const candidates = process.platform === 'android'
13
- ? [['libc.so', '__errno']]
14
- : [
15
- ['libc.so.6', '__errno_location'],
16
- [muslLibc, '__errno_location'],
17
- [`/lib/${muslLibc}`, '__errno_location'],
18
- ['libc.so', '__errno_location'],
19
- ['libc.so', '__errno'],
20
- ];
21
- const failures = [];
22
- for (const [name, symbol] of candidates) {
23
- try { library = dlopen(name, {
24
- connect: signature(['i32','ptr','u32']), socket: signature(['i32','i32','i32']),
25
- recvmsg: signature(['i32','ptr','i32'], 'i64'), sendmsg: signature(['i32','ptr','i32'], 'i64'),
26
- dup: signature(['i32']), send: signature(['i32','ptr','u64','i32'], 'i64'), close: signature(['i32']),
27
-
28
- // Standard Wayland clipboard FD transfer.
29
- pipe2: signature(['ptr','i32']), fcntl: signature(['i32','i32','i32']),
30
-
31
- [symbol]: signature([], 'ptr'),
32
- }); errnoSymbol = symbol; break; }
33
- catch (error) { failures.push(`${name} (${symbol}): ${error.message}`); }
34
- }
35
- if (!library) throw Error(`Cannot load libc on ${process.platform}:\n${failures.join('\n')}`);
36
- export const libc = library.symbols;
37
- import { toArrayBuffer } from 'bun:ffi';
38
- export const errno = () => new Int32Array(toArrayBuffer(libc[errnoSymbol](), 0, 4))[0];
39
- const retry = () => [4,11].includes(errno());
40
- export class Connection {
41
- constructor(fd, removed) {
42
- this.fd = fd; this.removed = removed; this.fds = []; this.input = Buffer.alloc(0); this.output = []; this.queuedBytes = 0;
43
- this.data = Buffer.alloc(65536); this.control = Buffer.alloc(4096); this.iov = Buffer.alloc(16); this.msg = Buffer.alloc(56);
44
- this.iov.writeBigUInt64LE(BigInt(ptr(this.data))); this.iov.writeBigUInt64LE(BigInt(this.data.length), 8);
45
- this.msg.writeBigUInt64LE(BigInt(ptr(this.iov)), 16); this.msg.writeBigUInt64LE(1n, 24);
46
- this.msg.writeBigUInt64LE(BigInt(ptr(this.control)), 32);
47
- }
48
- send(bytes, fds = []) {
49
- if (this.closed) return;
50
- const owned = [];
51
- try { for (const fd of fds) { const copy = libc.dup(fd); if (copy < 0) throw Error('Cannot duplicate outgoing FD'); owned.push(copy); } }
52
- catch (e) { for (const fd of owned) libc.close(fd); throw e; }
53
- this.output.push({ bytes, fds: owned }); this.queuedBytes += bytes.length;
54
- if (this.queuedBytes > 4*1024*1024) this.close();
55
- }
56
- flush() {
57
- for (let round = 0; round < 64 && this.output.length; round++) {
58
- const item = this.output[0];
59
- let n;
60
- if (item.fds.length) {
61
- const iov = Buffer.alloc(16), msg = Buffer.alloc(56);
62
- const size = 16 + item.fds.length * 4, control = Buffer.alloc(Math.ceil(size / 8) * 8);
63
- iov.writeBigUInt64LE(BigInt(ptr(item.bytes))); iov.writeBigUInt64LE(BigInt(item.bytes.length), 8);
64
- control.writeBigUInt64LE(BigInt(size)); control.writeInt32LE(1,8); control.writeInt32LE(1,12);
65
- item.fds.forEach((fd,i) => control.writeInt32LE(fd,16+i*4));
66
- msg.writeBigUInt64LE(BigInt(ptr(iov)),16); msg.writeBigUInt64LE(1n,24);
67
- msg.writeBigUInt64LE(BigInt(ptr(control)),32); msg.writeBigUInt64LE(BigInt(control.length),40);
68
- n = Number(libc.sendmsg(this.fd,msg,0x4000));
69
- } else n = Number(libc.send(this.fd,item.bytes,item.bytes.length,0x4000));
70
- if (n < 0) { if (!retry()) this.close(); return; }
71
- if (!n) return this.close();
72
- // SCM_RIGHTS is delivered with the first successfully sent byte, even on partial writes.
73
- for (const fd of item.fds) libc.close(fd); item.fds = [];
74
- this.queuedBytes -= n;
75
- if (n === item.bytes.length) this.output.shift(); else item.bytes = item.bytes.subarray(n);
76
- }
77
- }
78
- poll() {
79
- if (this.closed) return;
80
- try {
81
- for (let round = 0; round < 16; round++) {
82
- this.msg.writeBigUInt64LE(BigInt(this.control.length), 40); this.msg.writeUInt32LE(0, 48);
83
- const n = Number(libc.recvmsg(this.fd, this.msg, 0x40000000));
84
- if (n < 0) { if (retry()) break; return this.close(); }
85
- if (!n) return this.close();
86
- const end = Number(this.msg.readBigUInt64LE(40));
87
- for (let p = 0; p + 16 <= end;) {
88
- const size = Number(this.control.readBigUInt64LE(p)); if (size < 16 || p + size > end) throw Error('Invalid ancillary data');
89
- if (this.control.readInt32LE(p+8) === 1 && this.control.readInt32LE(p+12) === 1)
90
- for (let q = p + 16; q + 4 <= p + size; q += 4) this.fds.push(this.control.readInt32LE(q));
91
- p += Math.ceil(size / 8) * 8;
92
- }
93
- if (this.msg.readUInt32LE(48) & 8) throw Error('Truncated ancillary data');
94
- this.input = Buffer.concat([this.input, this.data.subarray(0,n)]);
95
- if (this.input.length > 1024*1024 || this.fds.length > 256) throw Error('Client queue limit');
96
- this.onData();
97
- if (this.closed) return;
98
- }
99
- this.flush();
100
- } catch (e) { console.error(`Wayland client: ${e.message}`);
101
- // Flush queued requests before disconnecting.
102
- this.flush();
103
- this.close(); }
104
- }
105
- close() {
106
- if (this.closed) return; this.closed = true; libc.close(this.fd);
107
- for (const item of this.output) for (const fd of item.fds) libc.close(fd);
108
- this.output = []; this.queuedBytes = 0;
109
- for (const fd of this.fds) libc.close(fd); this.fds = []; this.onClose?.(); this.removed();
110
- }
111
- }
112
-
113
- export function connectUnix(path) {
114
- if (Buffer.byteLength(path)>107 || path.includes('\0')) throw Error('Invalid Wayland socket path');
115
- const fd=libc.socket(1,1|0x800|0x80000,0);
116
- if(fd<0)throw Error(`socket: errno ${errno()}`);
117
- const address=Buffer.alloc(110); address.writeUInt16LE(1);address.write(path,2);
118
- if(libc.connect(fd,address,Buffer.byteLength(path)+3)<0){const code=errno();libc.close(fd);throw Error(`connect ${path}: errno ${code}`);}
119
- return new Connection(fd,()=>{});
120
- }
1
+ // Platform selection must precede loading any native library.
2
+ const backend = await import(process.platform === 'win32' ? './transport-win32.js' : './transport-posix.js');
3
+ export const {libc,errno,Connection,connectUnix} = backend;
@@ -0,0 +1,80 @@
1
+ // Windows-only named pagefile mappings. No DLL is loaded on other platforms.
2
+ import {dlopen,toArrayBuffer} from 'bun:ffi';
3
+ import {fstatSync,readSync,writeSync,closeSync} from 'node:fs';
4
+ if(process.platform!=='win32')throw Error('Named mappings require Windows');
5
+ const sig=(args,returns)=>({args,returns});
6
+ const {symbols:k}=dlopen('kernel32.dll',{
7
+ CreateFileMappingW:sig(['i64','ptr','u32','u32','u32','ptr'],'ptr'),
8
+ OpenFileMappingW:sig(['u32','i32','ptr'],'ptr'),
9
+ MapViewOfFile:sig(['ptr','u32','u32','u32','u64'],'ptr'),
10
+ UnmapViewOfFile:sig(['ptr'],'i32'),CloseHandle:sig(['ptr'],'i32'),GetLastError:sig([],'u32'),
11
+ });
12
+ const entries=new Map();let next=-1;
13
+ const MAX=256*1024*1024;
14
+ const wide=s=>Buffer.from(s+'\0','utf16le');
15
+ const fail=what=>Error(`${what}: Win32 error ${k.GetLastError()}`);
16
+ function mapped(name,size,kind,create){
17
+ if(!/^Local\\buninu-wayland-[\da-f-]{36}$/.test(name)||!Number.isInteger(size)||size<1||size>MAX)throw Error('Invalid named mapping');
18
+ const handle=create?k.CreateFileMappingW(-1n,null,4,0,size,wide(name)):k.OpenFileMappingW(0xf001f,0,wide(name));
19
+ if(!handle)throw fail('File mapping');
20
+ if(create&&k.GetLastError()===183){k.CloseHandle(handle);throw Error('Mapping name collision')}
21
+ const address=k.MapViewOfFile(handle,0xf001f,0,0,size);
22
+ if(!address){const e=fail('MapViewOfFile');k.CloseHandle(handle);throw e}
23
+ return {name,size,kind,handle,address,bytes:Buffer.from(toArrayBuffer(address,0,size)),refs:0,forwarded:false};
24
+ }
25
+ function add(entry,writer=false){entry.refs++;const fd=next--;entries.set(fd,{entry,writer});return fd}
26
+ export function createMapping(size,kind='pixels'){
27
+ const e=mapped('Local\\buninu-wayland-'+crypto.randomUUID(),size,kind,true);return add(e,kind==='pipe');
28
+ }
29
+ export function closeResource(fd){
30
+ if(fd>=0){closeSync(fd);return 0}
31
+ const item=entries.get(fd);if(!item)return 0;entries.delete(fd);
32
+ const e=item.entry;
33
+ if(item.writer&&!item.forwarded)Atomics.store(new Int32Array(e.bytes.buffer,e.bytes.byteOffset,2),1,1);
34
+ if(--e.refs===0){k.UnmapViewOfFile(e.address);k.CloseHandle(e.handle);e.bytes=null}
35
+ return 0;
36
+ }
37
+ export function sendResource(fd){
38
+ let item=entries.get(fd),temporary;
39
+ if(!item){const size=fstatSync(fd).size;if(size>MAX)throw Error('Resource exceeds limit');const data=Buffer.alloc(size);let at=0;while(at<size){const n=readSync(fd,data,at,size-at,at);if(!n)throw Error('Short resource file');at+=n}temporary=createMapping(Math.max(1,data.length),'bytes');item=entries.get(temporary);data.copy(item.entry.bytes)}
40
+ const retained=add(item.entry,false);
41
+ if(item.writer)item.forwarded=true;
42
+ const descriptor={name:item.entry.name,size:item.entry.size,kind:item.entry.kind,writer:item.writer};
43
+ if(temporary!==undefined)closeResource(temporary);
44
+ return {descriptor,release:()=>closeResource(retained)};
45
+ }
46
+ export function receiveResource(d){
47
+ if(!d||!['pixels','bytes','pipe'].includes(d.kind)||typeof d.writer!=='boolean'||(d.writer&&d.kind!=='pipe')||(d.kind==='pipe'&&d.size<9))throw Error('Invalid resource descriptor');
48
+ return add(mapped(d.name,d.size,d.kind,false),d.writer);
49
+ }
50
+ export function readResource(fd,buffer,offset=0,length=buffer.length-offset,position=null){
51
+ if(fd>=0)return readSync(fd,buffer,offset,length,position);
52
+ const item=entries.get(fd);if(!item)throw Error('Closed mapping');const e=item.entry;
53
+ if(e.kind==='pipe'){
54
+ const header=new Int32Array(e.bytes.buffer,e.bytes.byteOffset,2),available=Atomics.load(header,0),start=item.position??0;
55
+ if(available<start||available>e.size-8)throw Error('Invalid pipe length');
56
+ const n=Math.min(length,available-start);
57
+ if(!n&&!Atomics.load(header,1))throw Object.assign(Error('Pipe pending'),{code:'EAGAIN'});
58
+ e.bytes.copy(buffer,offset,8+start,8+start+n);item.position=start+n;return n;
59
+ }
60
+ const start=position??item.position??0,n=Math.max(0,Math.min(length,e.size-start));
61
+ e.bytes.copy(buffer,offset,start,start+n);if(position===null)item.position=start+n;return n;
62
+ }
63
+ export function writeResource(fd,buffer,offset=0,length=buffer.length-offset,position=null){
64
+ if(fd>=0)return writeSync(fd,buffer,offset,length,position);
65
+ const item=entries.get(fd);if(!item)throw Error('Closed mapping');const e=item.entry;
66
+ const start=e.kind==='pipe'?Atomics.load(new Int32Array(e.bytes.buffer,e.bytes.byteOffset,2),0):(position??item.position??0),header=e.kind==='pipe'?8:0;
67
+ if(start+length>e.size-header)throw Error('Mapping write exceeds capacity');
68
+ e.bytes.set(buffer.subarray(offset,offset+length),header+start);
69
+ if(header)Atomics.store(new Int32Array(e.bytes.buffer,e.bytes.byteOffset,2),0,start+length);else if(position===null)item.position=start+length;
70
+ return length;
71
+ }
72
+ let lastErrno=0;
73
+ export const libc={
74
+ close:closeResource,createMapping,fcntl:()=>0,
75
+ pipe2(out){const fd=createMapping(1024*1024+8,'pipe'),e=entries.get(fd).entry;out[0]=add(e);out[1]=fd;return 0},
76
+ pread(fd,b,n,p){try{return readResource(fd,b,0,n,Number(p))}catch{lastErrno=5;return -1}},
77
+ read(fd,b,n){try{return readResource(fd,b,0,n)}catch(e){lastErrno=e.code==='EAGAIN'?11:5;return -1}},
78
+ write(fd,b,n){try{return writeResource(fd,b,0,n)}catch{lastErrno=5;return -1}},
79
+ };
80
+ export const errno=()=>lastErrno;