Skip to content

Implement standard controls and dialogs in win32k - #15

Merged
AdvDebug merged 2 commits into
mainfrom
win32k-standard
Aug 26, 2026
Merged

Implement standard controls and dialogs in win32k#15
AdvDebug merged 2 commits into
mainfrom
win32k-standard

Conversation

@AdvDebug

Copy link
Copy Markdown
Owner

Brovan runs the real user32, so supporting the standard controls means supplying what win32k owes it rather than writing the controls. Button, Edit, Static, ListBox, ComboBox and ScrollBar now create, answer their own messages and send real WM_COMMAND notifications to their parent. Most of that was client-side state user32 reads without a syscall, the system class atoms, the per-DPI character dimensions dialog units convert with, the system colours and their brushes, and the per-class message tables a control checks before running its worker. While those tables read zero every message went straight to DefWindowProc, which is why nothing ever initialized.

Window procedures go through user32's kernel callback table now, so NtUserCreateWindowEx sends WM_NCCREATE and WM_CREATE and carries the creation on to WM_SIZE and WM_MOVE, and NtUserMessageCall delivers a real send to the window procedure instead of answering it as DefWindowProc. Painting picks up the DC viewport origin and shifts every primitive by the window's origin inside its top level ancestor, since they all land on one host surface.

Also bounds cbWndExtra at class registration, recycles user handle indexes and releases the window object when a window is destroyed, and keeps the internal visible bit out of what GWL_EXSTYLE stores.

Brovan runs the real user32, so supporting the standard controls means supplying what
win32k owes it rather than writing the controls. Button, Edit, Static, ListBox, ComboBox
and ScrollBar now create, answer their own messages and send real WM_COMMAND notifications
to their parent. Most of that was client-side state user32 reads without a syscall, the
system class atoms, the per-DPI character dimensions dialog units convert with, the system
colours and their brushes, and the per-class message tables a control checks before running
its worker. While those tables read zero every message went straight to DefWindowProc,
which is why nothing ever initialized.

Window procedures go through user32's kernel callback table now, so NtUserCreateWindowEx
sends WM_NCCREATE and WM_CREATE and carries the creation on to WM_SIZE and WM_MOVE, and
NtUserMessageCall delivers a real send to the window procedure instead of answering it as
DefWindowProc. Painting picks up the DC viewport origin and shifts every primitive by the
window's origin inside its top level ancestor, since they all land on one host surface.

Also bounds cbWndExtra at class registration, recycles user handle indexes and releases the
window object when a window is destroyed, and keeps the internal visible bit out of what
GWL_EXSTYLE stores.
@AdvDebug

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Aug 26, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

@AdvDebug

Copy link
Copy Markdown
Owner Author

@claude review

@claude

claude Bot commented Aug 26, 2026

Copy link
Copy Markdown

Claude finished @AdvDebug's task in 4m 8s —— View job


Review: Implement standard controls and dialogs in win32k

  • Gather context (PR description, changed files, diff)
  • Read repo guidelines (no CLAUDE.md present)
  • Review Win32kHelper.cs / WinSyscallsHelper.cs core logic
  • Review new NtUser*/NtGdi* syscall handlers
  • Review window creation / message dispatch / painting changes
  • Post final review

This is a large, well-organized piece of work — routing window procedures through the real kernel callback table, driving WM_NCCREATEWM_CREATEWM_SIZEWM_MOVE through a state machine (ContinueWindowCreation in WinSyscallsHelper.cs:3244), and shifting every GDI primitive by the window's surface origin so drawing lands correctly on the shared host surface. The comments throughout explain why non-obvious offsets/behaviors exist, which is very helpful for a codebase like this.

Findings

32-bit guests silently lose the whole creation handshakeWin32kHelper.InvokeWindowProc/TryBeginWindowProcCallback (Win32kHelper.cs:1490) bail out whenever PointerSize != 8. In NtUserCreateWindowEx.cs:97-101, if SendWindowCreateMessage returns false (which it always will for a 32-bit process), the code just falls through to Instance.SetRawSyscallReturn(hwnd) — the window is created but never receives WM_NCCREATE/WM_CREATE/WM_SIZE/WM_MOVE. Since the PR description states this exact gap ("every message went straight to DefWindowProc, which is why nothing ever initialized") is the core problem being fixed, standard controls hosted in a 32-bit (WOW64) guest process would still be silently broken with no error surfaced. If 32-bit guests are in scope, this is worth a follow-up; if x64-only is an accepted current limitation, it'd help to note that in the PR description or a code comment.

NtUserSetScrollInfo.cs:59Highest is computed as (long)Info.Maximum - (Info.Page - 1) when Info.Page > 1. Info.Page is uint and Info.Maximum is int; the subtraction promotes to long correctly here (good), but note Info.Page itself is never clamped against the range (Maximum - Minimum), so a huge page value just collapses Highest down to Minimum via the clamp below it — that appears intentional and matches real SetScrollInfo semantics, just flagging that I didn't see an explicit test for the Page == 0 degenerate case (falls into Info.Page > 1 being false, so Highest = Info.Maximum, which is correct).

Minor / nit-level observations

  • Win32kHelper.csWinWindowCreationStep/SyscallRetryRip plumbing in EnterUserCallback/ContinueWindowCreation is a bit intricate (restoring RAX/R10/RDX/R8/R9 and re-entering the syscall instruction). It's currently only exercised by NtUserUpdateWindow.cs. Worth a short comment at the SyscallRetryRip field itself (WinThreading.cs:73) pointing at that caller, since it's not obvious from the frame class alone that it's single-purpose today.
  • NtGdiSetBoundsRect.cs:37Previous == 0 ? DcbDisable : Previous duplicates a normalization that Win32kHelper.TrySetDcBounds already performs internally (Previous = Dc.BoundsFlags == 0 ? DcbDisable : Dc.BoundsFlags;), so Previous returned from TrySetDcBounds is never actually 0 by the time it reaches the syscall handler. Harmless, but the extra check is dead code.
  • RegisterWindowClass (WinSyscallsHelper.cs) — TryReserveSystemClassAtom only reserves the well-known atom if it isn't already claimed by another registered class; if a system class (e.g. "Button") is somehow re-registered under a different key (different instance handle/version) after the first registration claimed the reserved atom, the second registration silently falls through to an auto-assigned atom instead of failing loudly. Likely a non-issue in practice since real user32 registers each system class exactly once, but worth being aware of if class re-registration ever becomes reachable from guest code.

Not verified

I didn't build/run the project (no build/test instructions were available in this environment), so this review is based on static reading of the diff against origin/main. The state-machine logic for window creation, message routing, and coordinate translation all read as internally consistent, but real emulator behavior (timing of callback re-entry, register save/restore correctness under Unicorn) would benefit from an actual run against a guest binary that creates standard controls.

@AdvDebug

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 0e1925128b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@AdvDebug
AdvDebug merged commit 8ca9ac0 into main Aug 26, 2026
2 checks passed
@AdvDebug
AdvDebug deleted the win32k-standard branch August 26, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant