Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change
;;
GetWindowPos v 1.2
Retrieves the position and size of a window, adjusted for DPI scaling, in physical pixels.
Returns coordinates relative to the parent's client area (for child windows) or screen/owner
coordinates (for non-child windows), with border adjustments for child windows. Compatible
with Windows XP, 7, and later, using system DPI for scaling.
Arguments:
hWnd (in): Handle to the window (child, top-level, dialog, control, etc.).
PosStruct (out): Pointer to a WINPOS structure to receive the position and size.
The output values of WINPOS (cx, cy, width, height) are in logical pixels.
Return Value:
For child windows: Returns the parent window handle (hParent) on success.
For non-child windows: Returns TRUE (1) on success.
Returns 0 on failure (invalid hWnd, PosStruct, or system errors).
WINPOS Structure:
[WINPOS:
WINPOS.cx: D$ 0 ; x-coordinate of the window's upper-left corner (pixels). The value can also be negative. See Remarks
WINPOS.cy: D$ 0 ; y-coordinate of the window's upper-left corner (pixels). The value can also be negative. See Remarks
WINPOS.width: D$ 0 ; Width of the window (pixels).
WINPOS.height: D$ 0] ; Height of the window (pixels).
- Child windows: cx, cy are relative to the parent's client area, adjusted for borders;
width, height are client dimensions.
- Non-child windows: cx, cy are in screen coordinates (hParent = NULL) or owner's
client coordinates (hParent = owner), no border adjustment; width, height include borders.
Remarks:
- Enhanced version of GetWindowPos to handle child and non-child windows with DPI awareness.
- Uses GetClientRect for child windows, GetWindowRect for non-child windows.
- Converts coordinates to parent's client area (child) or screen/owner (non-child) using MapWindowPoints.
- Adjusts child window coordinates for borders using WINDOWINFO.cxWindowBorders, cyWindowBorders.
- All coordinates and borders are in logical pixels before SSE2 scaling.
- Scales coordinates and sizes to physical pixels using system DPI from GetDeviceCaps,
ensuring compatibility with Windows XP and 7 (no GetDpiForWindow or manifest required).
- Uses SSE2 to vectorize DPI scaling (ratio = Dpi / 96) for performance, with rounding to nearest.
- Borders are subtracted in logical pixels before scaling; SSE2 scaling (mulps) applies DPI ratio
to adjusted coordinates (e.g., cx = 10 - 2 = 8, then 8 * 1.5 = 12), correctly accounting for
border's effect in physical pixels (e.g., 2 * 1.5 = 3).
- No separate border scaling is needed, as WINPOS and borders are both in logical pixels.
- Assumes System DPI Awareness (default without manifest), using primary monitor's DPI.
- Does not support per-monitor DPI (unavailable on XP/7); all windows use system DPI.
- Uses LOGPIXELSX for DPI scaling, as X and Y DPI are equal in standard Windows configurations.
- Color depth experiments (e.g., (16M >> 8) & 511 = 36) are unrelated to DPI; GetDeviceCaps provides accurate DPI.
- Validates window handles, thread desktop, and default desktop for robustness.
- Preserves ECX and EDX registers as per calling convention.
- Uses PosStruct directly for rectangle operations, eliminating temporary buffers.
- Returns physical pixels for cx, cy, width, height, suitable for precise rendering or positioning.
- Fallback to no scaling (Dpi = 96) if DPI retrieval fails.
Usage Example:
[WINPOS:
WINPOS.cx: D$ 0
WINPOS.cy: D$ 0
WINPOS.width: D$ 0
WINPOS.height: D$ 0]
call 'USER32.GetDlgItem' D@hDlg, 156 ; Get control handle
call GetWindowPosEx eax, WINPOS
If eax = 0
; Handle failure
Else_If eax = 1
; Non-child window, use WINPOS
Else
; Child window, eax = hParent, use WINPOS and parent
End_If
Notes:
Negative values for cx and cy - Why and When cx and cy Can Be Negative ?
The cx and cy values in the WINPOS structure come from the left and top fields of the temporary Rect (a WINPOS structure)
after MapWindowPoints transforms coordinates. Negative values arise when the window's top-left corner is positioned
to the left or above the origin (0,0) of the target coordinate system. The target coordinate system depends on the window type:
* Child windows: Parent's client coordinates (origin at the top-left of the parent's client area).
" Non-child windows: Screen coordinates (hParent = NULL) or owner's client coordinates (hParent = owner).
Here are some specific situations for each case, including whether dragging a window off the desktop contributes.
1. Child Windows
* How MapWindowPoints Works:
* GetClientRect(hWnd, @Rect) sets @Rect to {left: 0, top: 0, right: client_width, bottom: client_height}
(client coordinates of hWnd).
* MapWindowPoints(hWnd, hParent, @Rect, 2) converts the points (left, top) and (right, bottom) from
hWnd's client coordinates to hParent's client coordinates.
* The parent's client area has its origin (0,0) at the top-left of its client area (excluding the parent's
borders, title bar, etc.).
* If hWnd's client area is positioned left or above the parent's client area origin, left (cx) or top (cy) become negative.
* Situations:
* Negative Positioning in Dialogs: In dialog-based applications, a child control (e.g., an edit box) can
be placed at a negative position relative to the dialog's client area. This is common in dialog templates
or dynamic layouts where controls are offset for visual effects or alignment.
* Example: A dialog template (in a .rc file) defines an edit control at
CONTROL "Edit", IDC_EDIT, "EDIT", WS_CHILD, -10, -5, 100, 20.
* The edit control's client area top-left is at (-10, -5) in the dialog's client coordinates.
* After MapWindowPoints, @Rect.left = -10, @Rect.top = -5, so cx = -10, cy = -5.
* Border adjustment (e.g., cxWindowBorders = 2) makes it more negative: cx = -10 - 2 = -12.
* Dynamic Positioning:
If code programmatically moves a child window using SetWindowPos(hWnd, NULL, -10, -5, width, height, SWP_NOZORDER),
the child's position becomes negative relative to the parent's client area.
* Parent Scrolling: In a scrollable parent (e.g., a dialog with a scrollable client area), a child control's
position may appear negative when the parent is scrolled, shifting the child's relative position above or
left of the visible client area origin.
* Dragging Off Desktop?:
* Not Applicable:
Child windows (e.g., controls in a dialog) cannot be dragged by the user, as they are
fixed within the parent's client area. Negative cx, cy for child windows result from deliberate positioning
(via dialog templates or SetWindowPos), not user interaction like dragging.
* Visualization:
* Imagine a dialog's client area as a canvas starting at (0,0). If an edit control is placed at (-10, -5),
its top-left corner is outside the dialog's client area (to the left and above). This is valid in Windows
and results in negative cx, cy.
2. Non-Child Windows
" How MapWindowPoints Works:
* GetWindowRect(hWnd, @Rect) sets @Rect to {left, top, right, bottom} in screen coordinates (entire window, including borders).
* MapWindowPoints(0, hParent, @Rect, 2) converts these screen coordinates to:
" Screen coordinates (if hParent = NULL): No change, but negative values are possible from GetWindowRect.
" Owner's client coordinates (if hParent is an owner window).
Conclusion
* Can cx, cy Be Negative?: Yes, for both child and non-child windows.
* Situations:
* Child Windows: Negative positions in dialog templates, programmatic SetWindowPos to negative coordinates,
or scrollable parent offsets. Not from dragging, as child windows are fixed.
* Non-Child Windows: Multi-monitor setups (secondary monitor with negative coordinates), popups above/left of an
owner's client area, or dragging/programmatic positioning off the primary monitor's left/top edge.
Dragging off desktop is a valid case for top-level windows.
Author: Gustavo Trigueiros (aka: Beyond2000!/Guga)
Build Date: May 2012 (v1.0), Updated April 2025 (v1.4)
;;
[DpiAdjust: F$ (1/96)]
Proc GetWindowPos::
Arguments @hWnd, @PosStruct
Local @hParent, @IsChild, @Dpi, @hDC, @AdjustForDpi, @Return
Structure @WINDOWINFO 96, @WINDOWINFO.cbSizeDis 0, @WINDOWINFO.rcWindow_leftDis 4, @WINDOWINFO.rcWindow_topDis 8,
@WINDOWINFO.rcWindow_rightDis 12, @WINDOWINFO.rcWindow_bottomDis 16, @WINDOWINFO.rcClient_leftDis 20,
@WINDOWINFO.rcClient_topDis 24, @WINDOWINFO.rcClient_rightDis 28, @WINDOWINFO.rcClient_bottomDis 32,
@WINDOWINFO.dwStyleDis 36, @WINDOWINFO.dwExStyleDis 40, @WINDOWINFO.dwWindowStatusDis 44, @WINDOWINFO.cxWindowBordersDis 48,
@WINDOWINFO.cyWindowBordersDis 52, @WINDOWINFO.atomWindowTypeDis 56, @WINDOWINFO.wCreatorVersionDis 58,
@XMMReg0Dis 62, @XMMReg1Dis 78
Uses ecx, edx
; Preserve XMM registers efficiently
movdqu X@XMMReg0Dis XMM0
movdqu X@XMMReg1Dis XMM1
xor eax eax
On D@hWnd = 0, ExitP
On D@PosStruct = 0, ExitP
; Validate thread's desktop
Call 'KERNEL32.GetCurrentThreadId'
Call 'USER32.GetThreadDesktop' eax
On eax = 0, ExitP
; Validate default desktop window
Call 'USER32.GetDesktopWindow'
On eax = 0, ExitP
; Validate window handle
Call 'USER32.IsWindow' D@hWnd
On eax = 0, ExitP
; Get parent or owner window
Call 'USER32.GetAncestor' D@hWnd, &GA_PARENT
mov D@hParent eax
; Validate parent handle (NULL is valid for top-level windows)
If D@hParent <> 0
Call 'USER32.IsWindow' D@hParent
On eax = 0, ExitP
End_If
; Get system DPI for scaling
; Use GetDeviceCaps on primary monitor's DC (hDC = NULL) to get LOGPIXELSX
; Default to 96 (100% scaling) if retrieval fails
mov D@Dpi 96
Call 'user32.GetDC' &NULL
mov D@hDC eax
If eax <> 0
Call 'GDI32.GetDeviceCaps' D@hDC, &LOGPIXELSX ; No need to calculate for LOGPIXELSY, since they are basically the same
If eax <> 0
mov D@Dpi eax
End_If
call 'user32.ReleaseDC' &NULL, D@hDC
End_If
; get a ratio for be fast. x*dpi/96 = x*Ratio, where ratio = Dpi/96
mov D@AdjustForDpi &FALSE
If D@dpi <> 96 ; If dpi is not 96, then we need to compute a ratio
movss xmm0 X$DpiAdjust
cvtsi2ss xmm1 D@Dpi ; converts a signed integer to double
mulss xmm1 xmm0 | pshufd xmm1 xmm1 SSE_FILL_DWORDS ; our ratio is now in the 4 dwords of the xmm1 register
mov D@AdjustForDpi &TRUE
End_If
; Check if this is a child window
call IsChildForWindowPos D@hParent, D@hWnd
mov D@IsChild eax
If eax = 0
; Non-child window: Get window rect and convert to parent/owner coords. Since we use the the same structure we need to correct the size here
call 'USER32.GetWindowRect' D@hWnd, D@PosStruct
mov ecx D@PosStruct
mov eax D$ecx+WINPOS.widthDis | sub eax D$ecx+WINPOS.cxDis | mov D$ecx+WINPOS.widthDis eax ; width = right - left
mov eax D$ecx+WINPOS.heightDis | sub eax D$ecx+WINPOS.cyDis | mov D$ecx+WINPOS.heightDis eax ; height = bottom - top
; and now we an pass to MapWindowPoints get cx and cy - which can be negative
call 'USER32.MapWindowPoints' 0, D@hParent, D@PosStruct, 2 ; values will be converted to logical pixels
Else
; Child window: Get client rect and convert to parent coords
call 'USER32.GetClientRect' D@hWnd, D@PosStruct
mov ecx D@PosStruct
mov eax D$ecx+WINPOS.widthDis | sub eax D$ecx+WINPOS.cxDis | mov D$ecx+WINPOS.widthDis eax ; width = right - left
mov eax D$ecx+WINPOS.heightDis | sub eax D$ecx+WINPOS.cyDis | mov D$ecx+WINPOS.heightDis eax ; height = bottom - top
; and now we an pass to MapWindowPoints get cx and cy - which can be negative. values will be converted to logical pixels
call 'USER32.MapWindowPoints' D@hWnd, D@hParent, D@PosStruct, 2
End_If
; Adjust for borders only for child windows
If D@IsChild = &TRUE
call 'RosMem.FastZeroMem' D@WINDOWINFO, 64 ; Size_of_WINDOWINFO is, in fact 64 bytes nd not 60
mov D@WINDOWINFO.cbSizeDis 64
call 'USER32.GetWindowInfo' D@hWnd, D@WINDOWINFO ; the information retrieved is also in logical pixels
mov ecx D@PosStruct
mov eax D@WINDOWINFO.cxWindowBordersDis | sub D$ecx+WINPOS.cxDis eax ; adjust X for borders
mov eax D@WINDOWINFO.cyWindowBordersDis | sub D$ecx+WINPOS.cyDis eax ; adjust Y for borders
mov eax D@hParent ; Also sucess, but we return the hParent, instead of &TRUE. So you can kow that hwnd is a child windows
Else
mov eax &TRUE ; On sucess, and if hWnd is a window, returns &TRUE
End_If
mov D@Return eax
If D@AdjustForDpi = &TRUE
mov ecx D@PosStruct
movdqu xmm0 X$ecx | SSE_CONV_4INT_TO_4FLOAT xmm0 | mulps xmm0 xmm1 | SSE_CONV_4FLOAT_TO_4INT_ROUND xmm0 | movdqu X$ecx xmm0
End_If
mov eax D@Return
; Restore XMM registers
movdqu XMM0 X@XMMReg0Dis
movdqu XMM1 X@XMMReg1Dis
EndP
;;
IsChildForWindowPos
Specialized version of IsChild for use with GetWindowPosEx. Determines whether a window
is a child or descendant of a specified parent window, assuming input validation is done
by the caller.
Arguments:
hWndParent (in): Handle to the potential parent window (may be NULL for top-level windows).
hWnd (in): Handle to the window to test (assumed valid).
Return Value:
Returns TRUE (1) if hWnd is a child or descendant of hWndParent.
Returns FALSE (0) if:
- hWndParent is NULL or invalid.
- hWnd is not a child window (lacks WS_CHILD style or has WS_POPUP).
- The parent chain ends without reaching hWndParent.
Remarks:
- Optimized for GetWindowPosEx, which validates hWnd, hWndParent, and desktop context.
- Skips redundant IsWindow, GetThreadDesktop, and GetDesktopWindow checks.
- Traverses the parent chain using GetParent until hWndParent is found or a non-child
window is encountered.
- Preserves ECX and EDX registers.
- Assumes hWnd is valid (checked by GetWindowPosEx) and hWndParent is either valid,
NULL, or invalid (checked before traversal).
Author: Gustavo Trigueiros (aka: Beyond2000!)
Build Date: April 2025 (v1.0)
;;
Proc IsChildForWindowPos:
Arguments @hWndParent, @hWnd
Local @CurrentWnd, @Style
Uses ecx, edx
; Initialize return value
xor eax eax
On D@hWndParent = 0, ExitP
; Initialize CurrentWnd = hWnd
mov eax D@hWnd | mov D@CurrentWnd eax
; Traverse parent chain
.While D@CurrentWnd <> 0
; Get window style
call 'USER32.GetWindowLongA' D@CurrentWnd, &GWL_STYLE
; Check if window is a child: (style & (WS_POPUP | WS_CHILD)) == WS_CHILD
and eax (&WS_POPUP+&WS_CHILD) ; Bitwise OR for mask
.If eax = &WS_CHILD
; Get parent window
Call 'USER32.GetParent' D@CurrentWnd
mov D@CurrentWnd eax
; Check if parent is NULL
On eax = 0, ExitP
; Check if parent matches hWndParent
If eax = D@hWndParent
mov eax &TRUE
ExitP
End_If
.Else
; Not a child window, stop traversal
xor eax eax
ExitP
.End_If
.End_While
; Default return FALSE
xor eax eax
EndP
[SSE_FILL_DWORDS 0] ; Fill all 4 Dwords in a xmm register with the value of the 1st Dword. So, we are copying the 1st Dword to all others 3
; Ex: mov al 053 | imul eax eax 01010101 | movd xmm1 ax | pshufd xmm1 xmm1 SSE_FILL_DWORDS ; the contents of each one of all 8 words in xmm1 will be 053
[pshufd | pshufd #1 #2 #3]
[SSE_ABS_REAL4 | pslld #1 1 | psrld #1 1] ; pslld xmm1 1 | psrld xmm1 1
[SSE_CONV_4INT_TO_4FLOAT | cvtdq2ps #1 #1] ; pslld xmm1 1 | psrld xmm1 1 cvtdq2ps xmm1 xmm1
[SSE_FIND_REAL4_MAX | pshufd xmm7 #1 SSE_SWAP_DWORDS | maxps #1 xmm7 | pshufd xmm7 #1 SSE_INVERT_DWORDS | maxps #1 xmm7]
[SSE_CONV_4FLOAT_TO_4INT | cvttps2dq #1 #1]; Convert four floats to integers (truncate)
[SSE_CONV_4FLOAT_TO_4INT_ROUND | cvtps2dq #1 #1] ; Convert four floats to integers (round to nearest)
Quote from: jejump on April 23, 2025, 01:11:22 AMBasically, the wait loop is waiting on either signals from the parallel port, or the Esc key from the keyboard to exit the loop. Just before this wait loop is entered, the keyboard interrupt mask is applied on port 21h, bit 1, so that no keyboard interrupt is processed.
KBS_Service:
pushf
push AX
push DX
push ES
in AL, 20h
test AL, 2
jz KBS_End
in AL, 60h
cmp AL, 1
in al, 64h
test al, 01h ; input buffer full?
jz nokey
mov ah, al
in al, 60h
test ah, 20h ; is it input from PS/2 mouse?
jnz nokeyps2
mov AL,20h ;Clear interrupt controller
out 20h,AL
out 0Ah,AL
Quote"Not sure what exactly you're doing by reading the interrupt controller (port 20h) before looking for a keystroke: what's the logic here? Are you first looking for a pending interrupt, as if a key had been pressed? That might be the problem, or at least part of it."
Quote from: jejump on April 23, 2025, 01:11:22 AM"Just before this wait loop is entered, the keyboard interrupt mask is applied on port 21h, bit 1, so that no keyboard interrupt is processed. It gets re-enabled later on."