quickwin 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +7 -7
- package/README.md +7 -7
- package/constants.ts +1070 -0
- package/enum_map.d.ts +130 -0
- package/examples/PdfCanvas.js +2 -2
- package/examples/PdfCanvas.tsx +3 -2
- package/examples/{execd.js → execd/main.js} +717 -148
- package/examples/execd/tray.js +192 -0
- package/examples/{execd_worker.js → execd/worker.js} +7 -7
- package/examples/pdf_preview2.js +46 -46
- package/examples/pdf_preview2.ts +55 -54
- package/examples/pdf_viewer.js +34 -19
- package/examples/pdf_viewer.tsx +6 -5
- package/examples/test_gallery.js +35 -20
- package/examples/test_gallery.tsx +9 -8
- package/examples/test_react_lazy.js +34 -19
- package/examples/test_react_lazy.tsx +7 -6
- package/examples/test_react_listbox.js +34 -19
- package/examples/test_react_listbox.tsx +8 -7
- package/examples/test_root_window.js +34 -19
- package/examples/test_root_window.tsx +5 -5
- package/examples/test_tab.js +34 -19
- package/examples/test_tab.tsx +7 -6
- package/examples/tray_demo.js +8 -8
- package/examples/tray_demo.tsx +9 -8
- package/lib/dumpRects.js +2 -2
- package/lib/dumpRects.ts +3 -2
- package/lib/fetch.js +15 -15
- package/lib/fetch.ts +16 -15
- package/lib/ffi/bind.js +3 -3
- package/lib/ffi/bind.ts +3 -3
- package/lib/http-server.js +54 -29
- package/lib/http-server.ts +56 -27
- package/lib/react-qw/components/Button.js +2 -2
- package/lib/react-qw/components/CheckBox.js +4 -4
- package/lib/react-qw/components/ComboBox.js +10 -10
- package/lib/react-qw/components/DateTimePicker.js +12 -12
- package/lib/react-qw/components/Input.js +7 -7
- package/lib/react-qw/components/Link.js +4 -4
- package/lib/react-qw/components/ListBox.js +12 -12
- package/lib/react-qw/components/ListView.js +35 -35
- package/lib/react-qw/components/PathPicker.js +3 -3
- package/lib/react-qw/components/ProgressBar.js +4 -4
- package/lib/react-qw/components/RadioButton.js +4 -4
- package/lib/react-qw/components/ScrollView.js +28 -28
- package/lib/react-qw/components/Slider.js +11 -11
- package/lib/react-qw/components/Tab.js +10 -10
- package/lib/react-qw/components/Tooltip.js +8 -8
- package/lib/react-qw/components/TreeView.js +20 -20
- package/lib/react-qw/index.js +5 -5
- package/lib/react-qw/index.ts +6 -5
- package/lib/react-qw/props.js +4 -4
- package/lib/react-qw/props.ts +5 -4
- package/lib/react-qw/reconciler.js +10 -10
- package/lib/react-qw/reconciler.ts +11 -10
- package/lib/text-measure.js +3 -3
- package/lib/text-measure.ts +4 -3
- package/lib/websocket.js +17 -17
- package/lib/websocket.ts +18 -17
- package/lib/windows/advapi32.js +7 -7
- package/lib/windows/advapi32.ts +7 -7
- package/lib/windows/gdi32.js +16 -16
- package/lib/windows/gdi32.ts +16 -16
- package/lib/windows/kernel32.js +41 -2
- package/lib/windows/kernel32.ts +45 -2
- package/lib/windows/shell32.js +24 -3
- package/lib/windows/shell32.ts +27 -4
- package/lib/windows/structs.js +54 -0
- package/lib/windows/structs.ts +63 -1
- package/lib/windows/user32.js +18 -6
- package/lib/windows/user32.ts +18 -6
- package/lib/windows/winspool.js +3 -3
- package/lib/windows/winspool.ts +3 -3
- package/package.json +4 -3
- package/quickwin.d.ts +32 -10
- 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/test_ffi_bind.js +7 -7
- package/test/test_ffi_bind.ts +8 -8
- package/test/test_ffi_struct.js +2 -2
- package/test/test_ffi_struct.ts +3 -3
- package/test/test_ffi_windows.js +1 -1
- package/test/test_ffi_windows.ts +1 -1
- package/test/test_files.js +7 -7
- package/test/test_files.ts +8 -8
- package/test/test_gdi_draw.js +51 -51
- package/test/test_gdi_draw.ts +57 -57
- package/test/test_http_server.js +3 -3
- package/test/test_http_server.ts +4 -3
- package/test/test_ipv6.js +3 -3
- package/test/test_ipv6.ts +4 -3
- package/test/test_net_event.js +3 -3
- package/test/test_net_event.ts +4 -3
- package/test/test_printer.js +4 -4
- package/test/test_printer.ts +5 -5
- package/test/test_react_complex.js +36 -21
- package/test/test_react_counter.js +34 -19
- package/test/test_react_flex.js +36 -21
- package/test/test_react_hidden.js +34 -19
- package/test/test_react_listview_link.js +35 -20
- package/test/test_react_notify.js +52 -37
- package/test/test_react_render.js +34 -19
- package/test/test_registry.js +6 -6
- package/test/test_registry.ts +8 -8
- package/test/test_wolfssl_read.js +8 -8
- package/test/test_wolfssl_read.ts +9 -8
- package/examples/execd.tsx +0 -798
- package/examples/execd_worker.ts +0 -127
- package/quickwin_const.d.ts +0 -1063
package/lib/fetch.js
CHANGED
|
@@ -180,7 +180,7 @@ async function fetchRequest(req) {
|
|
|
180
180
|
sock.set_on_event(fd, (event) => {
|
|
181
181
|
if (state === ST_DONE)
|
|
182
182
|
return;
|
|
183
|
-
if (event.lNetworkEvents & 16 /*
|
|
183
|
+
if (event.lNetworkEvents & 16 /* FdEvent.FD_CONNECT */) {
|
|
184
184
|
const err = event.iErrorCode[0];
|
|
185
185
|
if (err !== 0) {
|
|
186
186
|
doReject(new Error('Connection failed: ' + err));
|
|
@@ -194,10 +194,10 @@ async function fetchRequest(req) {
|
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
if (req.rejectUnauthorized === false) {
|
|
197
|
-
wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /*
|
|
197
|
+
wolfssl.wolfSSL_CTX_set_verify(ctx, 0 /* VerifyMode.SSL_VERIFY_NONE */);
|
|
198
198
|
}
|
|
199
199
|
else {
|
|
200
|
-
wolfssl.wolfSSL_CTX_set_verify(ctx, 1 /*
|
|
200
|
+
wolfssl.wolfSSL_CTX_set_verify(ctx, 1 /* VerifyMode.SSL_VERIFY_PEER */);
|
|
201
201
|
wolfssl.loadTrustedCerts(ctx);
|
|
202
202
|
}
|
|
203
203
|
ssl = wolfssl.wolfSSL_new(ctx);
|
|
@@ -207,7 +207,7 @@ async function fetchRequest(req) {
|
|
|
207
207
|
}
|
|
208
208
|
wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd));
|
|
209
209
|
if (parsedUrl.hostname) {
|
|
210
|
-
wolfssl.wolfSSL_UseSNI(ssl, 0 /*
|
|
210
|
+
wolfssl.wolfSSL_UseSNI(ssl, 0 /* SniType.WOLFSSL_SNI_HOST_NAME */, parsedUrl.hostname);
|
|
211
211
|
if (req.rejectUnauthorized !== false) {
|
|
212
212
|
wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname);
|
|
213
213
|
}
|
|
@@ -219,21 +219,21 @@ async function fetchRequest(req) {
|
|
|
219
219
|
state = ST_RECV_HEADERS;
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
if ((event.lNetworkEvents & 1 /*
|
|
222
|
+
if ((event.lNetworkEvents & 1 /* FdEvent.FD_READ */) || (event.lNetworkEvents & 2 /* FdEvent.FD_WRITE */)) {
|
|
223
223
|
if (state === ST_HANDSHAKE) {
|
|
224
224
|
if (!ssl) {
|
|
225
225
|
doReject(new Error('TLS not initialized'));
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
228
|
const ret = wolfssl.wolfSSL_connect(ssl);
|
|
229
|
-
if (ret === 1 /*
|
|
229
|
+
if (ret === 1 /* ReturnCode.SSL_SUCCESS */) {
|
|
230
230
|
wolfssl.wolfSSL_write(ssl, httpRequest);
|
|
231
231
|
state = ST_RECV_HEADERS;
|
|
232
232
|
}
|
|
233
233
|
else {
|
|
234
234
|
const err = wolfssl.wolfSSL_get_error(ssl, ret);
|
|
235
|
-
if (err !== 2 /*
|
|
236
|
-
err !== 3 /*
|
|
235
|
+
if (err !== 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ &&
|
|
236
|
+
err !== 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */) {
|
|
237
237
|
doReject(new Error('TLS handshake failed: ' + err));
|
|
238
238
|
}
|
|
239
239
|
}
|
|
@@ -251,10 +251,10 @@ async function fetchRequest(req) {
|
|
|
251
251
|
}
|
|
252
252
|
else {
|
|
253
253
|
const err = wolfssl.wolfSSL_get_error(ssl, n);
|
|
254
|
-
if (err === 2 /*
|
|
255
|
-
err === 3 /*
|
|
254
|
+
if (err === 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
|
|
255
|
+
err === 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
|
|
256
256
|
break;
|
|
257
|
-
if (n === 0 || err === 6 /*
|
|
257
|
+
if (n === 0 || err === 6 /* WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
|
|
258
258
|
break;
|
|
259
259
|
doReject(new Error('TLS read failed: ' + err));
|
|
260
260
|
return;
|
|
@@ -338,10 +338,10 @@ async function fetchRequest(req) {
|
|
|
338
338
|
}
|
|
339
339
|
else {
|
|
340
340
|
const err = wolfssl.wolfSSL_get_error(ssl, n);
|
|
341
|
-
if (err === 2 /*
|
|
342
|
-
err === 3 /*
|
|
341
|
+
if (err === 2 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ */ ||
|
|
342
|
+
err === 3 /* WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE */)
|
|
343
343
|
break;
|
|
344
|
-
if (n === 0 || err === 6 /*
|
|
344
|
+
if (n === 0 || err === 6 /* WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN */)
|
|
345
345
|
break;
|
|
346
346
|
// Fatal TLS error mid-body: the response already
|
|
347
347
|
// resolved, so fail the body stream instead of
|
|
@@ -390,7 +390,7 @@ async function fetchRequest(req) {
|
|
|
390
390
|
}
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
|
-
if (event.lNetworkEvents & 32 /*
|
|
393
|
+
if (event.lNetworkEvents & 32 /* FdEvent.FD_CLOSE */) {
|
|
394
394
|
if (state === ST_DONE)
|
|
395
395
|
return;
|
|
396
396
|
if (state === ST_RECV_HEADERS) {
|
package/lib/fetch.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
RequestImpl,
|
|
13
13
|
ResponseImpl,
|
|
14
14
|
} from './http-common.js'
|
|
15
|
+
import { FdEvent, ReturnCode, SniType, VerifyMode, WolfsslErrorCode } from '../constants'
|
|
15
16
|
|
|
16
17
|
// ── HTTP Response Parser ──
|
|
17
18
|
|
|
@@ -178,7 +179,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
178
179
|
sock.set_on_event(fd, (event: { lNetworkEvents: number; iErrorCode: number[] }) => {
|
|
179
180
|
if (state === ST_DONE) return
|
|
180
181
|
|
|
181
|
-
if (event.lNetworkEvents &
|
|
182
|
+
if (event.lNetworkEvents & FdEvent.FD_CONNECT) {
|
|
182
183
|
const err = event.iErrorCode[0]
|
|
183
184
|
if (err !== 0) { doReject(new Error('Connection failed: ' + err)); return }
|
|
184
185
|
|
|
@@ -187,16 +188,16 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
187
188
|
ctx = wolfssl.wolfSSL_CTX_new(tlsMethod)
|
|
188
189
|
if (!ctx) { doReject(new Error('SSL_CTX_new failed')); return }
|
|
189
190
|
if (req.rejectUnauthorized === false) {
|
|
190
|
-
wolfssl.wolfSSL_CTX_set_verify(ctx,
|
|
191
|
+
wolfssl.wolfSSL_CTX_set_verify(ctx, VerifyMode.SSL_VERIFY_NONE)
|
|
191
192
|
} else {
|
|
192
|
-
wolfssl.wolfSSL_CTX_set_verify(ctx,
|
|
193
|
+
wolfssl.wolfSSL_CTX_set_verify(ctx, VerifyMode.SSL_VERIFY_PEER)
|
|
193
194
|
wolfssl.loadTrustedCerts(ctx)
|
|
194
195
|
}
|
|
195
196
|
ssl = wolfssl.wolfSSL_new(ctx)
|
|
196
197
|
if (!ssl) { doReject(new Error('SSL_new failed')); return }
|
|
197
198
|
wolfssl.wolfSSL_set_fd(ssl, sock.get_fd(fd))
|
|
198
199
|
if (parsedUrl.hostname) {
|
|
199
|
-
wolfssl.wolfSSL_UseSNI(ssl,
|
|
200
|
+
wolfssl.wolfSSL_UseSNI(ssl, SniType.WOLFSSL_SNI_HOST_NAME, parsedUrl.hostname)
|
|
200
201
|
if (req.rejectUnauthorized !== false) {
|
|
201
202
|
wolfssl.wolfSSL_check_domain_name(ssl, parsedUrl.hostname)
|
|
202
203
|
}
|
|
@@ -208,17 +209,17 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
208
209
|
}
|
|
209
210
|
}
|
|
210
211
|
|
|
211
|
-
if ((event.lNetworkEvents &
|
|
212
|
+
if ((event.lNetworkEvents & FdEvent.FD_READ) || (event.lNetworkEvents & FdEvent.FD_WRITE)) {
|
|
212
213
|
if (state === ST_HANDSHAKE) {
|
|
213
214
|
if (!ssl) { doReject(new Error('TLS not initialized')); return }
|
|
214
215
|
const ret = wolfssl.wolfSSL_connect(ssl)
|
|
215
|
-
if (ret ===
|
|
216
|
+
if (ret === ReturnCode.SSL_SUCCESS) {
|
|
216
217
|
wolfssl.wolfSSL_write(ssl, httpRequest)
|
|
217
218
|
state = ST_RECV_HEADERS
|
|
218
219
|
} else {
|
|
219
220
|
const err = wolfssl.wolfSSL_get_error(ssl, ret)
|
|
220
|
-
if (err !==
|
|
221
|
-
err !==
|
|
221
|
+
if (err !== WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ &&
|
|
222
|
+
err !== WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) {
|
|
222
223
|
doReject(new Error('TLS handshake failed: ' + err))
|
|
223
224
|
}
|
|
224
225
|
}
|
|
@@ -234,9 +235,9 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
234
235
|
data = buf.slice(0, n)
|
|
235
236
|
} else {
|
|
236
237
|
const err = wolfssl.wolfSSL_get_error(ssl, n)
|
|
237
|
-
if (err ===
|
|
238
|
-
err ===
|
|
239
|
-
if (n === 0 || err ===
|
|
238
|
+
if (err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ ||
|
|
239
|
+
err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
|
|
240
|
+
if (n === 0 || err === WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
|
|
240
241
|
doReject(new Error('TLS read failed: ' + err))
|
|
241
242
|
return
|
|
242
243
|
}
|
|
@@ -315,9 +316,9 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
315
316
|
data = buf.slice(0, n)
|
|
316
317
|
} else {
|
|
317
318
|
const err = wolfssl.wolfSSL_get_error(ssl, n)
|
|
318
|
-
if (err ===
|
|
319
|
-
err ===
|
|
320
|
-
if (n === 0 || err ===
|
|
319
|
+
if (err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_READ ||
|
|
320
|
+
err === WolfsslErrorCode.WOLFSSL_ERROR_WANT_WRITE) break
|
|
321
|
+
if (n === 0 || err === WolfsslErrorCode.WOLFSSL_ERROR_ZERO_RETURN) break
|
|
321
322
|
// Fatal TLS error mid-body: the response already
|
|
322
323
|
// resolved, so fail the body stream instead of
|
|
323
324
|
// silently waiting for an FD event that never comes.
|
|
@@ -355,7 +356,7 @@ async function fetchRequest(req: RequestImpl): Promise<ResponseImpl> {
|
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
|
|
358
|
-
if (event.lNetworkEvents &
|
|
359
|
+
if (event.lNetworkEvents & FdEvent.FD_CLOSE) {
|
|
359
360
|
if (state === ST_DONE) return
|
|
360
361
|
if (state === ST_RECV_HEADERS) {
|
|
361
362
|
doReject(new Error('Connection closed before response'))
|
package/lib/ffi/bind.js
CHANGED
|
@@ -73,7 +73,7 @@ function makeFn(proc, sig, encoders, decoders) {
|
|
|
73
73
|
}
|
|
74
74
|
return (...a) => callPacked(proc, ps, a, encoders, decoders);
|
|
75
75
|
}
|
|
76
|
-
// 每个参数在 argFrame 里占的字节数。与 quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
|
|
76
|
+
// 每个参数在 argFrame 里占的字节数。与 src/quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
|
|
77
77
|
// 保持一致(ia32:≤4B 类型进 4 字节、更大的进 8 字节;x64:恒 8 字节槽)。
|
|
78
78
|
// 注意这是「槽宽」,不是类型的 sizeof——同一定义也用于返回槽。指针布局恒指针宽。
|
|
79
79
|
const IA32_ARG_SIZE = {
|
|
@@ -334,7 +334,7 @@ const leakRegistry = new FinalizationRegistry((held) => {
|
|
|
334
334
|
// args/ret 为「底档 + '!' 绑定」的记录(与 ParsedSig 同形态——修饰随 token 走,
|
|
335
335
|
// 消费者要么整体拿到、要么整体拿不到)。'!' 违约(C 传入 0 / fn 返回 0)打印诊断并
|
|
336
336
|
// 中止本次分发(fn 不被调用 / 结果槽保持 0),与 fn-throw 同风格:此处不 throw,
|
|
337
|
-
// 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,quickjs-ffi-closure.c)。
|
|
337
|
+
// 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,src/quickjs-ffi-closure.c)。
|
|
338
338
|
function dispatchClosure(args, ret, fn, frameBuf, retBuf, sig) {
|
|
339
339
|
const dv = new DataView(retBuf);
|
|
340
340
|
const is64 = os.arch === 'x64';
|
|
@@ -409,7 +409,7 @@ export function closure(sig, fn, opts) {
|
|
|
409
409
|
const retSpec = { tok: cTokenToType(retToken), nonnull: retToken.endsWith('!') };
|
|
410
410
|
const is64 = os.arch === 'x64';
|
|
411
411
|
const stackCapture = argSpecs.reduce((s, x) => s + IA32_ARG_SIZE[x.tok], 0);
|
|
412
|
-
// C 桩捕获窗定长(quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
|
|
412
|
+
// C 桩捕获窗定长(src/quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
|
|
413
413
|
// 即至多 16 参;ia32 16×4B)——超限 dispatch 会读到捕获缓冲之外的垃圾,或
|
|
414
414
|
// RangeError 被吞成返回 0,故创建期 fail-fast。扩容改 C 侧 buf 尺寸并同步放宽。
|
|
415
415
|
if (is64 ? argSpecs.length > 16 : stackCapture > 64) {
|
package/lib/ffi/bind.ts
CHANGED
|
@@ -123,7 +123,7 @@ function makeFn(proc: number, sig: string, encoders: CodecMap, decoders: CodecMa
|
|
|
123
123
|
return (...a: unknown[]) => callPacked(proc, ps, a, encoders, decoders)
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
// 每个参数在 argFrame 里占的字节数。与 quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
|
|
126
|
+
// 每个参数在 argFrame 里占的字节数。与 src/quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
|
|
127
127
|
// 保持一致(ia32:≤4B 类型进 4 字节、更大的进 8 字节;x64:恒 8 字节槽)。
|
|
128
128
|
// 注意这是「槽宽」,不是类型的 sizeof——同一定义也用于返回槽。指针布局恒指针宽。
|
|
129
129
|
const IA32_ARG_SIZE: Record<C_BasicType_No_Void, number> = {
|
|
@@ -429,7 +429,7 @@ const leakRegistry: FinalizationRegistry<{ sig: string, ptr: number }> =
|
|
|
429
429
|
// args/ret 为「底档 + '!' 绑定」的记录(与 ParsedSig 同形态——修饰随 token 走,
|
|
430
430
|
// 消费者要么整体拿到、要么整体拿不到)。'!' 违约(C 传入 0 / fn 返回 0)打印诊断并
|
|
431
431
|
// 中止本次分发(fn 不被调用 / 结果槽保持 0),与 fn-throw 同风格:此处不 throw,
|
|
432
|
-
// 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,quickjs-ffi-closure.c)。
|
|
432
|
+
// 异常通道只留给用户回调(C 桩捕获 wrapper 异常并清零,src/quickjs-ffi-closure.c)。
|
|
433
433
|
function dispatchClosure(
|
|
434
434
|
args: { tok: C_BasicType_No_Void, nonnull: boolean }[],
|
|
435
435
|
ret: { tok: C_BasicType, nonnull: boolean },
|
|
@@ -508,7 +508,7 @@ export function closure<S extends string>(sig: S, fn: BindFn<S, {}, {}>,
|
|
|
508
508
|
const retSpec = { tok: cTokenToType(retToken), nonnull: retToken.endsWith('!') }
|
|
509
509
|
const is64 = os.arch === 'x64'
|
|
510
510
|
const stackCapture = argSpecs.reduce((s, x) => s + IA32_ARG_SIZE[x.tok], 0)
|
|
511
|
-
// C 桩捕获窗定长(quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
|
|
511
|
+
// C 桩捕获窗定长(src/quickjs-ffi-closure.c:x64 栈区 96B=12 槽,第 5 参起在栈上,
|
|
512
512
|
// 即至多 16 参;ia32 16×4B)——超限 dispatch 会读到捕获缓冲之外的垃圾,或
|
|
513
513
|
// RangeError 被吞成返回 0,故创建期 fail-fast。扩容改 C 侧 buf 尺寸并同步放宽。
|
|
514
514
|
if (is64 ? argSpecs.length > 16 : stackCapture > 64) {
|
package/lib/http-server.js
CHANGED
|
@@ -24,6 +24,11 @@ function concatU8(parts) {
|
|
|
24
24
|
return out;
|
|
25
25
|
}
|
|
26
26
|
function toArrayBuffer(u8) {
|
|
27
|
+
// 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
|
|
28
|
+
// body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
|
|
29
|
+
//(FFI 持有的 buffer 生命周期不受 view 约束)。
|
|
30
|
+
if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength)
|
|
31
|
+
return u8.buffer;
|
|
27
32
|
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength);
|
|
28
33
|
}
|
|
29
34
|
function findCrLfCrLf(buf) {
|
|
@@ -163,7 +168,7 @@ export class HttpServer {
|
|
|
163
168
|
}
|
|
164
169
|
this.serverSock = s;
|
|
165
170
|
sock.set_on_event(s, (event) => {
|
|
166
|
-
if (event.lNetworkEvents & 8 /*
|
|
171
|
+
if (event.lNetworkEvents & 8 /* FdEvent.FD_ACCEPT */)
|
|
167
172
|
this.acceptLoop();
|
|
168
173
|
});
|
|
169
174
|
return 0;
|
|
@@ -190,6 +195,7 @@ export class HttpServer {
|
|
|
190
195
|
const c = {
|
|
191
196
|
sock: ac.handle,
|
|
192
197
|
parts: [],
|
|
198
|
+
partsLen: 0,
|
|
193
199
|
pending: null,
|
|
194
200
|
current: null,
|
|
195
201
|
outQueue: null,
|
|
@@ -207,11 +213,11 @@ export class HttpServer {
|
|
|
207
213
|
onConnEvent(c, event) {
|
|
208
214
|
if (c.closed)
|
|
209
215
|
return;
|
|
210
|
-
if (event.lNetworkEvents & 1 /*
|
|
216
|
+
if (event.lNetworkEvents & 1 /* FdEvent.FD_READ */)
|
|
211
217
|
onRead(c);
|
|
212
|
-
if (event.lNetworkEvents & 2 /*
|
|
218
|
+
if (event.lNetworkEvents & 2 /* FdEvent.FD_WRITE */)
|
|
213
219
|
flushQueue(c);
|
|
214
|
-
if (event.lNetworkEvents & 32 /*
|
|
220
|
+
if (event.lNetworkEvents & 32 /* FdEvent.FD_CLOSE */) {
|
|
215
221
|
onRead(c);
|
|
216
222
|
abortConn(c);
|
|
217
223
|
closeConn(c);
|
|
@@ -229,7 +235,9 @@ function onRead(c) {
|
|
|
229
235
|
const d = sock.recv(c.sock, 8192);
|
|
230
236
|
if (!d)
|
|
231
237
|
break;
|
|
232
|
-
|
|
238
|
+
const u8 = new Uint8Array(d);
|
|
239
|
+
c.parts.push(u8);
|
|
240
|
+
c.partsLen += u8.length;
|
|
233
241
|
}
|
|
234
242
|
if (!c.closed)
|
|
235
243
|
tryProcess(c);
|
|
@@ -334,9 +342,19 @@ async function writeStreamingBody(c, body, chunked, trailer) {
|
|
|
334
342
|
if (done)
|
|
335
343
|
break;
|
|
336
344
|
if (value && value.byteLength > 0) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
345
|
+
// 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
|
|
346
|
+
// toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
|
|
347
|
+
// 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
|
|
348
|
+
// 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
|
|
349
|
+
const MAX_SEND = 65536;
|
|
350
|
+
for (let off = 0; off < value.byteLength; off += MAX_SEND) {
|
|
351
|
+
const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength));
|
|
352
|
+
const frame = chunked ? encodeChunkFrame(piece, enc) : piece;
|
|
353
|
+
if (!queueSend(c, frame)) {
|
|
354
|
+
await new Promise((resolve) => { c.resumeRead = resolve; });
|
|
355
|
+
}
|
|
356
|
+
if (c.closed)
|
|
357
|
+
break;
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
360
|
}
|
|
@@ -383,38 +401,43 @@ function whole(c) {
|
|
|
383
401
|
function trimParts(c, buf, consumed) {
|
|
384
402
|
if (consumed >= buf.length) {
|
|
385
403
|
c.parts = [];
|
|
404
|
+
c.partsLen = 0;
|
|
386
405
|
}
|
|
387
406
|
else {
|
|
388
407
|
c.parts = [buf.subarray(consumed)];
|
|
408
|
+
c.partsLen = buf.length - consumed;
|
|
389
409
|
}
|
|
390
410
|
}
|
|
391
411
|
function tryProcess(c) {
|
|
392
412
|
if (c.closed || c.current)
|
|
393
413
|
return;
|
|
394
|
-
const buf = whole(c);
|
|
395
414
|
if (c.pending) {
|
|
396
415
|
const bodyStart = c.pending.bodyStart;
|
|
397
416
|
const parsed = c.pending.parsed;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
return;
|
|
404
|
-
body = dec.data;
|
|
405
|
-
end = dec.end;
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
const needed = parsed.contentLength;
|
|
409
|
-
if (bodyStart + needed > buf.length)
|
|
417
|
+
if (!parsed.chunked) {
|
|
418
|
+
// O(1) length check while the body is still arriving — only
|
|
419
|
+
// concatenate once, when the last byte is in.
|
|
420
|
+
const end = bodyStart + parsed.contentLength;
|
|
421
|
+
if (end > c.partsLen)
|
|
410
422
|
return;
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
const buf = whole(c);
|
|
424
|
+
c.pending = null;
|
|
425
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf);
|
|
426
|
+
return;
|
|
413
427
|
}
|
|
428
|
+
// chunked: no total length up front, so a decode attempt still needs
|
|
429
|
+
// the concatenated view (re-tried per read event as before).
|
|
430
|
+
const buf = whole(c);
|
|
431
|
+
const dec = decodeChunked(buf, bodyStart);
|
|
432
|
+
if (!dec)
|
|
433
|
+
return;
|
|
414
434
|
c.pending = null;
|
|
415
|
-
dispatchRequest(c, parsed,
|
|
435
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf);
|
|
416
436
|
return;
|
|
417
437
|
}
|
|
438
|
+
// Header phase: bodies are checked against `partsLen` above, so a full
|
|
439
|
+
// concatenation is only needed here — once per request, not per read.
|
|
440
|
+
const buf = whole(c);
|
|
418
441
|
const headerEnd = findCrLfCrLf(buf);
|
|
419
442
|
if (headerEnd < 0)
|
|
420
443
|
return;
|
|
@@ -432,20 +455,20 @@ function tryProcess(c) {
|
|
|
432
455
|
c.pending = { parsed, bodyStart };
|
|
433
456
|
return;
|
|
434
457
|
}
|
|
435
|
-
dispatchRequest(c, parsed, dec.data, dec.end);
|
|
458
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf);
|
|
436
459
|
}
|
|
437
460
|
else if (parsed.contentLength > 0) {
|
|
438
461
|
if (bodyStart + parsed.contentLength > buf.length) {
|
|
439
462
|
c.pending = { parsed, bodyStart };
|
|
440
463
|
return;
|
|
441
464
|
}
|
|
442
|
-
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength);
|
|
465
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf);
|
|
443
466
|
}
|
|
444
467
|
else {
|
|
445
|
-
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart);
|
|
468
|
+
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf);
|
|
446
469
|
}
|
|
447
470
|
}
|
|
448
|
-
function dispatchRequest(c, parsed, body, end) {
|
|
471
|
+
function dispatchRequest(c, parsed, body, end, wholeBuf) {
|
|
449
472
|
const connHeader = (parsed.headers['connection'] || '').toLowerCase();
|
|
450
473
|
if (parsed.version === 'HTTP/1.1')
|
|
451
474
|
c.keepAlive = connHeader.indexOf('close') < 0;
|
|
@@ -468,7 +491,9 @@ function dispatchRequest(c, parsed, body, end) {
|
|
|
468
491
|
});
|
|
469
492
|
c.ac = ac;
|
|
470
493
|
c.current = { req };
|
|
471
|
-
|
|
494
|
+
// `wholeBuf` is the concatenation the caller already produced — reuse it
|
|
495
|
+
// instead of concatenating the very same parts a second time.
|
|
496
|
+
trimParts(c, wholeBuf ?? whole(c), end);
|
|
472
497
|
void handleRequest(c);
|
|
473
498
|
}
|
|
474
499
|
async function handleRequest(c) {
|
package/lib/http-server.ts
CHANGED
|
@@ -2,6 +2,7 @@ import './polyfill.js'
|
|
|
2
2
|
import * as sock from 'sock'
|
|
3
3
|
import { HeadersImpl, RequestImpl, ResponseImpl, _readStream } from './http-common.js'
|
|
4
4
|
import { AbortController } from './abort.js'
|
|
5
|
+
import { FdEvent } from '../constants'
|
|
5
6
|
|
|
6
7
|
type SockHandle = import('sock').SockHandle
|
|
7
8
|
|
|
@@ -34,6 +35,10 @@ interface ActiveReq {
|
|
|
34
35
|
interface Conn {
|
|
35
36
|
sock: SockHandle
|
|
36
37
|
parts: Uint8Array[]
|
|
38
|
+
/** Cached total length of `parts` — lets the pending-body wait check
|
|
39
|
+
* "have we received enough yet" in O(1) instead of re-concatenating
|
|
40
|
+
* every received byte on each read event (O(n²) for large bodies). */
|
|
41
|
+
partsLen: number
|
|
37
42
|
pending: PendingReq | null
|
|
38
43
|
current: ActiveReq | null
|
|
39
44
|
outQueue: Uint8Array | null
|
|
@@ -68,6 +73,10 @@ function concatU8(parts: Uint8Array[]): Uint8Array {
|
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
function toArrayBuffer(u8: Uint8Array): ArrayBuffer {
|
|
76
|
+
// 已经是完整 buffer(byteOffset=0、无尾部余量)时零复制返回——发送大
|
|
77
|
+
// body 的主路径,避免每片 send 前白拷一份。视图场景仍 slice 出独立副本
|
|
78
|
+
//(FFI 持有的 buffer 生命周期不受 view 约束)。
|
|
79
|
+
if (u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength) return u8.buffer as ArrayBuffer
|
|
71
80
|
return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer
|
|
72
81
|
}
|
|
73
82
|
|
|
@@ -191,7 +200,7 @@ export class HttpServer {
|
|
|
191
200
|
if (sock.listen(s, backlog ?? 64) !== 0) { sock.closesocket(s); return -1 }
|
|
192
201
|
this.serverSock = s
|
|
193
202
|
sock.set_on_event(s, (event: NetEvent) => {
|
|
194
|
-
if (event.lNetworkEvents &
|
|
203
|
+
if (event.lNetworkEvents & FdEvent.FD_ACCEPT) this.acceptLoop()
|
|
195
204
|
})
|
|
196
205
|
return 0
|
|
197
206
|
}
|
|
@@ -212,6 +221,7 @@ export class HttpServer {
|
|
|
212
221
|
const c: Conn = {
|
|
213
222
|
sock: ac.handle,
|
|
214
223
|
parts: [],
|
|
224
|
+
partsLen: 0,
|
|
215
225
|
pending: null,
|
|
216
226
|
current: null,
|
|
217
227
|
outQueue: null,
|
|
@@ -229,9 +239,9 @@ export class HttpServer {
|
|
|
229
239
|
|
|
230
240
|
private onConnEvent(c: Conn, event: NetEvent): void {
|
|
231
241
|
if (c.closed) return
|
|
232
|
-
if (event.lNetworkEvents &
|
|
233
|
-
if (event.lNetworkEvents &
|
|
234
|
-
if (event.lNetworkEvents &
|
|
242
|
+
if (event.lNetworkEvents & FdEvent.FD_READ) onRead(c)
|
|
243
|
+
if (event.lNetworkEvents & FdEvent.FD_WRITE) flushQueue(c)
|
|
244
|
+
if (event.lNetworkEvents & FdEvent.FD_CLOSE) {
|
|
235
245
|
onRead(c)
|
|
236
246
|
abortConn(c)
|
|
237
247
|
closeConn(c)
|
|
@@ -250,7 +260,9 @@ function onRead(c: Conn): void {
|
|
|
250
260
|
while (true) {
|
|
251
261
|
const d = sock.recv(c.sock, 8192)
|
|
252
262
|
if (!d) break
|
|
253
|
-
|
|
263
|
+
const u8 = new Uint8Array(d)
|
|
264
|
+
c.parts.push(u8)
|
|
265
|
+
c.partsLen += u8.length
|
|
254
266
|
}
|
|
255
267
|
if (!c.closed) tryProcess(c)
|
|
256
268
|
}
|
|
@@ -349,9 +361,18 @@ async function writeStreamingBody(c: Conn, body: ReadableStream<Uint8Array> | nu
|
|
|
349
361
|
const { done, value } = await reader.read()
|
|
350
362
|
if (done) break
|
|
351
363
|
if (value && value.byteLength > 0) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
364
|
+
// 切成 ≤64KB 再入 queueSend:单发 200MB 级大 chunk 时
|
|
365
|
+
// toArrayBuffer 会全量 slice 复制,partial drain 又每次复制
|
|
366
|
+
// 剩余(O(n²)),XP 32 位 1GB 上直接把进程水位顶穿导致断连;
|
|
367
|
+
// 小片让每次复制/发送恒为常数大小,backpressure 语义不变。
|
|
368
|
+
const MAX_SEND = 65536
|
|
369
|
+
for (let off = 0; off < value.byteLength; off += MAX_SEND) {
|
|
370
|
+
const piece = value.byteLength <= MAX_SEND ? value : value.subarray(off, Math.min(off + MAX_SEND, value.byteLength))
|
|
371
|
+
const frame = chunked ? encodeChunkFrame(piece, enc) : piece
|
|
372
|
+
if (!queueSend(c, frame)) {
|
|
373
|
+
await new Promise<void>((resolve) => { c.resumeRead = resolve })
|
|
374
|
+
}
|
|
375
|
+
if (c.closed) break
|
|
355
376
|
}
|
|
356
377
|
}
|
|
357
378
|
}
|
|
@@ -395,36 +416,42 @@ function whole(c: Conn): Uint8Array {
|
|
|
395
416
|
function trimParts(c: Conn, buf: Uint8Array, consumed: number): void {
|
|
396
417
|
if (consumed >= buf.length) {
|
|
397
418
|
c.parts = []
|
|
419
|
+
c.partsLen = 0
|
|
398
420
|
} else {
|
|
399
421
|
c.parts = [buf.subarray(consumed)]
|
|
422
|
+
c.partsLen = buf.length - consumed
|
|
400
423
|
}
|
|
401
424
|
}
|
|
402
425
|
|
|
403
426
|
function tryProcess(c: Conn): void {
|
|
404
427
|
if (c.closed || c.current) return
|
|
405
|
-
const buf = whole(c)
|
|
406
428
|
|
|
407
429
|
if (c.pending) {
|
|
408
430
|
const bodyStart = c.pending.bodyStart
|
|
409
431
|
const parsed = c.pending.parsed
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (bodyStart + needed > buf.length) return
|
|
420
|
-
body = buf.subarray(bodyStart, bodyStart + needed)
|
|
421
|
-
end = bodyStart + needed
|
|
432
|
+
if (!parsed.chunked) {
|
|
433
|
+
// O(1) length check while the body is still arriving — only
|
|
434
|
+
// concatenate once, when the last byte is in.
|
|
435
|
+
const end = bodyStart + parsed.contentLength
|
|
436
|
+
if (end > c.partsLen) return
|
|
437
|
+
const buf = whole(c)
|
|
438
|
+
c.pending = null
|
|
439
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, end), end, buf)
|
|
440
|
+
return
|
|
422
441
|
}
|
|
442
|
+
// chunked: no total length up front, so a decode attempt still needs
|
|
443
|
+
// the concatenated view (re-tried per read event as before).
|
|
444
|
+
const buf = whole(c)
|
|
445
|
+
const dec = decodeChunked(buf, bodyStart)
|
|
446
|
+
if (!dec) return
|
|
423
447
|
c.pending = null
|
|
424
|
-
dispatchRequest(c, parsed,
|
|
448
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf)
|
|
425
449
|
return
|
|
426
450
|
}
|
|
427
451
|
|
|
452
|
+
// Header phase: bodies are checked against `partsLen` above, so a full
|
|
453
|
+
// concatenation is only needed here — once per request, not per read.
|
|
454
|
+
const buf = whole(c)
|
|
428
455
|
const headerEnd = findCrLfCrLf(buf)
|
|
429
456
|
if (headerEnd < 0) return
|
|
430
457
|
const absEnd = headerEnd + 4
|
|
@@ -439,19 +466,19 @@ function tryProcess(c: Conn): void {
|
|
|
439
466
|
c.pending = { parsed, bodyStart }
|
|
440
467
|
return
|
|
441
468
|
}
|
|
442
|
-
dispatchRequest(c, parsed, dec.data, dec.end)
|
|
469
|
+
dispatchRequest(c, parsed, dec.data, dec.end, buf)
|
|
443
470
|
} else if (parsed.contentLength > 0) {
|
|
444
471
|
if (bodyStart + parsed.contentLength > buf.length) {
|
|
445
472
|
c.pending = { parsed, bodyStart }
|
|
446
473
|
return
|
|
447
474
|
}
|
|
448
|
-
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength)
|
|
475
|
+
dispatchRequest(c, parsed, buf.subarray(bodyStart, bodyStart + parsed.contentLength), bodyStart + parsed.contentLength, buf)
|
|
449
476
|
} else {
|
|
450
|
-
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart)
|
|
477
|
+
dispatchRequest(c, parsed, new Uint8Array(0), bodyStart, buf)
|
|
451
478
|
}
|
|
452
479
|
}
|
|
453
480
|
|
|
454
|
-
function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number): void {
|
|
481
|
+
function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: number, wholeBuf?: Uint8Array): void {
|
|
455
482
|
const connHeader = (parsed.headers['connection'] || '').toLowerCase()
|
|
456
483
|
if (parsed.version === 'HTTP/1.1') c.keepAlive = connHeader.indexOf('close') < 0
|
|
457
484
|
else c.keepAlive = connHeader.indexOf('keep-alive') >= 0
|
|
@@ -476,7 +503,9 @@ function dispatchRequest(c: Conn, parsed: ParsedHeader, body: Uint8Array, end: n
|
|
|
476
503
|
|
|
477
504
|
c.ac = ac
|
|
478
505
|
c.current = { req }
|
|
479
|
-
|
|
506
|
+
// `wholeBuf` is the concatenation the caller already produced — reuse it
|
|
507
|
+
// instead of concatenating the very same parts a second time.
|
|
508
|
+
trimParts(c, wholeBuf ?? whole(c), end)
|
|
480
509
|
void handleRequest(c)
|
|
481
510
|
}
|
|
482
511
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
export const Button = forwardRef(({ children, onClick, style, disabled }, ref) => {
|
|
4
|
-
return (_jsx("w", { type: "BUTTON", text: children != null ? (Array.isArray(children) ? children.join('') : String(children)) : '', ws: 268435456 /*
|
|
5
|
-
if (e.msg === 514 /*
|
|
4
|
+
return (_jsx("w", { type: "BUTTON", text: children != null ? (Array.isArray(children) ? children.join('') : String(children)) : '', ws: 268435456 /* WindowStyle.VISIBLE */, style: style, disabled: disabled, ref: ref, onEvent: (e) => {
|
|
5
|
+
if (e.msg === 514 /* WmMsg.LBUTTONUP */)
|
|
6
6
|
onClick?.();
|
|
7
7
|
} }));
|
|
8
8
|
});
|
|
@@ -10,17 +10,17 @@ export const CheckBox = forwardRef(({ checked: controlledChecked, defaultChecked
|
|
|
10
10
|
const h = cbRef.current;
|
|
11
11
|
if (!h)
|
|
12
12
|
return;
|
|
13
|
-
gui.SendMessage(h, 241 /*
|
|
13
|
+
gui.SendMessage(h, 241 /* ButtonMsg.SETCHECK */, displayChecked ? 1 /* ButtonCheckState.CHECKED */ : 0 /* ButtonCheckState.UNCHECKED */, 0);
|
|
14
14
|
}, [displayChecked]);
|
|
15
|
-
return (_jsx("w", { type: "BUTTON", text: label ?? '', ws: 268435456 /*
|
|
15
|
+
return (_jsx("w", { type: "BUTTON", text: label ?? '', ws: 268435456 /* WindowStyle.VISIBLE */ | 3 /* ButtonStyle.AUTOCHECKBOX */, style: style, disabled: disabled, ref: (h) => {
|
|
16
16
|
cbRef.current = h;
|
|
17
17
|
if (typeof ref === 'function')
|
|
18
18
|
ref(h);
|
|
19
19
|
else if (ref)
|
|
20
20
|
ref.current = h;
|
|
21
21
|
}, onEvent: (e) => {
|
|
22
|
-
if (e.msg === 514 /*
|
|
23
|
-
const newChecked = gui.SendMessage(e.hwnd, 240 /*
|
|
22
|
+
if (e.msg === 514 /* WmMsg.LBUTTONUP */) {
|
|
23
|
+
const newChecked = gui.SendMessage(e.hwnd, 240 /* ButtonMsg.GETCHECK */, 0, 0) !== 0;
|
|
24
24
|
if (newChecked !== displayChecked) {
|
|
25
25
|
if (!isControlled)
|
|
26
26
|
setInternalChecked(newChecked);
|