Skip to content

Commit 4de0889

Browse files
committed
chore(api): add demo/create_window.mts
1 parent e6b7389 commit 4de0889

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env tsx
2+
/* eslint-disable no-bitwise */
3+
/* eslint-disable import/no-extraneous-dependencies */
4+
/**
5+
* Create a window and receive events
6+
* window closed after 30sec if event triggered
7+
*
8+
* Run @CLI and then move mouse in the area of the window
9+
*
10+
* @CLI `ts-node -P tsconfig.cjs.json demo/create_window.ts`
11+
* @author waiting
12+
* @link https://github.com/waitingsong/node-win32-api
13+
*/
14+
import assert from 'assert'
15+
16+
import { sleep } from '@waiting/shared-core'
17+
import { ffi, User32, Kernel32 } from 'win32-api'
18+
import { CW_USEDEFAULT, WS_CAPTION, WS_SYSMENU } from 'win32-def/consts'
19+
import * as D from 'win32-def/def'
20+
import * as S from 'win32-def/struct'
21+
import type * as T from 'win32-def/types'
22+
23+
24+
const user32 = User32.load()
25+
const libKnl = Kernel32.load()
26+
assert(libKnl)
27+
const hModule = libKnl.GetModuleHandleW(null)
28+
assert(hModule)
29+
30+
31+
const title = 'Node.js WinForms App'
32+
const proc = ffi.proto('WndProc', D.LRESULT, [D.HWND, D.UINT, D.WPARAM, D.LPARAM])
33+
const WndProc = ffi.register(jsCallback, ffi.pointer(proc))
34+
35+
try {
36+
const { clsAtom, winHwnd } = await createWindow(title)
37+
await sleep(1500)
38+
39+
setTitle(winHwnd, `${title} - updated`)
40+
await sleep(1500)
41+
user32.UnregisterClassW(clsAtom, 0)
42+
}
43+
finally {
44+
ffi.unregister(WndProc)
45+
console.info('finish')
46+
}
47+
48+
49+
50+
async function createWindow(winTitle: string): Promise<{ winHwnd: T.HWND, clsAtom: T.ATOM }> {
51+
const windowName = winTitle
52+
const className = 'NodeClass'
53+
54+
// Common Controls
55+
const { payload: icc } = S.INITCOMMONCONTROLSEX_Factory()
56+
icc.dwICC = 0x40ff
57+
58+
const { payload: wClass } = S.WNDCLASSEXW_Factory()
59+
wClass.lpszClassName = className
60+
wClass.lpfnWndProc = WndProc
61+
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor
62+
wClass.hbrBackground = 13
63+
64+
const atom = await user32.RegisterClassExW_Async(wClass)
65+
assert(atom, 'RegisterClassExW failed')
66+
67+
const dStyle = WS_CAPTION | WS_SYSMENU
68+
try {
69+
const hWnd = user32.CreateWindowExW(
70+
0,
71+
className,
72+
windowName,
73+
dStyle, // overlapped window
74+
CW_USEDEFAULT,
75+
CW_USEDEFAULT,
76+
600,
77+
400,
78+
0,
79+
0,
80+
0,
81+
0,
82+
)
83+
assert(hWnd)
84+
85+
// Don't use _Async
86+
user32.ShowWindow(hWnd, 1)
87+
user32.UpdateWindow(hWnd)
88+
89+
return { winHwnd: hWnd, clsAtom: atom }
90+
}
91+
catch {
92+
user32.UnregisterClassW(atom, hModule)
93+
ffi.unregister(WndProc)
94+
}
95+
96+
throw new Error('CreateWindowExW failed')
97+
}
98+
99+
100+
function setTitle(appHwnd: T.HWND, newTitle: string): void {
101+
// Don't use SetWindowTextW_Async
102+
const res = user32.SetWindowTextW(appHwnd, newTitle)
103+
assert(res, 'SetWindowTextW failed')
104+
}
105+
106+
107+
function jsCallback(hwnd: T.HWND, uMsg: T.UINT, wParam: T.WPARAM, lParam: T.LPARAM): T.LRESULT {
108+
console.info('WndProc callback: ', { uMsg, wParam, lParam })
109+
let result = 0
110+
switch (uMsg) {
111+
default:
112+
result = user32.DefWindowProcW(hwnd, uMsg, wParam, lParam)
113+
break
114+
}
115+
console.info(`Sending LRESULT: ${result}\n`)
116+
return result
117+
}

0 commit comments

Comments
 (0)