quickwin 1.1.3 → 1.2.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.
@@ -1,35 +1,80 @@
1
1
  import { createServer } from '../lib/http-server.js'
2
- import * as std from 'std'
3
-
4
- function readAll(file: std.FILE): string {
5
- const chunks: Uint8Array[] = []
6
- const buf = new Uint8Array(4096)
7
- while (true) {
8
- const n = file.read(buf.buffer, 0, 4096)
9
- if (n <= 0) break
10
- chunks.push(buf.slice(0, n))
11
- }
12
- let total = 0
13
- for (const c of chunks) total += c.length
14
- const out = new Uint8Array(total)
15
- let offset = 0
16
- for (const c of chunks) { out.set(c, offset); offset += c.length }
17
- return new TextDecoder().decode(out)
2
+ import * as os from 'os'
3
+
4
+ // WORKER_DATA_URL 由 esbuild --define 在 build.ts 中注入(base64 data: URL)
5
+ declare const WORKER_DATA_URL: string
6
+
7
+ const EXEC_TIMEOUT_MS = 60_000
8
+
9
+ interface ExecResult {
10
+ out: Uint8Array
11
+ code: number
12
+ error: string | null
13
+ }
14
+
15
+ function runInWorker(cmd: string): Promise<ExecResult> {
16
+ return new Promise((resolve, reject) => {
17
+ const worker = new os.Worker(WORKER_DATA_URL)
18
+ const id = 1
19
+ let settled = false
20
+ let timer: ReturnType<typeof os.setTimeout>
21
+
22
+ const finish = (fn: () => void) => {
23
+ if (settled) return
24
+ settled = true
25
+ os.clearTimeout(timer)
26
+ worker.onmessage = null
27
+ fn()
28
+ }
29
+
30
+ timer = os.setTimeout(() => {
31
+ finish(() => reject(new Error('exec timeout')))
32
+ }, EXEC_TIMEOUT_MS)
33
+
34
+ worker.onmessage = (e) => {
35
+ const msg = e.data as { type: string; id: number; out?: Uint8Array; code?: number; error?: string | null }
36
+ if (msg.type !== 'result' || msg.id !== id) return
37
+ finish(() => resolve({ out: msg.out ?? new Uint8Array(0), code: msg.code ?? -1, error: msg.error ?? null }))
38
+ }
39
+
40
+ try {
41
+ worker.postMessage({ type: 'run', id, cmd })
42
+ } catch (ex) {
43
+ finish(() => reject(ex instanceof Error ? ex : new Error(String(ex))))
44
+ }
45
+ })
18
46
  }
19
47
 
20
48
  const server = createServer(async (req) => {
21
- if (req.method === 'POST' && new URL(req.url).pathname === '/exec') {
49
+ const path = new URL(req.url).pathname
50
+ if (req.method === 'GET' && (path === '/' || path === '/health')) {
51
+ return new Response('ok', { headers: { 'Content-Type': 'text/plain' } })
52
+ }
53
+ if (req.method === 'POST' && path === '/exec') {
22
54
  const body = await req.json() as { cmd: string }
23
- const file = std.popen(body.cmd, 'r')
24
- if (!file) return new Response('popen failed', { status: 500 })
25
- const output = readAll(file)
26
- file.close()
27
- return new Response(output, {
28
- headers: { 'Content-Type': 'application/octet-stream' }
29
- })
55
+ try {
56
+ const { out, code, error } = await runInWorker(body.cmd)
57
+ if (error) {
58
+ return new Response(JSON.stringify({ error }), {
59
+ status: 500,
60
+ headers: { 'Content-Type': 'application/json' }
61
+ })
62
+ }
63
+ return new Response(out, {
64
+ headers: {
65
+ 'Content-Type': 'application/octet-stream',
66
+ 'X-Exit-Code': String(code),
67
+ }
68
+ })
69
+ } catch (ex) {
70
+ return new Response(JSON.stringify({ error: String(ex) }), {
71
+ status: 504,
72
+ headers: { 'Content-Type': 'application/json' }
73
+ })
74
+ }
30
75
  }
31
76
  return new Response('Not Found', { status: 404 })
32
77
  })
33
78
 
34
- server.listen(8080)
35
- console.log('listening on http://localhost:8080')
79
+ const rc = server.listen(8080, '0.0.0.0')
80
+ console.log('listen rc=' + rc + ' addr=' + JSON.stringify(server.address()))
@@ -0,0 +1,45 @@
1
+ import * as os from 'os';
2
+ import * as std from 'std';
3
+ function readBytes(file) {
4
+ const chunks = [];
5
+ const buf = new Uint8Array(4096);
6
+ while (true) {
7
+ const n = file.read(buf.buffer, 0, 4096);
8
+ if (n <= 0)
9
+ break;
10
+ chunks.push(buf.slice(0, n));
11
+ }
12
+ let total = 0;
13
+ for (const c of chunks)
14
+ total += c.length;
15
+ const out = new Uint8Array(total);
16
+ let offset = 0;
17
+ for (const c of chunks) {
18
+ out.set(c, offset);
19
+ offset += c.length;
20
+ }
21
+ return out;
22
+ }
23
+ const parent = os.Worker.parent;
24
+ parent.onmessage = (e) => {
25
+ const msg = e.data;
26
+ if (msg.type !== 'run' || typeof msg.cmd !== 'string')
27
+ return;
28
+ let out = new Uint8Array(0);
29
+ let code = -1;
30
+ let error = null;
31
+ try {
32
+ const file = std.popen(msg.cmd, 'r');
33
+ if (!file) {
34
+ error = 'popen failed';
35
+ }
36
+ else {
37
+ out = readBytes(file);
38
+ code = file.close();
39
+ }
40
+ }
41
+ catch (ex) {
42
+ error = String(ex);
43
+ }
44
+ parent.postMessage({ type: 'result', id: msg.id, out, code, error });
45
+ };
@@ -0,0 +1,39 @@
1
+ import * as os from 'os'
2
+ import * as std from 'std'
3
+
4
+ function readBytes(file: std.FILE): Uint8Array {
5
+ const chunks: Uint8Array[] = []
6
+ const buf = new Uint8Array(4096)
7
+ while (true) {
8
+ const n = file.read(buf.buffer, 0, 4096)
9
+ if (n <= 0) break
10
+ chunks.push(buf.slice(0, n))
11
+ }
12
+ let total = 0
13
+ for (const c of chunks) total += c.length
14
+ const out = new Uint8Array(total)
15
+ let offset = 0
16
+ for (const c of chunks) { out.set(c, offset); offset += c.length }
17
+ return out
18
+ }
19
+
20
+ const parent = os.Worker.parent
21
+ parent.onmessage = (e) => {
22
+ const msg = e.data as { type: string; id: number; cmd?: string }
23
+ if (msg.type !== 'run' || typeof msg.cmd !== 'string') return
24
+ let out: Uint8Array = new Uint8Array(0)
25
+ let code = -1
26
+ let error: string | null = null
27
+ try {
28
+ const file = std.popen(msg.cmd, 'r')
29
+ if (!file) {
30
+ error = 'popen failed'
31
+ } else {
32
+ out = readBytes(file)
33
+ code = file.close()
34
+ }
35
+ } catch (ex) {
36
+ error = String(ex)
37
+ }
38
+ parent.postMessage({ type: 'result', id: msg.id, out, code, error })
39
+ }
@@ -0,0 +1,104 @@
1
+ import * as std from 'std';
2
+ import * as gui from 'gui';
3
+ import * as win from 'win';
4
+ import * as ffi from 'ffi';
5
+ const user32 = win.LoadLibrary('user32.dll');
6
+ if (!user32) {
7
+ print('LoadLibrary user32 failed');
8
+ std.exit(1);
9
+ }
10
+ function loadProc(lib, name) {
11
+ const ptr = win.GetProcAddress(lib, name);
12
+ if (!ptr) {
13
+ print('GetProcAddress ' + name + ' failed');
14
+ std.exit(1);
15
+ }
16
+ return ptr;
17
+ }
18
+ const EnumDisplaySettingsA = loadProc(user32, 'EnumDisplaySettingsA');
19
+ const ChangeDisplaySettingsA = loadProc(user32, 'ChangeDisplaySettingsA');
20
+ /** DEVMODEA used by EnumDisplaySettings; driver reports actual dmSize (often 124). */
21
+ const DEVMODE_SIZE = 220;
22
+ const DM_PELSWIDTH = 0x00800000;
23
+ const DM_PELSHEIGHT = 0x01000000;
24
+ const ENUM_CURRENT_SETTINGS = -1;
25
+ const CDS_UPDATEREGISTRY = 0x00000001;
26
+ const CDS_TEST = 0x00000002;
27
+ const DISP_CHANGE = {
28
+ 0: 'SUCCESSFUL',
29
+ 1: 'RESTART',
30
+ [-1]: 'FAILED',
31
+ [-2]: 'BADMODE',
32
+ [-3]: 'NOTUPDATED',
33
+ [-4]: 'BADFLAGS',
34
+ [-5]: 'BADPARAM',
35
+ [-6]: 'BADDUALVIEW',
36
+ };
37
+ function newDevMode() {
38
+ const buf = new ArrayBuffer(DEVMODE_SIZE);
39
+ new DataView(buf).setUint16(36, DEVMODE_SIZE, true);
40
+ return buf;
41
+ }
42
+ function enumMode(modeNum, buf) {
43
+ return ffi.ffiCall(EnumDisplaySettingsA, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER], [null, modeNum, buf], ffi.FFI_TYPE_SINT32);
44
+ }
45
+ function getPels(dv) {
46
+ return [dv.getUint32(108, true), dv.getUint32(112, true), dv.getUint32(120, true)];
47
+ }
48
+ function applyFromCurrent(w, h, flags, label) {
49
+ const dm = newDevMode();
50
+ if (!enumMode(ENUM_CURRENT_SETTINGS, dm)) {
51
+ print(label + ': EnumDisplaySettings CURRENT failed');
52
+ return null;
53
+ }
54
+ const dv = new DataView(dm);
55
+ const before = getPels(dv);
56
+ const size = dv.getUint16(36, true);
57
+ const extra = dv.getUint16(38, true);
58
+ const fields = dv.getUint32(40, true);
59
+ const bpp = dv.getUint32(104, true);
60
+ const freq = dv.getUint32(120, true);
61
+ print(label + ': base size=' + size + ' extra=' + extra + ' fields=0x' + fields.toString(16) +
62
+ ' ' + before[0] + 'x' + before[1] + '@' + freq + ' bpp=' + bpp);
63
+ dv.setUint32(40, fields | DM_PELSWIDTH | DM_PELSHEIGHT, true);
64
+ dv.setUint32(108, w, true);
65
+ dv.setUint32(112, h, true);
66
+ const ret = ffi.ffiCall(ChangeDisplaySettingsA, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_UINT32], [dm, flags], ffi.FFI_TYPE_SINT32);
67
+ print(label + ': ChangeDisplaySettings ' + w + 'x' + h +
68
+ ' flags=0x' + flags.toString(16) + ' ret=' + ret + ' (' + (DISP_CHANGE[ret] ?? '?') + ')');
69
+ return ret;
70
+ }
71
+ const targetW = Number(scriptArgs[1] ?? '1024');
72
+ const targetH = Number(scriptArgs[2] ?? '768');
73
+ if (!Number.isFinite(targetW) || !Number.isFinite(targetH) || targetW <= 0 || targetH <= 0) {
74
+ print('usage: qwin.exe setres.js [width] [height]');
75
+ std.exit(1);
76
+ }
77
+ print('before GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
78
+ const probe = newDevMode();
79
+ const modes32 = [];
80
+ for (let i = 0; i < 200; i++) {
81
+ if (!enumMode(i, probe))
82
+ break;
83
+ const dv = new DataView(probe);
84
+ if (dv.getUint32(104, true) !== 32)
85
+ continue;
86
+ const p = getPels(dv);
87
+ modes32.push(p[0] + 'x' + p[1] + '@' + p[2]);
88
+ }
89
+ print('32bpp modes sample: ' + modes32.slice(0, 25).join(', ') + (modes32.length > 25 ? ' ...' : ''));
90
+ const testRet = applyFromCurrent(targetW, targetH, CDS_TEST, 'TEST');
91
+ if (testRet === 0) {
92
+ applyFromCurrent(targetW, targetH, 0, 'DYNAMIC');
93
+ print('after dynamic GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
94
+ applyFromCurrent(targetW, targetH, CDS_UPDATEREGISTRY, 'REGISTRY');
95
+ print('after registry GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
96
+ }
97
+ else if (testRet !== null) {
98
+ print('target rejected; try 800x600');
99
+ const t2 = applyFromCurrent(800, 600, CDS_TEST, 'TEST800');
100
+ if (t2 === 0) {
101
+ applyFromCurrent(800, 600, 0, 'DYNAMIC800');
102
+ print('after 800x600 GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
103
+ }
104
+ }
@@ -0,0 +1,120 @@
1
+ import * as std from 'std'
2
+ import * as gui from 'gui'
3
+ import * as win from 'win'
4
+ import * as ffi from 'ffi'
5
+
6
+ const user32 = win.LoadLibrary('user32.dll')
7
+ if (!user32) { print('LoadLibrary user32 failed'); std.exit(1) }
8
+
9
+ function loadProc(lib: win.HMODULE, name: string): number {
10
+ const ptr = win.GetProcAddress(lib, name)
11
+ if (!ptr) { print('GetProcAddress ' + name + ' failed'); std.exit(1) }
12
+ return ptr
13
+ }
14
+
15
+ const EnumDisplaySettingsA = loadProc(user32, 'EnumDisplaySettingsA')
16
+ const ChangeDisplaySettingsA = loadProc(user32, 'ChangeDisplaySettingsA')
17
+
18
+ /** DEVMODEA used by EnumDisplaySettings; driver reports actual dmSize (often 124). */
19
+ const DEVMODE_SIZE = 220
20
+ const DM_PELSWIDTH = 0x00800000
21
+ const DM_PELSHEIGHT = 0x01000000
22
+ const ENUM_CURRENT_SETTINGS = -1
23
+ const CDS_UPDATEREGISTRY = 0x00000001
24
+ const CDS_TEST = 0x00000002
25
+
26
+ const DISP_CHANGE: Record<number, string> = {
27
+ 0: 'SUCCESSFUL',
28
+ 1: 'RESTART',
29
+ [-1]: 'FAILED',
30
+ [-2]: 'BADMODE',
31
+ [-3]: 'NOTUPDATED',
32
+ [-4]: 'BADFLAGS',
33
+ [-5]: 'BADPARAM',
34
+ [-6]: 'BADDUALVIEW',
35
+ }
36
+
37
+ function newDevMode(): ArrayBuffer {
38
+ const buf = new ArrayBuffer(DEVMODE_SIZE)
39
+ new DataView(buf).setUint16(36, DEVMODE_SIZE, true)
40
+ return buf
41
+ }
42
+
43
+ function enumMode(modeNum: number, buf: ArrayBuffer): number {
44
+ return ffi.ffiCall(
45
+ EnumDisplaySettingsA,
46
+ [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER],
47
+ [null, modeNum, buf],
48
+ ffi.FFI_TYPE_SINT32,
49
+ )
50
+ }
51
+
52
+ function getPels(dv: DataView): [number, number, number] {
53
+ return [dv.getUint32(108, true), dv.getUint32(112, true), dv.getUint32(120, true)]
54
+ }
55
+
56
+ function applyFromCurrent(w: number, h: number, flags: number, label: string): number | null {
57
+ const dm = newDevMode()
58
+ if (!enumMode(ENUM_CURRENT_SETTINGS, dm)) {
59
+ print(label + ': EnumDisplaySettings CURRENT failed')
60
+ return null
61
+ }
62
+ const dv = new DataView(dm)
63
+ const before = getPels(dv)
64
+ const size = dv.getUint16(36, true)
65
+ const extra = dv.getUint16(38, true)
66
+ const fields = dv.getUint32(40, true)
67
+ const bpp = dv.getUint32(104, true)
68
+ const freq = dv.getUint32(120, true)
69
+ print(label + ': base size=' + size + ' extra=' + extra + ' fields=0x' + fields.toString(16) +
70
+ ' ' + before[0] + 'x' + before[1] + '@' + freq + ' bpp=' + bpp)
71
+
72
+ dv.setUint32(40, fields | DM_PELSWIDTH | DM_PELSHEIGHT, true)
73
+ dv.setUint32(108, w, true)
74
+ dv.setUint32(112, h, true)
75
+
76
+ const ret = ffi.ffiCall(
77
+ ChangeDisplaySettingsA,
78
+ [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_UINT32],
79
+ [dm, flags],
80
+ ffi.FFI_TYPE_SINT32,
81
+ )
82
+ print(label + ': ChangeDisplaySettings ' + w + 'x' + h +
83
+ ' flags=0x' + flags.toString(16) + ' ret=' + ret + ' (' + (DISP_CHANGE[ret] ?? '?') + ')')
84
+ return ret
85
+ }
86
+
87
+ const targetW = Number(scriptArgs[1] ?? '1024')
88
+ const targetH = Number(scriptArgs[2] ?? '768')
89
+ if (!Number.isFinite(targetW) || !Number.isFinite(targetH) || targetW <= 0 || targetH <= 0) {
90
+ print('usage: qwin.exe setres.js [width] [height]')
91
+ std.exit(1)
92
+ }
93
+
94
+ print('before GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
95
+
96
+ const probe = newDevMode()
97
+ const modes32: string[] = []
98
+ for (let i = 0; i < 200; i++) {
99
+ if (!enumMode(i, probe)) break
100
+ const dv = new DataView(probe)
101
+ if (dv.getUint32(104, true) !== 32) continue
102
+ const p = getPels(dv)
103
+ modes32.push(p[0] + 'x' + p[1] + '@' + p[2])
104
+ }
105
+ print('32bpp modes sample: ' + modes32.slice(0, 25).join(', ') + (modes32.length > 25 ? ' ...' : ''))
106
+
107
+ const testRet = applyFromCurrent(targetW, targetH, CDS_TEST, 'TEST')
108
+ if (testRet === 0) {
109
+ applyFromCurrent(targetW, targetH, 0, 'DYNAMIC')
110
+ print('after dynamic GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
111
+ applyFromCurrent(targetW, targetH, CDS_UPDATEREGISTRY, 'REGISTRY')
112
+ print('after registry GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
113
+ } else if (testRet !== null) {
114
+ print('target rejected; try 800x600')
115
+ const t2 = applyFromCurrent(800, 600, CDS_TEST, 'TEST800')
116
+ if (t2 === 0) {
117
+ applyFromCurrent(800, 600, 0, 'DYNAMIC800')
118
+ print('after 800x600 GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
119
+ }
120
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "quickwin",
4
- "version": "1.1.3",
4
+ "version": "1.2.0",
5
5
  "description": "QuickJS Win32 runtime — GUI, networking, WASM, FFI, and more",
6
6
  "bin": {
7
7
  "quickwin": "qwin.exe",
package/quickwin.d.ts CHANGED
@@ -330,7 +330,7 @@ declare module "os" {
330
330
  const S_IFREG: number;
331
331
 
332
332
  class Worker {
333
- /** Constructor to create a new thread (worker) with an API close to the `WebWorkers`. `module_filename` is a string specifying the module filename which is executed in the newly created thread. */
333
+ /** Constructor to create a new thread (worker) with an API close to the `WebWorkers`. `module_filename` is a file path, `http(s)://` URL, or `data:text/javascript;base64,...` URL executed in the newly created thread. */
334
334
  constructor(module_filename: string);
335
335
  /** Send a message to the corresponding worker. `msg` is cloned in the destination worker using an algorithm similar to the `HTML` structured clone algorithm. */
336
336
  postMessage(msg: unknown): void;
@@ -340,8 +340,8 @@ declare module "os" {
340
340
  static parent: Worker;
341
341
  }
342
342
 
343
- /** Return a string representing the platform: `"linux"`, `"darwin"`, `"win32"` or `"js"`. */
344
- const platform: string;
343
+ /** Process architecture (pointer width): `"x64"` or `"ia32"`. */
344
+ const arch: string;
345
345
  }
346
346
 
347
347
  declare module "sock" {
Binary file
package/qwin-nowasm.exe CHANGED
Binary file
package/qwin-x86.exe CHANGED
Binary file
package/qwin.exe CHANGED
Binary file
package/test/run.js CHANGED
@@ -34,7 +34,9 @@ const suiteDefs = [
34
34
  { name: 'worker', file: './test_worker.js', tags: [] },
35
35
  { name: 'worker-wasm', file: './test_worker_wasm_concurrent.js', tags: ['wasm'] },
36
36
  { name: 'worker-http', file: './test_worker_net.js', tags: ['net'] },
37
- { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] }
37
+ { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] },
38
+ { name: 'worker-concurrent', file: './test_worker_concurrent.js', tags: [] },
39
+ { name: 'worker-data-url', file: './test_worker_data_url.js', tags: [] }
38
40
  ];
39
41
  async function main() {
40
42
  const filter = scriptArgs.length > 1 ? scriptArgs[1] : '';
package/test/run.ts CHANGED
@@ -38,7 +38,9 @@ const suiteDefs = [
38
38
  { name: 'worker', file: './test_worker.js', tags: [] },
39
39
  { name: 'worker-wasm', file: './test_worker_wasm_concurrent.js', tags: ['wasm'] },
40
40
  { name: 'worker-http', file: './test_worker_net.js', tags: ['net'] },
41
- { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] }
41
+ { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] },
42
+ { name: 'worker-concurrent', file: './test_worker_concurrent.js', tags: [] },
43
+ { name: 'worker-data-url', file: './test_worker_data_url.js', tags: [] }
42
44
  ]
43
45
 
44
46
  async function main(): Promise<void> {
package/test/test_ffi.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import * as std from 'std';
2
2
  import * as win from 'win';
3
3
  import * as ffi from 'ffi';
4
+ import * as os from 'os';
5
+ const MAX_WCHARS = 4096;
4
6
  function decodeWideAtPtr(ptr) {
5
7
  if (!ptr)
6
8
  return '';
7
9
  const chars = [];
8
10
  let pos = ptr;
9
- while (true) {
11
+ while (chars.length < MAX_WCHARS) {
10
12
  const low = ffi.readByte(pos);
11
13
  const high = ffi.readByte(pos + 1);
12
14
  const ch = low + high * 256;
@@ -18,6 +20,9 @@ function decodeWideAtPtr(ptr) {
18
20
  return String.fromCharCode(...chars);
19
21
  }
20
22
  function readPtr(dv, offset) {
23
+ if (os.arch === 'ia32') {
24
+ return dv.getUint32(offset, true);
25
+ }
21
26
  const low = dv.getUint32(offset, true);
22
27
  const high = dv.getUint32(offset + 4, true);
23
28
  return low + high * 4294967296;
@@ -122,15 +127,25 @@ export const suite = {
122
127
  std.printf(' printers found: %d\n', returnedBuf[0]);
123
128
  if (returnedBuf[0] > 0) {
124
129
  const dv = new DataView(printerBuf);
125
- const structSize = 136;
130
+ // PRINTER_INFO_2W layout (MinGW verified):
131
+ // x64: sizeof=136 name=8 port=24 drv=32 comment=40 location=48 status=124
132
+ // ia32: sizeof=84 name=4 port=12 drv=16 comment=20 location=24 status=72
133
+ const ia32 = os.arch === 'ia32';
134
+ const structSize = ia32 ? 84 : 136;
135
+ const offName = ia32 ? 4 : 8;
136
+ const offPort = ia32 ? 12 : 24;
137
+ const offDrv = ia32 ? 16 : 32;
138
+ const offComment = ia32 ? 20 : 40;
139
+ const offLocation = ia32 ? 24 : 48;
140
+ const offStatus = ia32 ? 72 : 124;
126
141
  for (let i = 0; i < returnedBuf[0] && i < 20; i++) {
127
142
  const off = i * structSize;
128
- const name = decodeWideAtPtr(readPtr(dv, off + 8));
129
- const port = decodeWideAtPtr(readPtr(dv, off + 24));
130
- const driver = decodeWideAtPtr(readPtr(dv, off + 32));
131
- const location = decodeWideAtPtr(readPtr(dv, off + 48));
132
- const comment = decodeWideAtPtr(readPtr(dv, off + 40));
133
- const status = dv.getUint32(off + 124, true);
143
+ const name = decodeWideAtPtr(readPtr(dv, off + offName));
144
+ const port = decodeWideAtPtr(readPtr(dv, off + offPort));
145
+ const driver = decodeWideAtPtr(readPtr(dv, off + offDrv));
146
+ const location = decodeWideAtPtr(readPtr(dv, off + offLocation));
147
+ const comment = decodeWideAtPtr(readPtr(dv, off + offComment));
148
+ const status = dv.getUint32(off + offStatus, true);
134
149
  std.printf(' [%d] %s\n', i + 1, name);
135
150
  std.printf(' port: %s\n', port);
136
151
  std.printf(' driver: %s\n', driver);
package/test/test_ffi.ts CHANGED
@@ -1,13 +1,16 @@
1
1
  import * as std from 'std'
2
2
  import * as win from 'win'
3
3
  import * as ffi from 'ffi'
4
+ import * as os from 'os'
4
5
  import { Tester } from './test_helper.js'
5
6
 
7
+ const MAX_WCHARS = 4096
8
+
6
9
  function decodeWideAtPtr(ptr: number): string {
7
10
  if (!ptr) return ''
8
11
  const chars: number[] = []
9
12
  let pos = ptr
10
- while (true) {
13
+ while (chars.length < MAX_WCHARS) {
11
14
  const low = ffi.readByte(pos)
12
15
  const high = ffi.readByte(pos + 1)
13
16
  const ch = low + high * 256
@@ -19,6 +22,9 @@ function decodeWideAtPtr(ptr: number): string {
19
22
  }
20
23
 
21
24
  function readPtr(dv: DataView, offset: number): number {
25
+ if (os.arch === 'ia32') {
26
+ return dv.getUint32(offset, true)
27
+ }
22
28
  const low = dv.getUint32(offset, true)
23
29
  const high = dv.getUint32(offset + 4, true)
24
30
  return low + high * 4294967296
@@ -136,16 +142,26 @@ export const suite = {
136
142
 
137
143
  if (returnedBuf[0]! > 0) {
138
144
  const dv = new DataView(printerBuf)
139
- const structSize = 136
145
+ // PRINTER_INFO_2W layout (MinGW verified):
146
+ // x64: sizeof=136 name=8 port=24 drv=32 comment=40 location=48 status=124
147
+ // ia32: sizeof=84 name=4 port=12 drv=16 comment=20 location=24 status=72
148
+ const ia32 = os.arch === 'ia32'
149
+ const structSize = ia32 ? 84 : 136
150
+ const offName = ia32 ? 4 : 8
151
+ const offPort = ia32 ? 12 : 24
152
+ const offDrv = ia32 ? 16 : 32
153
+ const offComment = ia32 ? 20 : 40
154
+ const offLocation = ia32 ? 24 : 48
155
+ const offStatus = ia32 ? 72 : 124
140
156
 
141
157
  for (let i = 0; i < returnedBuf[0]! && i < 20; i++) {
142
158
  const off = i * structSize
143
- const name = decodeWideAtPtr(readPtr(dv, off + 8))
144
- const port = decodeWideAtPtr(readPtr(dv, off + 24))
145
- const driver = decodeWideAtPtr(readPtr(dv, off + 32))
146
- const location = decodeWideAtPtr(readPtr(dv, off + 48))
147
- const comment = decodeWideAtPtr(readPtr(dv, off + 40))
148
- const status = dv.getUint32(off + 124, true)
159
+ const name = decodeWideAtPtr(readPtr(dv, off + offName))
160
+ const port = decodeWideAtPtr(readPtr(dv, off + offPort))
161
+ const driver = decodeWideAtPtr(readPtr(dv, off + offDrv))
162
+ const location = decodeWideAtPtr(readPtr(dv, off + offLocation))
163
+ const comment = decodeWideAtPtr(readPtr(dv, off + offComment))
164
+ const status = dv.getUint32(off + offStatus, true)
149
165
  std.printf(' [%d] %s\n', i + 1, name)
150
166
  std.printf(' port: %s\n', port)
151
167
  std.printf(' driver: %s\n', driver)
@@ -0,0 +1,45 @@
1
+ import * as os from 'os';
2
+ import * as std from 'std';
3
+ function runRound() {
4
+ const ids = [1, 2];
5
+ return Promise.all(ids.map((id) => new Promise((resolve, reject) => {
6
+ const worker = new os.Worker('./worker_module.js');
7
+ const timer = os.setTimeout(() => {
8
+ worker.onmessage = null;
9
+ reject(new Error(`worker ${id} timeout`));
10
+ }, 5000);
11
+ worker.onmessage = (e) => {
12
+ os.clearTimeout(timer);
13
+ worker.postMessage({ type: 'done' });
14
+ worker.onmessage = null;
15
+ resolve(e.data);
16
+ };
17
+ worker.postMessage({ type: 'start' });
18
+ })));
19
+ }
20
+ export const suite = {
21
+ name: 'worker-concurrent',
22
+ run: async (t) => {
23
+ t.section('dual-worker-init');
24
+ const N = 20;
25
+ let ok = 0;
26
+ let lastErr = null;
27
+ for (let i = 0; i < N; i++) {
28
+ try {
29
+ const results = await runRound();
30
+ if (results.length === 2 &&
31
+ results.every((r) => r.type === 'result' && r.value === 42)) {
32
+ ok++;
33
+ }
34
+ }
35
+ catch (e) {
36
+ lastErr = e;
37
+ break;
38
+ }
39
+ }
40
+ if (ok !== N && lastErr) {
41
+ std.printf('round error: %s\n', String(lastErr));
42
+ }
43
+ t.check('concurrent dual-worker rounds', N, ok);
44
+ }
45
+ };