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.
- package/README.en.md +7 -3
- package/README.md +7 -3
- package/examples/exec_server.js +3535 -34
- package/examples/exec_server.ts +71 -26
- package/examples/exec_server_worker.js +45 -0
- package/examples/exec_server_worker.ts +39 -0
- package/examples/setres.js +104 -0
- package/examples/setres.ts +120 -0
- package/package.json +1 -1
- package/quickwin.d.ts +3 -3
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/run.js +3 -1
- package/test/run.ts +3 -1
- package/test/test_ffi.js +23 -8
- package/test/test_ffi.ts +24 -8
- package/test/test_worker_concurrent.js +45 -0
- package/test/test_worker_concurrent.ts +56 -0
- package/test/test_worker_data_url.js +46 -0
- package/test/test_worker_data_url.ts +51 -0
package/examples/exec_server.ts
CHANGED
|
@@ -1,35 +1,80 @@
|
|
|
1
1
|
import { createServer } from '../lib/http-server.js'
|
|
2
|
-
import * as
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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('
|
|
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
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
|
|
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
|
-
/**
|
|
344
|
-
const
|
|
343
|
+
/** Process architecture (pointer width): `"x64"` or `"ia32"`. */
|
|
344
|
+
const arch: string;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
declare module "sock" {
|
package/qwin-nowasm-x86.exe
CHANGED
|
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 (
|
|
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
|
-
|
|
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 +
|
|
129
|
-
const port = decodeWideAtPtr(readPtr(dv, off +
|
|
130
|
-
const driver = decodeWideAtPtr(readPtr(dv, off +
|
|
131
|
-
const location = decodeWideAtPtr(readPtr(dv, off +
|
|
132
|
-
const comment = decodeWideAtPtr(readPtr(dv, off +
|
|
133
|
-
const status = dv.getUint32(off +
|
|
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 (
|
|
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
|
-
|
|
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 +
|
|
144
|
-
const port = decodeWideAtPtr(readPtr(dv, off +
|
|
145
|
-
const driver = decodeWideAtPtr(readPtr(dv, off +
|
|
146
|
-
const location = decodeWideAtPtr(readPtr(dv, off +
|
|
147
|
-
const comment = decodeWideAtPtr(readPtr(dv, off +
|
|
148
|
-
const status = dv.getUint32(off +
|
|
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
|
+
};
|