[PATCH v4 0/10] MR10180: Desktop Window Manager (DWM) Overlay and Composition Support
## Overview This patchset introduces support for Desktop Window Manager (DWM) APIs—specifically `DwmExtendFrameIntoClientArea` in `dwmapi` for region-based cutouts and glass effects. It patches the `dwmapi` stub and resolves issues with transparency, input routing, and Z-order management for cross-process Windows overlays on Linux for both X11 and Wayland. This project originated as a side project and direct solution to a long-standing issue with the dwmapi stub. It was a steep learning curve and a significant step forward from basic compositor tweaks. While I have invested significant effort into adhering to Wine's architectural standards and documenting my findings, my knowledge of Wine internals and general graphics development is limited. Some patches required a middle-ground compromise due to architectural limitations in Wayland. I am submitting this as a working, well-documented proof-of-concept and as a handover for further review, refinement, and hopefully integration by more experienced developers. This proposal introduces a best-effort attempt at an upstream-compliant implementation to bridge Windows composition syscalls (`dwmapi.dll`) directly to native Linux display protocols. Historically, users have relied on external bash scripts, custom window manager rules, external `.dll` injections, or compositing workarounds (like pixel shaders to remove black backgrounds) to achieve partial functionality. The goal of this patchset is to provide native support for these DWM features, eliminating the need for external interventions. ## Key Features Implemented * **Core Infrastructure:** Plumbing for `NtUserSetWindowDwmConfig` from `dwmapi` down to the user drivers. * **X11 Driver:** Implemented dynamic 32-bit ARGB visual upgrades upon DWM Glass requests and XShape region-based cutout features. * Mapped `WS_EX_TRANSPARENT` to empty `XShape` masks for click-through input routing. * Resolve Z-Order priority via `_NET_WM_STATE_ABOVE`. * **Wayland Driver:** Implemented a dual-path DWM masking strategy (CPU Alpha Matrix for GDI layers, `glScissor` for OpenGL hardware subsurfaces) alongside ARGB late-binding. * Synchronized physical DWM cutout margins and empty input regions across `wl_surface` hierarchies. * Implemented `zxdg_foreign_v2` to share surface handles via 16-bit Win32 Global Atoms, delegating Win32 overlay Z-order management to the Wayland compositor. * Added Z-order sniffing and geometry preservation to prevent compositor crashes during rapid Win32 `SW_HIDE`/`SW_SHOW` toggles. * **Vulkan/win32u:** Added native swapchain invalidation (`VK_ERROR_OUT_OF_DATE_KHR`) via an asynchronous driver callback to support applications that late-bind DWM transparency after initial Vulkan setup. ## Testing & Reference Documentation I have comprehensively documented the entire project, detailing the architecture, implementations, and alternatives, along with build, regression, and project-specific test tools in a centralized custom repository. I highly recommend checking out my supplementary repository for the project - [dwm-overlays](https://gitlab.com/namoninja/wine-custom/-/tree/main/projects/dwm-overlays) - and reading the [`README.md`](https://gitlab.com/namoninja/wine-custom/-/blob/main/projects/dwm-overlays/R...) for full technical details and scope. Tested successfully with: * **DWM Tester (Internal tool):** Validated against a matrix of GDI, OpenGL, region cutouts, glass effects, input routing, and focus. Tested for regressions. * **GW2TacO (C++):** Validated legacy GDI fallback path, transparency, Z-order, WH_MOUSELL hooks, and aggressive `SetWindowPos` calls. * **Blish HUD (.NET/C#):** Validated DXVK/Vulkan path, late ARGB binding, input, Z-order, and Vulkan/DXVK swapchain recreation. These overlays served as optimal test candidates because they utilize aggressive methodologies, such as high frequency `SetWindowPos` calls and omitting `WS_EX_LAYERED`, `WS_EX_TOPMOST`, or `GW_OWNER` during initialization. While Wine implementation is not supposed to accommodate workarounds for non-standard app behavior, my objective stance on this is basic compatibility: if it functions natively on Windows, it should function under Wine. Bridging this expectation to Wayland's strict surface isolation protocols along with Wine's upstream standards, presented a significant challenge. ## Patch Sequence Here is a summary of the implementations along with a (subjective) assessment of how well aligned the concepts are with upstream. Deviations were introduced only to navigate limitations and are isolated to `DwmExtendFrameIntoClientArea` upgrade requests to prevent side effects during standard operations. 1. `win32u`: Add NtUserSetWindowDwmConfig syscall. 1. **:green_square: Clean/standard -** Standard Win32 syscall routing through `gdi_driver`. Adheres to Wine's architecture. 2. `winex11.drv`: Implement SetWindowDwmConfig for opaque regions. 1. **:green_square: Clean/standard** - Utilizes standard `XShape` protocol logic to manage bounding and input regions. 3. `winex11.drv`: Support ARGB visuals for DWM glass effect. 1. **:yellow_square: Pragmatic/deviation -** Dynamic visual upgrades during runtime deviate from static X11 visual assumptions but are implemented safely without spoofing. 4. `winex11.drv`: Implement click-through input routing for transparent windows. 1. **:green_square: Clean/standard -** Proper application of `XShape` empty input masking to achieve `WS_EX_TRANSPARENT` behavior. 5. `winex11.drv`: Resolve overlay Z-Order priority via \_NET_WM_STATE_ABOVE. 1. **:yellow_square: Pragmatic/deviation -** Forces `_NET_WM_STATE_ABOVE` for DWM glass windows to bypass X11 Window Manager heuristics, which frequently silently drop late `SetWindowPos(HWND_TOPMOST)` requests from unfocused or input-transparent windows. 6. `winewayland.drv`: Implement ARGB late-binding and DWM extended frame support. 1. **:yellow_square: Pragmatic/deviation -** CPU matrixing is inefficient but necessary; Wayland's source-over model physically prevents GDI from cropping OpenGL layers, mandating the `glScissor` intervention. 7. `winewayland.drv`: Implement wl_surface input regions for transparency and DWM. 1. **:green_square: Clean/standard -** Synchronizes physical cutout margins and empty input regions across both the toplevel and OpenGL subsurface layers. 8. `winewayland.drv`: Implement cross-process window hierarchy via zxdg_foreign_v2. 1. **:green_square: Clean/standard -** Memory-safe implementation of Wayland protocols for cross-process surface linking. 9. `winewayland.drv`: Stabilize cross-process overlay lifecycles and parenting. 1. **:yellow_square: Pragmatic/deviation -** Heuristic sniffing bridges the gap for Win32 overlays lacking explicit parents. Latch logic mitigates cross-process mapping race conditions. 10. `win32u`: Implement dynamic ARGB Vulkan swapchain recreation. 1. **:yellow_square: Pragmatic/deviation -** Relies entirely on native Vulkan invalidation. Eliminates `NtUserGetProp` IPC blocking and generic format spoofing. Injects VK_ERROR_OUT_OF_DATE_KHR during image acquisition to force client frameworks (e.g., DXVK) to reconstruct swapchains, allowing the injection of PRE_MULTIPLIED alpha configurations for late-bound transparency. ## Final Comments Should this patchset not align with upstream goals, I am happy for it to be evaluated for `wine-staging`, however, I was unable to find a way to request a PR in that repository. The full patchset is available in my custom repository. I am aware there may be areas that still require refinement. While I attempted to maintain atomic commits and follow correct procedures, I am relatively new to the complexity of a project of this scale and to Git collaboration in general, so I hope my rebase is clean and merge request correct. I have tested and patched any regressions I could find, but I could have missed some. I could only test this to the limitations of my own time and hardware, and not as in-depth as the wider community can. I would have preferred to refine this further and expand on other features, but due to time constraints, this is as far as I can take the implementation. I present it as a handover, or at the minimum, as a technical blueprint for future developers. Thank you to all the Wine developers for maintaining such an incredible project and for the opportunity to learn and contribute. -- v4: win32u: Implement dynamic ARGB Vulkan swapchain recreation. winewayland.drv: Stabilize cross-process overlay lifecycles and parenting. winewayland.drv: Implement cross-process window hierarchy via zxdg_foreign_v2. winewayland.drv: Implement wl_surface input regions for transparency and DWM. winewayland.drv: Implement ARGB late-binding and DWM extended frame support. https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Introduce a new system call, NtUserSetWindowDwmConfig, to pass Desktop Window Manager (DWM) configuration data from dwmapi down to the user driver. This infrastructure allows passing frame extension margins to the driver, supporting custom opaque region insets (for client-side decorations) and full-window transparency requested via DwmExtendFrameIntoClientArea. - Define NtUserSetWindowDwmConfig in win32u, ntuser.h, and user32.spec. - Add DWM_CONFIG_OPAQUE_REGION flag in winuser.h. - Update dwmapi to import win32u and utilize the new syscall. - Expand user_driver_funcs in gdi_driver.h with pSetWindowDwmConfig. - Implement WOW64 thunk in dlls/wow64win/user.c. --- dlls/dwmapi/Makefile.in | 2 +- dlls/dwmapi/dwmapi_main.c | 11 +- dlls/user32/user32.spec | 1 + dlls/win32u/driver.c | 6 + dlls/win32u/main.c | 5 + dlls/win32u/win32syscalls.h | 402 ++++++++++++++++++------------------ dlls/win32u/win32u.spec | 1 + dlls/win32u/window.c | 15 ++ dlls/wow64win/user.c | 9 + include/ntuser.h | 1 + include/wine/gdi_driver.h | 1 + include/winuser.h | 3 + 12 files changed, 254 insertions(+), 203 deletions(-) diff --git a/dlls/dwmapi/Makefile.in b/dlls/dwmapi/Makefile.in index cf889cb148e..f837e58b439 100644 --- a/dlls/dwmapi/Makefile.in +++ b/dlls/dwmapi/Makefile.in @@ -1,5 +1,5 @@ MODULE = dwmapi.dll -IMPORTS = user32 +IMPORTS = user32 win32u IMPORTLIB = dwmapi VER_FILEDESCRIPTION_STR = "Desktop Window Manager API" diff --git a/dlls/dwmapi/dwmapi_main.c b/dlls/dwmapi/dwmapi_main.c index 4b881b4d523..23444fc102c 100644 --- a/dlls/dwmapi/dwmapi_main.c +++ b/dlls/dwmapi/dwmapi_main.c @@ -27,6 +27,7 @@ #include "winbase.h" #include "wingdi.h" #include "winuser.h" +#include "ntuser.h" #include "dwmapi.h" #include "wine/debug.h" @@ -66,9 +67,15 @@ HRESULT WINAPI DwmEnableComposition(UINT uCompositionAction) /********************************************************************** * DwmExtendFrameIntoClientArea (DWMAPI.@) */ -HRESULT WINAPI DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS* margins) +HRESULT WINAPI DwmExtendFrameIntoClientArea(HWND hwnd, const MARGINS *margins) { - FIXME("(%p, %p) stub\n", hwnd, margins); + if (!margins) return E_INVALIDARG; + + TRACE("%p: margins %d, %d, %d, %d\n", hwnd, + margins->cxLeftWidth, margins->cxRightWidth, + margins->cyTopHeight, margins->cyBottomHeight); + + NtUserSetWindowDwmConfig(hwnd, DWM_CONFIG_OPAQUE_REGION, margins); return S_OK; } diff --git a/dlls/user32/user32.spec b/dlls/user32/user32.spec index 0645792a312..5cd83b6846f 100644 --- a/dlls/user32/user32.spec +++ b/dlls/user32/user32.spec @@ -1051,6 +1051,7 @@ @ stdcall SetKeyboardState(ptr) NtUserSetKeyboardState @ stdcall SetLastErrorEx(long long) @ stdcall SetLayeredWindowAttributes(ptr long long long) NtUserSetLayeredWindowAttributes +@ stdcall SetWindowDwmConfig(ptr long ptr) NtUserSetWindowDwmConfig @ stdcall SetLogonNotifyWindow(long long) # @ stub SetMagnificationDesktopColorEffect # @ stub SetMagnificationDesktopMagnification diff --git a/dlls/win32u/driver.c b/dlls/win32u/driver.c index 60aba702d1a..25bd18f423c 100644 --- a/dlls/win32u/driver.c +++ b/dlls/win32u/driver.c @@ -826,6 +826,11 @@ static void nulldrv_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE al { } +static BOOL WINAPI nulldrv_SetWindowDwmConfig(HWND hwnd, int command, const void *data) +{ + return FALSE; +} + static void nulldrv_SetParent( HWND hwnd, HWND parent, HWND old_parent ) { } @@ -1291,6 +1296,7 @@ static const struct user_driver_funcs lazy_load_driver = loaderdrv_SetDesktopWindow, nulldrv_ActivateWindow, loaderdrv_SetLayeredWindowAttributes, + nulldrv_SetWindowDwmConfig, nulldrv_SetParent, loaderdrv_SetWindowRgn, nulldrv_SetWindowIcons, diff --git a/dlls/win32u/main.c b/dlls/win32u/main.c index 5593eda9aab..e1efa20b878 100644 --- a/dlls/win32u/main.c +++ b/dlls/win32u/main.c @@ -2246,6 +2246,11 @@ BOOL SYSCALL_API NtUserSetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE SYSCALL_FUNC( NtUserSetLayeredWindowAttributes ); } +BOOL SYSCALL_API NtUserSetWindowDwmConfig( HWND hwnd, LONG command, const void *data ) +{ + SYSCALL_FUNC( NtUserSetWindowDwmConfig ); +} + BOOL SYSCALL_API NtUserSetMenu( HWND hwnd, HMENU menu ) { SYSCALL_FUNC( NtUserSetMenu ); diff --git a/dlls/win32u/win32syscalls.h b/dlls/win32u/win32syscalls.h index 7826afd3fc3..f5c71927cc2 100644 --- a/dlls/win32u/win32syscalls.h +++ b/dlls/win32u/win32syscalls.h @@ -1441,106 +1441,107 @@ SYSCALL_ENTRY( 0x159d, NtUserSetWindowCompositionTransition, 0 ) \ SYSCALL_ENTRY( 0x159e, NtUserSetWindowContextHelpId, 8 ) \ SYSCALL_ENTRY( 0x159f, NtUserSetWindowDisplayAffinity, 0 ) \ - SYSCALL_ENTRY( 0x15a0, NtUserSetWindowFNID, 0 ) \ - SYSCALL_ENTRY( 0x15a1, NtUserSetWindowFeedbackSetting, 0 ) \ - SYSCALL_ENTRY( 0x15a2, NtUserSetWindowGroup, 0 ) \ - SYSCALL_ENTRY( 0x15a3, NtUserSetWindowLong, 16 ) \ - SYSCALL_ENTRY( 0x15a4, NtUserSetWindowLongPtr, 16 ) \ - SYSCALL_ENTRY( 0x15a5, NtUserSetWindowMessageCapability, 0 ) \ - SYSCALL_ENTRY( 0x15a6, NtUserSetWindowPlacement, 8 ) \ - SYSCALL_ENTRY( 0x15a7, NtUserSetWindowPos, 28 ) \ - SYSCALL_ENTRY( 0x15a8, NtUserSetWindowRgn, 12 ) \ - SYSCALL_ENTRY( 0x15a9, NtUserSetWindowRgnEx, 0 ) \ - SYSCALL_ENTRY( 0x15aa, NtUserSetWindowShowState, 0 ) \ - SYSCALL_ENTRY( 0x15ab, NtUserSetWindowState, 0 ) \ - SYSCALL_ENTRY( 0x15ac, NtUserSetWindowStationUser, 0 ) \ - SYSCALL_ENTRY( 0x15ad, NtUserSetWindowWord, 12 ) \ - SYSCALL_ENTRY( 0x15ae, NtUserSetWindowsHookAW, 0 ) \ - SYSCALL_ENTRY( 0x15af, NtUserSetWindowsHookEx, 24 ) \ - SYSCALL_ENTRY( 0x15b0, NtUserShellForegroundBoostProcess, 0 ) \ - SYSCALL_ENTRY( 0x15b1, NtUserShellHandwritingDelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15b2, NtUserShellHandwritingHandleDelegatedInput, 0 ) \ - SYSCALL_ENTRY( 0x15b3, NtUserShellHandwritingUndelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15b4, NtUserShellMigrateWindow, 0 ) \ - SYSCALL_ENTRY( 0x15b5, NtUserShellRegisterHotKey, 0 ) \ - SYSCALL_ENTRY( 0x15b6, NtUserShellSetWindowPos, 0 ) \ - SYSCALL_ENTRY( 0x15b7, NtUserShowCaret, 4 ) \ - SYSCALL_ENTRY( 0x15b8, NtUserShowCursor, 4 ) \ - SYSCALL_ENTRY( 0x15b9, NtUserShowOwnedPopups, 8 ) \ - SYSCALL_ENTRY( 0x15ba, NtUserShowScrollBar, 12 ) \ - SYSCALL_ENTRY( 0x15bb, NtUserShowStartGlass, 0 ) \ - SYSCALL_ENTRY( 0x15bc, NtUserShowSystemCursor, 0 ) \ - SYSCALL_ENTRY( 0x15bd, NtUserShowWindow, 8 ) \ - SYSCALL_ENTRY( 0x15be, NtUserShowWindowAsync, 8 ) \ - SYSCALL_ENTRY( 0x15bf, NtUserShutdownBlockReasonCreate, 0 ) \ - SYSCALL_ENTRY( 0x15c0, NtUserShutdownBlockReasonQuery, 0 ) \ - SYSCALL_ENTRY( 0x15c1, NtUserShutdownReasonDestroy, 0 ) \ - SYSCALL_ENTRY( 0x15c2, NtUserSignalRedirectionStartComplete, 0 ) \ - SYSCALL_ENTRY( 0x15c3, NtUserSlicerControl, 0 ) \ - SYSCALL_ENTRY( 0x15c4, NtUserSoundSentry, 0 ) \ - SYSCALL_ENTRY( 0x15c5, NtUserStopAndEndInertia, 0 ) \ - SYSCALL_ENTRY( 0x15c6, NtUserSuppressWindowActions, 0 ) \ - SYSCALL_ENTRY( 0x15c7, NtUserSuppressWindowDisplayChange, 0 ) \ - SYSCALL_ENTRY( 0x15c8, NtUserSwapMouseButton, 0 ) \ - SYSCALL_ENTRY( 0x15c9, NtUserSwitchDesktop, 4 ) \ - SYSCALL_ENTRY( 0x15ca, NtUserSwitchToThisWindow, 0 ) \ - SYSCALL_ENTRY( 0x15cb, NtUserSystemParametersInfo, 16 ) \ - SYSCALL_ENTRY( 0x15cc, NtUserSystemParametersInfoForDpi, 20 ) \ - SYSCALL_ENTRY( 0x15cd, NtUserTestForInteractiveUser, 0 ) \ - SYSCALL_ENTRY( 0x15ce, NtUserThreadMessageQueueAttached, 0 ) \ - SYSCALL_ENTRY( 0x15cf, NtUserThunkedMenuInfo, 8 ) \ - SYSCALL_ENTRY( 0x15d0, NtUserThunkedMenuItemInfo, 24 ) \ - SYSCALL_ENTRY( 0x15d1, NtUserToUnicodeEx, 28 ) \ - SYSCALL_ENTRY( 0x15d2, NtUserTraceLoggingSendMixedModeTelemetry, 0 ) \ - SYSCALL_ENTRY( 0x15d3, NtUserTrackMouseEvent, 4 ) \ - SYSCALL_ENTRY( 0x15d4, NtUserTrackPopupMenuEx, 24 ) \ - SYSCALL_ENTRY( 0x15d5, NtUserTransformPoint, 0 ) \ - SYSCALL_ENTRY( 0x15d6, NtUserTransformRect, 0 ) \ - SYSCALL_ENTRY( 0x15d7, NtUserTranslateAccelerator, 12 ) \ - SYSCALL_ENTRY( 0x15d8, NtUserTranslateMessage, 8 ) \ - SYSCALL_ENTRY( 0x15d9, NtUserUndelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15da, NtUserUnhookWinEvent, 4 ) \ - SYSCALL_ENTRY( 0x15db, NtUserUnhookWindowsHook, 8 ) \ - SYSCALL_ENTRY( 0x15dc, NtUserUnhookWindowsHookEx, 4 ) \ - SYSCALL_ENTRY( 0x15dd, NtUserUnloadKeyboardLayout, 0 ) \ - SYSCALL_ENTRY( 0x15de, NtUserUnlockWindowStation, 0 ) \ - SYSCALL_ENTRY( 0x15df, NtUserUnregisterClass, 12 ) \ - SYSCALL_ENTRY( 0x15e0, NtUserUnregisterHotKey, 8 ) \ - SYSCALL_ENTRY( 0x15e1, NtUserUnregisterSessionPort, 0 ) \ - SYSCALL_ENTRY( 0x15e2, NtUserUnregisterUserApiHook, 0 ) \ - SYSCALL_ENTRY( 0x15e3, NtUserUpdateClientRect, 0 ) \ - SYSCALL_ENTRY( 0x15e4, NtUserUpdateDefaultDesktopThumbnail, 0 ) \ - SYSCALL_ENTRY( 0x15e5, NtUserUpdateInputContext, 12 ) \ - SYSCALL_ENTRY( 0x15e6, NtUserUpdateInstance, 0 ) \ - SYSCALL_ENTRY( 0x15e7, NtUserUpdateLayeredWindow, 40 ) \ - SYSCALL_ENTRY( 0x15e8, NtUserUpdatePerUserImmEnabling, 0 ) \ - SYSCALL_ENTRY( 0x15e9, NtUserUpdatePerUserSystemParameters, 0 ) \ - SYSCALL_ENTRY( 0x15ea, NtUserUpdateWindow, 0 ) \ - SYSCALL_ENTRY( 0x15eb, NtUserUpdateWindowInputSinkHints, 0 ) \ - SYSCALL_ENTRY( 0x15ec, NtUserUpdateWindowTrackingInfo, 0 ) \ - SYSCALL_ENTRY( 0x15ed, NtUserUpdateWindows, 0 ) \ - SYSCALL_ENTRY( 0x15ee, NtUserUserHandleGrantAccess, 0 ) \ - SYSCALL_ENTRY( 0x15ef, NtUserUserPowerCalloutWorker, 0 ) \ - SYSCALL_ENTRY( 0x15f0, NtUserValidateHandleSecure, 0 ) \ - SYSCALL_ENTRY( 0x15f1, NtUserValidateRect, 8 ) \ - SYSCALL_ENTRY( 0x15f2, NtUserValidateRgn, 8 ) \ - SYSCALL_ENTRY( 0x15f3, NtUserValidateTimerCallback, 0 ) \ - SYSCALL_ENTRY( 0x15f4, NtUserVkKeyScanEx, 8 ) \ - SYSCALL_ENTRY( 0x15f5, NtUserWOWCleanup, 0 ) \ - SYSCALL_ENTRY( 0x15f6, NtUserWOWModuleUnload, 0 ) \ - SYSCALL_ENTRY( 0x15f7, NtUserWaitAvailableMessageEx, 0 ) \ - SYSCALL_ENTRY( 0x15f8, NtUserWaitForInputIdle, 12 ) \ - SYSCALL_ENTRY( 0x15f9, NtUserWaitForMsgAndEvent, 0 ) \ - SYSCALL_ENTRY( 0x15fa, NtUserWaitForRedirectionStartComplete, 0 ) \ - SYSCALL_ENTRY( 0x15fb, NtUserWaitMessage, 0 ) \ - SYSCALL_ENTRY( 0x15fc, NtUserWakeRITForShutdown, 0 ) \ - SYSCALL_ENTRY( 0x15fd, NtUserWindowFromDC, 4 ) \ - SYSCALL_ENTRY( 0x15fe, NtUserWindowFromPhysicalPoint, 0 ) \ - SYSCALL_ENTRY( 0x15ff, NtUserWindowFromPoint, 8 ) \ - SYSCALL_ENTRY( 0x1600, NtUserYieldTask, 0 ) \ - SYSCALL_ENTRY( 0x1601, NtUserZapActiveAndFocus, 0 ) \ - SYSCALL_ENTRY( 0x1602, NtValidateCompositionSurfaceHandle, 0 ) \ - SYSCALL_ENTRY( 0x1603, NtVisualCaptureBits, 0 ) + SYSCALL_ENTRY( 0x15a0, NtUserSetWindowDwmConfig, 12 ) \ + SYSCALL_ENTRY( 0x15a1, NtUserSetWindowFNID, 0 ) \ + SYSCALL_ENTRY( 0x15a2, NtUserSetWindowFeedbackSetting, 0 ) \ + SYSCALL_ENTRY( 0x15a3, NtUserSetWindowGroup, 0 ) \ + SYSCALL_ENTRY( 0x15a4, NtUserSetWindowLong, 16 ) \ + SYSCALL_ENTRY( 0x15a5, NtUserSetWindowLongPtr, 16 ) \ + SYSCALL_ENTRY( 0x15a6, NtUserSetWindowMessageCapability, 0 ) \ + SYSCALL_ENTRY( 0x15a7, NtUserSetWindowPlacement, 8 ) \ + SYSCALL_ENTRY( 0x15a8, NtUserSetWindowPos, 28 ) \ + SYSCALL_ENTRY( 0x15a9, NtUserSetWindowRgn, 12 ) \ + SYSCALL_ENTRY( 0x15aa, NtUserSetWindowRgnEx, 0 ) \ + SYSCALL_ENTRY( 0x15ab, NtUserSetWindowShowState, 0 ) \ + SYSCALL_ENTRY( 0x15ac, NtUserSetWindowState, 0 ) \ + SYSCALL_ENTRY( 0x15ad, NtUserSetWindowStationUser, 0 ) \ + SYSCALL_ENTRY( 0x15ae, NtUserSetWindowWord, 12 ) \ + SYSCALL_ENTRY( 0x15af, NtUserSetWindowsHookAW, 0 ) \ + SYSCALL_ENTRY( 0x15b0, NtUserSetWindowsHookEx, 24 ) \ + SYSCALL_ENTRY( 0x15b1, NtUserShellForegroundBoostProcess, 0 ) \ + SYSCALL_ENTRY( 0x15b2, NtUserShellHandwritingDelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15b3, NtUserShellHandwritingHandleDelegatedInput, 0 ) \ + SYSCALL_ENTRY( 0x15b4, NtUserShellHandwritingUndelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15b5, NtUserShellMigrateWindow, 0 ) \ + SYSCALL_ENTRY( 0x15b6, NtUserShellRegisterHotKey, 0 ) \ + SYSCALL_ENTRY( 0x15b7, NtUserShellSetWindowPos, 0 ) \ + SYSCALL_ENTRY( 0x15b8, NtUserShowCaret, 4 ) \ + SYSCALL_ENTRY( 0x15b9, NtUserShowCursor, 4 ) \ + SYSCALL_ENTRY( 0x15ba, NtUserShowOwnedPopups, 8 ) \ + SYSCALL_ENTRY( 0x15bb, NtUserShowScrollBar, 12 ) \ + SYSCALL_ENTRY( 0x15bc, NtUserShowStartGlass, 0 ) \ + SYSCALL_ENTRY( 0x15bd, NtUserShowSystemCursor, 0 ) \ + SYSCALL_ENTRY( 0x15be, NtUserShowWindow, 8 ) \ + SYSCALL_ENTRY( 0x15bf, NtUserShowWindowAsync, 8 ) \ + SYSCALL_ENTRY( 0x15c0, NtUserShutdownBlockReasonCreate, 0 ) \ + SYSCALL_ENTRY( 0x15c1, NtUserShutdownBlockReasonQuery, 0 ) \ + SYSCALL_ENTRY( 0x15c2, NtUserShutdownReasonDestroy, 0 ) \ + SYSCALL_ENTRY( 0x15c3, NtUserSignalRedirectionStartComplete, 0 ) \ + SYSCALL_ENTRY( 0x15c4, NtUserSlicerControl, 0 ) \ + SYSCALL_ENTRY( 0x15c5, NtUserSoundSentry, 0 ) \ + SYSCALL_ENTRY( 0x15c6, NtUserStopAndEndInertia, 0 ) \ + SYSCALL_ENTRY( 0x15c7, NtUserSuppressWindowActions, 0 ) \ + SYSCALL_ENTRY( 0x15c8, NtUserSuppressWindowDisplayChange, 0 ) \ + SYSCALL_ENTRY( 0x15c9, NtUserSwapMouseButton, 0 ) \ + SYSCALL_ENTRY( 0x15ca, NtUserSwitchDesktop, 4 ) \ + SYSCALL_ENTRY( 0x15cb, NtUserSwitchToThisWindow, 0 ) \ + SYSCALL_ENTRY( 0x15cc, NtUserSystemParametersInfo, 16 ) \ + SYSCALL_ENTRY( 0x15cd, NtUserSystemParametersInfoForDpi, 20 ) \ + SYSCALL_ENTRY( 0x15ce, NtUserTestForInteractiveUser, 0 ) \ + SYSCALL_ENTRY( 0x15cf, NtUserThreadMessageQueueAttached, 0 ) \ + SYSCALL_ENTRY( 0x15d0, NtUserThunkedMenuInfo, 8 ) \ + SYSCALL_ENTRY( 0x15d1, NtUserThunkedMenuItemInfo, 24 ) \ + SYSCALL_ENTRY( 0x15d2, NtUserToUnicodeEx, 28 ) \ + SYSCALL_ENTRY( 0x15d3, NtUserTraceLoggingSendMixedModeTelemetry, 0 ) \ + SYSCALL_ENTRY( 0x15d4, NtUserTrackMouseEvent, 4 ) \ + SYSCALL_ENTRY( 0x15d5, NtUserTrackPopupMenuEx, 24 ) \ + SYSCALL_ENTRY( 0x15d6, NtUserTransformPoint, 0 ) \ + SYSCALL_ENTRY( 0x15d7, NtUserTransformRect, 0 ) \ + SYSCALL_ENTRY( 0x15d8, NtUserTranslateAccelerator, 12 ) \ + SYSCALL_ENTRY( 0x15d9, NtUserTranslateMessage, 8 ) \ + SYSCALL_ENTRY( 0x15da, NtUserUndelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15db, NtUserUnhookWinEvent, 4 ) \ + SYSCALL_ENTRY( 0x15dc, NtUserUnhookWindowsHook, 8 ) \ + SYSCALL_ENTRY( 0x15dd, NtUserUnhookWindowsHookEx, 4 ) \ + SYSCALL_ENTRY( 0x15de, NtUserUnloadKeyboardLayout, 0 ) \ + SYSCALL_ENTRY( 0x15df, NtUserUnlockWindowStation, 0 ) \ + SYSCALL_ENTRY( 0x15e0, NtUserUnregisterClass, 12 ) \ + SYSCALL_ENTRY( 0x15e1, NtUserUnregisterHotKey, 8 ) \ + SYSCALL_ENTRY( 0x15e2, NtUserUnregisterSessionPort, 0 ) \ + SYSCALL_ENTRY( 0x15e3, NtUserUnregisterUserApiHook, 0 ) \ + SYSCALL_ENTRY( 0x15e4, NtUserUpdateClientRect, 0 ) \ + SYSCALL_ENTRY( 0x15e5, NtUserUpdateDefaultDesktopThumbnail, 0 ) \ + SYSCALL_ENTRY( 0x15e6, NtUserUpdateInputContext, 12 ) \ + SYSCALL_ENTRY( 0x15e7, NtUserUpdateInstance, 0 ) \ + SYSCALL_ENTRY( 0x15e8, NtUserUpdateLayeredWindow, 40 ) \ + SYSCALL_ENTRY( 0x15e9, NtUserUpdatePerUserImmEnabling, 0 ) \ + SYSCALL_ENTRY( 0x15ea, NtUserUpdatePerUserSystemParameters, 0 ) \ + SYSCALL_ENTRY( 0x15eb, NtUserUpdateWindow, 0 ) \ + SYSCALL_ENTRY( 0x15ec, NtUserUpdateWindowInputSinkHints, 0 ) \ + SYSCALL_ENTRY( 0x15ed, NtUserUpdateWindowTrackingInfo, 0 ) \ + SYSCALL_ENTRY( 0x15ee, NtUserUpdateWindows, 0 ) \ + SYSCALL_ENTRY( 0x15ef, NtUserUserHandleGrantAccess, 0 ) \ + SYSCALL_ENTRY( 0x15f0, NtUserUserPowerCalloutWorker, 0 ) \ + SYSCALL_ENTRY( 0x15f1, NtUserValidateHandleSecure, 0 ) \ + SYSCALL_ENTRY( 0x15f2, NtUserValidateRect, 8 ) \ + SYSCALL_ENTRY( 0x15f3, NtUserValidateRgn, 8 ) \ + SYSCALL_ENTRY( 0x15f4, NtUserValidateTimerCallback, 0 ) \ + SYSCALL_ENTRY( 0x15f5, NtUserVkKeyScanEx, 8 ) \ + SYSCALL_ENTRY( 0x15f6, NtUserWOWCleanup, 0 ) \ + SYSCALL_ENTRY( 0x15f7, NtUserWOWModuleUnload, 0 ) \ + SYSCALL_ENTRY( 0x15f8, NtUserWaitAvailableMessageEx, 0 ) \ + SYSCALL_ENTRY( 0x15f9, NtUserWaitForInputIdle, 12 ) \ + SYSCALL_ENTRY( 0x15fa, NtUserWaitForMsgAndEvent, 0 ) \ + SYSCALL_ENTRY( 0x15fb, NtUserWaitForRedirectionStartComplete, 0 ) \ + SYSCALL_ENTRY( 0x15fc, NtUserWaitMessage, 0 ) \ + SYSCALL_ENTRY( 0x15fd, NtUserWakeRITForShutdown, 0 ) \ + SYSCALL_ENTRY( 0x15fe, NtUserWindowFromDC, 4 ) \ + SYSCALL_ENTRY( 0x15ff, NtUserWindowFromPhysicalPoint, 0 ) \ + SYSCALL_ENTRY( 0x1600, NtUserWindowFromPoint, 8 ) \ + SYSCALL_ENTRY( 0x1601, NtUserYieldTask, 0 ) \ + SYSCALL_ENTRY( 0x1602, NtUserZapActiveAndFocus, 0 ) \ + SYSCALL_ENTRY( 0x1603, NtValidateCompositionSurfaceHandle, 0 ) \ + SYSCALL_ENTRY( 0x1604, NtVisualCaptureBits, 0 ) #ifdef _WIN64 #define ALL_SYSCALLS \ SYSCALL_ENTRY( 0x1000, NtBindCompositionSurface, 0 ) \ @@ -2983,106 +2984,107 @@ SYSCALL_ENTRY( 0x159d, NtUserSetWindowCompositionTransition, 0 ) \ SYSCALL_ENTRY( 0x159e, NtUserSetWindowContextHelpId, 16 ) \ SYSCALL_ENTRY( 0x159f, NtUserSetWindowDisplayAffinity, 0 ) \ - SYSCALL_ENTRY( 0x15a0, NtUserSetWindowFNID, 0 ) \ - SYSCALL_ENTRY( 0x15a1, NtUserSetWindowFeedbackSetting, 0 ) \ - SYSCALL_ENTRY( 0x15a2, NtUserSetWindowGroup, 0 ) \ - SYSCALL_ENTRY( 0x15a3, NtUserSetWindowLong, 32 ) \ - SYSCALL_ENTRY( 0x15a4, NtUserSetWindowLongPtr, 32 ) \ - SYSCALL_ENTRY( 0x15a5, NtUserSetWindowMessageCapability, 0 ) \ - SYSCALL_ENTRY( 0x15a6, NtUserSetWindowPlacement, 16 ) \ - SYSCALL_ENTRY( 0x15a7, NtUserSetWindowPos, 56 ) \ - SYSCALL_ENTRY( 0x15a8, NtUserSetWindowRgn, 24 ) \ - SYSCALL_ENTRY( 0x15a9, NtUserSetWindowRgnEx, 0 ) \ - SYSCALL_ENTRY( 0x15aa, NtUserSetWindowShowState, 0 ) \ - SYSCALL_ENTRY( 0x15ab, NtUserSetWindowState, 0 ) \ - SYSCALL_ENTRY( 0x15ac, NtUserSetWindowStationUser, 0 ) \ - SYSCALL_ENTRY( 0x15ad, NtUserSetWindowWord, 24 ) \ - SYSCALL_ENTRY( 0x15ae, NtUserSetWindowsHookAW, 0 ) \ - SYSCALL_ENTRY( 0x15af, NtUserSetWindowsHookEx, 48 ) \ - SYSCALL_ENTRY( 0x15b0, NtUserShellForegroundBoostProcess, 0 ) \ - SYSCALL_ENTRY( 0x15b1, NtUserShellHandwritingDelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15b2, NtUserShellHandwritingHandleDelegatedInput, 0 ) \ - SYSCALL_ENTRY( 0x15b3, NtUserShellHandwritingUndelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15b4, NtUserShellMigrateWindow, 0 ) \ - SYSCALL_ENTRY( 0x15b5, NtUserShellRegisterHotKey, 0 ) \ - SYSCALL_ENTRY( 0x15b6, NtUserShellSetWindowPos, 0 ) \ - SYSCALL_ENTRY( 0x15b7, NtUserShowCaret, 8 ) \ - SYSCALL_ENTRY( 0x15b8, NtUserShowCursor, 8 ) \ - SYSCALL_ENTRY( 0x15b9, NtUserShowOwnedPopups, 16 ) \ - SYSCALL_ENTRY( 0x15ba, NtUserShowScrollBar, 24 ) \ - SYSCALL_ENTRY( 0x15bb, NtUserShowStartGlass, 0 ) \ - SYSCALL_ENTRY( 0x15bc, NtUserShowSystemCursor, 0 ) \ - SYSCALL_ENTRY( 0x15bd, NtUserShowWindow, 16 ) \ - SYSCALL_ENTRY( 0x15be, NtUserShowWindowAsync, 16 ) \ - SYSCALL_ENTRY( 0x15bf, NtUserShutdownBlockReasonCreate, 0 ) \ - SYSCALL_ENTRY( 0x15c0, NtUserShutdownBlockReasonQuery, 0 ) \ - SYSCALL_ENTRY( 0x15c1, NtUserShutdownReasonDestroy, 0 ) \ - SYSCALL_ENTRY( 0x15c2, NtUserSignalRedirectionStartComplete, 0 ) \ - SYSCALL_ENTRY( 0x15c3, NtUserSlicerControl, 0 ) \ - SYSCALL_ENTRY( 0x15c4, NtUserSoundSentry, 0 ) \ - SYSCALL_ENTRY( 0x15c5, NtUserStopAndEndInertia, 0 ) \ - SYSCALL_ENTRY( 0x15c6, NtUserSuppressWindowActions, 0 ) \ - SYSCALL_ENTRY( 0x15c7, NtUserSuppressWindowDisplayChange, 0 ) \ - SYSCALL_ENTRY( 0x15c8, NtUserSwapMouseButton, 0 ) \ - SYSCALL_ENTRY( 0x15c9, NtUserSwitchDesktop, 8 ) \ - SYSCALL_ENTRY( 0x15ca, NtUserSwitchToThisWindow, 0 ) \ - SYSCALL_ENTRY( 0x15cb, NtUserSystemParametersInfo, 32 ) \ - SYSCALL_ENTRY( 0x15cc, NtUserSystemParametersInfoForDpi, 40 ) \ - SYSCALL_ENTRY( 0x15cd, NtUserTestForInteractiveUser, 0 ) \ - SYSCALL_ENTRY( 0x15ce, NtUserThreadMessageQueueAttached, 0 ) \ - SYSCALL_ENTRY( 0x15cf, NtUserThunkedMenuInfo, 16 ) \ - SYSCALL_ENTRY( 0x15d0, NtUserThunkedMenuItemInfo, 48 ) \ - SYSCALL_ENTRY( 0x15d1, NtUserToUnicodeEx, 56 ) \ - SYSCALL_ENTRY( 0x15d2, NtUserTraceLoggingSendMixedModeTelemetry, 0 ) \ - SYSCALL_ENTRY( 0x15d3, NtUserTrackMouseEvent, 8 ) \ - SYSCALL_ENTRY( 0x15d4, NtUserTrackPopupMenuEx, 48 ) \ - SYSCALL_ENTRY( 0x15d5, NtUserTransformPoint, 0 ) \ - SYSCALL_ENTRY( 0x15d6, NtUserTransformRect, 0 ) \ - SYSCALL_ENTRY( 0x15d7, NtUserTranslateAccelerator, 24 ) \ - SYSCALL_ENTRY( 0x15d8, NtUserTranslateMessage, 16 ) \ - SYSCALL_ENTRY( 0x15d9, NtUserUndelegateInput, 0 ) \ - SYSCALL_ENTRY( 0x15da, NtUserUnhookWinEvent, 8 ) \ - SYSCALL_ENTRY( 0x15db, NtUserUnhookWindowsHook, 16 ) \ - SYSCALL_ENTRY( 0x15dc, NtUserUnhookWindowsHookEx, 8 ) \ - SYSCALL_ENTRY( 0x15dd, NtUserUnloadKeyboardLayout, 0 ) \ - SYSCALL_ENTRY( 0x15de, NtUserUnlockWindowStation, 0 ) \ - SYSCALL_ENTRY( 0x15df, NtUserUnregisterClass, 24 ) \ - SYSCALL_ENTRY( 0x15e0, NtUserUnregisterHotKey, 16 ) \ - SYSCALL_ENTRY( 0x15e1, NtUserUnregisterSessionPort, 0 ) \ - SYSCALL_ENTRY( 0x15e2, NtUserUnregisterUserApiHook, 0 ) \ - SYSCALL_ENTRY( 0x15e3, NtUserUpdateClientRect, 0 ) \ - SYSCALL_ENTRY( 0x15e4, NtUserUpdateDefaultDesktopThumbnail, 0 ) \ - SYSCALL_ENTRY( 0x15e5, NtUserUpdateInputContext, 24 ) \ - SYSCALL_ENTRY( 0x15e6, NtUserUpdateInstance, 0 ) \ - SYSCALL_ENTRY( 0x15e7, NtUserUpdateLayeredWindow, 80 ) \ - SYSCALL_ENTRY( 0x15e8, NtUserUpdatePerUserImmEnabling, 0 ) \ - SYSCALL_ENTRY( 0x15e9, NtUserUpdatePerUserSystemParameters, 0 ) \ - SYSCALL_ENTRY( 0x15ea, NtUserUpdateWindow, 0 ) \ - SYSCALL_ENTRY( 0x15eb, NtUserUpdateWindowInputSinkHints, 0 ) \ - SYSCALL_ENTRY( 0x15ec, NtUserUpdateWindowTrackingInfo, 0 ) \ - SYSCALL_ENTRY( 0x15ed, NtUserUpdateWindows, 0 ) \ - SYSCALL_ENTRY( 0x15ee, NtUserUserHandleGrantAccess, 0 ) \ - SYSCALL_ENTRY( 0x15ef, NtUserUserPowerCalloutWorker, 0 ) \ - SYSCALL_ENTRY( 0x15f0, NtUserValidateHandleSecure, 0 ) \ - SYSCALL_ENTRY( 0x15f1, NtUserValidateRect, 16 ) \ - SYSCALL_ENTRY( 0x15f2, NtUserValidateRgn, 16 ) \ - SYSCALL_ENTRY( 0x15f3, NtUserValidateTimerCallback, 0 ) \ - SYSCALL_ENTRY( 0x15f4, NtUserVkKeyScanEx, 16 ) \ - SYSCALL_ENTRY( 0x15f5, NtUserWOWCleanup, 0 ) \ - SYSCALL_ENTRY( 0x15f6, NtUserWOWModuleUnload, 0 ) \ - SYSCALL_ENTRY( 0x15f7, NtUserWaitAvailableMessageEx, 0 ) \ - SYSCALL_ENTRY( 0x15f8, NtUserWaitForInputIdle, 24 ) \ - SYSCALL_ENTRY( 0x15f9, NtUserWaitForMsgAndEvent, 0 ) \ - SYSCALL_ENTRY( 0x15fa, NtUserWaitForRedirectionStartComplete, 0 ) \ - SYSCALL_ENTRY( 0x15fb, NtUserWaitMessage, 0 ) \ - SYSCALL_ENTRY( 0x15fc, NtUserWakeRITForShutdown, 0 ) \ - SYSCALL_ENTRY( 0x15fd, NtUserWindowFromDC, 8 ) \ - SYSCALL_ENTRY( 0x15fe, NtUserWindowFromPhysicalPoint, 0 ) \ - SYSCALL_ENTRY( 0x15ff, NtUserWindowFromPoint, 16 ) \ - SYSCALL_ENTRY( 0x1600, NtUserYieldTask, 0 ) \ - SYSCALL_ENTRY( 0x1601, NtUserZapActiveAndFocus, 0 ) \ - SYSCALL_ENTRY( 0x1602, NtValidateCompositionSurfaceHandle, 0 ) \ - SYSCALL_ENTRY( 0x1603, NtVisualCaptureBits, 0 ) + SYSCALL_ENTRY( 0x15a0, NtUserSetWindowDwmConfig, 24 ) \ + SYSCALL_ENTRY( 0x15a1, NtUserSetWindowFNID, 0 ) \ + SYSCALL_ENTRY( 0x15a2, NtUserSetWindowFeedbackSetting, 0 ) \ + SYSCALL_ENTRY( 0x15a3, NtUserSetWindowGroup, 0 ) \ + SYSCALL_ENTRY( 0x15a4, NtUserSetWindowLong, 32 ) \ + SYSCALL_ENTRY( 0x15a5, NtUserSetWindowLongPtr, 32 ) \ + SYSCALL_ENTRY( 0x15a6, NtUserSetWindowMessageCapability, 0 ) \ + SYSCALL_ENTRY( 0x15a7, NtUserSetWindowPlacement, 16 ) \ + SYSCALL_ENTRY( 0x15a8, NtUserSetWindowPos, 56 ) \ + SYSCALL_ENTRY( 0x15a9, NtUserSetWindowRgn, 24 ) \ + SYSCALL_ENTRY( 0x15aa, NtUserSetWindowRgnEx, 0 ) \ + SYSCALL_ENTRY( 0x15ab, NtUserSetWindowShowState, 0 ) \ + SYSCALL_ENTRY( 0x15ac, NtUserSetWindowState, 0 ) \ + SYSCALL_ENTRY( 0x15ad, NtUserSetWindowStationUser, 0 ) \ + SYSCALL_ENTRY( 0x15ae, NtUserSetWindowWord, 24 ) \ + SYSCALL_ENTRY( 0x15af, NtUserSetWindowsHookAW, 0 ) \ + SYSCALL_ENTRY( 0x15b0, NtUserSetWindowsHookEx, 48 ) \ + SYSCALL_ENTRY( 0x15b1, NtUserShellForegroundBoostProcess, 0 ) \ + SYSCALL_ENTRY( 0x15b2, NtUserShellHandwritingDelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15b3, NtUserShellHandwritingHandleDelegatedInput, 0 ) \ + SYSCALL_ENTRY( 0x15b4, NtUserShellHandwritingUndelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15b5, NtUserShellMigrateWindow, 0 ) \ + SYSCALL_ENTRY( 0x15b6, NtUserShellRegisterHotKey, 0 ) \ + SYSCALL_ENTRY( 0x15b7, NtUserShellSetWindowPos, 0 ) \ + SYSCALL_ENTRY( 0x15b8, NtUserShowCaret, 8 ) \ + SYSCALL_ENTRY( 0x15b9, NtUserShowCursor, 8 ) \ + SYSCALL_ENTRY( 0x15ba, NtUserShowOwnedPopups, 16 ) \ + SYSCALL_ENTRY( 0x15bb, NtUserShowScrollBar, 24 ) \ + SYSCALL_ENTRY( 0x15bc, NtUserShowStartGlass, 0 ) \ + SYSCALL_ENTRY( 0x15bd, NtUserShowSystemCursor, 0 ) \ + SYSCALL_ENTRY( 0x15be, NtUserShowWindow, 16 ) \ + SYSCALL_ENTRY( 0x15bf, NtUserShowWindowAsync, 16 ) \ + SYSCALL_ENTRY( 0x15c0, NtUserShutdownBlockReasonCreate, 0 ) \ + SYSCALL_ENTRY( 0x15c1, NtUserShutdownBlockReasonQuery, 0 ) \ + SYSCALL_ENTRY( 0x15c2, NtUserShutdownReasonDestroy, 0 ) \ + SYSCALL_ENTRY( 0x15c3, NtUserSignalRedirectionStartComplete, 0 ) \ + SYSCALL_ENTRY( 0x15c4, NtUserSlicerControl, 0 ) \ + SYSCALL_ENTRY( 0x15c5, NtUserSoundSentry, 0 ) \ + SYSCALL_ENTRY( 0x15c6, NtUserStopAndEndInertia, 0 ) \ + SYSCALL_ENTRY( 0x15c7, NtUserSuppressWindowActions, 0 ) \ + SYSCALL_ENTRY( 0x15c8, NtUserSuppressWindowDisplayChange, 0 ) \ + SYSCALL_ENTRY( 0x15c9, NtUserSwapMouseButton, 0 ) \ + SYSCALL_ENTRY( 0x15ca, NtUserSwitchDesktop, 8 ) \ + SYSCALL_ENTRY( 0x15cb, NtUserSwitchToThisWindow, 0 ) \ + SYSCALL_ENTRY( 0x15cc, NtUserSystemParametersInfo, 32 ) \ + SYSCALL_ENTRY( 0x15cd, NtUserSystemParametersInfoForDpi, 40 ) \ + SYSCALL_ENTRY( 0x15ce, NtUserTestForInteractiveUser, 0 ) \ + SYSCALL_ENTRY( 0x15cf, NtUserThreadMessageQueueAttached, 0 ) \ + SYSCALL_ENTRY( 0x15d0, NtUserThunkedMenuInfo, 16 ) \ + SYSCALL_ENTRY( 0x15d1, NtUserThunkedMenuItemInfo, 48 ) \ + SYSCALL_ENTRY( 0x15d2, NtUserToUnicodeEx, 56 ) \ + SYSCALL_ENTRY( 0x15d3, NtUserTraceLoggingSendMixedModeTelemetry, 0 ) \ + SYSCALL_ENTRY( 0x15d4, NtUserTrackMouseEvent, 8 ) \ + SYSCALL_ENTRY( 0x15d5, NtUserTrackPopupMenuEx, 48 ) \ + SYSCALL_ENTRY( 0x15d6, NtUserTransformPoint, 0 ) \ + SYSCALL_ENTRY( 0x15d7, NtUserTransformRect, 0 ) \ + SYSCALL_ENTRY( 0x15d8, NtUserTranslateAccelerator, 24 ) \ + SYSCALL_ENTRY( 0x15d9, NtUserTranslateMessage, 16 ) \ + SYSCALL_ENTRY( 0x15da, NtUserUndelegateInput, 0 ) \ + SYSCALL_ENTRY( 0x15db, NtUserUnhookWinEvent, 8 ) \ + SYSCALL_ENTRY( 0x15dc, NtUserUnhookWindowsHook, 16 ) \ + SYSCALL_ENTRY( 0x15dd, NtUserUnhookWindowsHookEx, 8 ) \ + SYSCALL_ENTRY( 0x15de, NtUserUnloadKeyboardLayout, 0 ) \ + SYSCALL_ENTRY( 0x15df, NtUserUnlockWindowStation, 0 ) \ + SYSCALL_ENTRY( 0x15e0, NtUserUnregisterClass, 24 ) \ + SYSCALL_ENTRY( 0x15e1, NtUserUnregisterHotKey, 16 ) \ + SYSCALL_ENTRY( 0x15e2, NtUserUnregisterSessionPort, 0 ) \ + SYSCALL_ENTRY( 0x15e3, NtUserUnregisterUserApiHook, 0 ) \ + SYSCALL_ENTRY( 0x15e4, NtUserUpdateClientRect, 0 ) \ + SYSCALL_ENTRY( 0x15e5, NtUserUpdateDefaultDesktopThumbnail, 0 ) \ + SYSCALL_ENTRY( 0x15e6, NtUserUpdateInputContext, 24 ) \ + SYSCALL_ENTRY( 0x15e7, NtUserUpdateInstance, 0 ) \ + SYSCALL_ENTRY( 0x15e8, NtUserUpdateLayeredWindow, 80 ) \ + SYSCALL_ENTRY( 0x15e9, NtUserUpdatePerUserImmEnabling, 0 ) \ + SYSCALL_ENTRY( 0x15ea, NtUserUpdatePerUserSystemParameters, 0 ) \ + SYSCALL_ENTRY( 0x15eb, NtUserUpdateWindow, 0 ) \ + SYSCALL_ENTRY( 0x15ec, NtUserUpdateWindowInputSinkHints, 0 ) \ + SYSCALL_ENTRY( 0x15ed, NtUserUpdateWindowTrackingInfo, 0 ) \ + SYSCALL_ENTRY( 0x15ee, NtUserUpdateWindows, 0 ) \ + SYSCALL_ENTRY( 0x15ef, NtUserUserHandleGrantAccess, 0 ) \ + SYSCALL_ENTRY( 0x15f0, NtUserUserPowerCalloutWorker, 0 ) \ + SYSCALL_ENTRY( 0x15f1, NtUserValidateHandleSecure, 0 ) \ + SYSCALL_ENTRY( 0x15f2, NtUserValidateRect, 16 ) \ + SYSCALL_ENTRY( 0x15f3, NtUserValidateRgn, 16 ) \ + SYSCALL_ENTRY( 0x15f4, NtUserValidateTimerCallback, 0 ) \ + SYSCALL_ENTRY( 0x15f5, NtUserVkKeyScanEx, 16 ) \ + SYSCALL_ENTRY( 0x15f6, NtUserWOWCleanup, 0 ) \ + SYSCALL_ENTRY( 0x15f7, NtUserWOWModuleUnload, 0 ) \ + SYSCALL_ENTRY( 0x15f8, NtUserWaitAvailableMessageEx, 0 ) \ + SYSCALL_ENTRY( 0x15f9, NtUserWaitForInputIdle, 24 ) \ + SYSCALL_ENTRY( 0x15fa, NtUserWaitForMsgAndEvent, 0 ) \ + SYSCALL_ENTRY( 0x15fb, NtUserWaitForRedirectionStartComplete, 0 ) \ + SYSCALL_ENTRY( 0x15fc, NtUserWaitMessage, 0 ) \ + SYSCALL_ENTRY( 0x15fd, NtUserWakeRITForShutdown, 0 ) \ + SYSCALL_ENTRY( 0x15fe, NtUserWindowFromDC, 8 ) \ + SYSCALL_ENTRY( 0x15ff, NtUserWindowFromPhysicalPoint, 0 ) \ + SYSCALL_ENTRY( 0x1600, NtUserWindowFromPoint, 16 ) \ + SYSCALL_ENTRY( 0x1601, NtUserYieldTask, 0 ) \ + SYSCALL_ENTRY( 0x1602, NtUserZapActiveAndFocus, 0 ) \ + SYSCALL_ENTRY( 0x1603, NtValidateCompositionSurfaceHandle, 0 ) \ + SYSCALL_ENTRY( 0x1604, NtVisualCaptureBits, 0 ) #else #define ALL_SYSCALLS ALL_SYSCALLS32 #endif diff --git a/dlls/win32u/win32u.spec b/dlls/win32u/win32u.spec index 66e8541d915..01698e49c46 100644 --- a/dlls/win32u/win32u.spec +++ b/dlls/win32u/win32u.spec @@ -1382,6 +1382,7 @@ @ stdcall -syscall NtUserSetInternalWindowPos(long long ptr ptr) @ stdcall -syscall NtUserSetKeyboardState(ptr) @ stdcall -syscall NtUserSetLayeredWindowAttributes(ptr long long long) +@ stdcall -syscall NtUserSetWindowDwmConfig(ptr long ptr) @ stub -syscall NtUserSetMagnificationDesktopMagnifierOffsetsDWMUpdated @ stub -syscall NtUserSetManipulationInputTarget @ stdcall -syscall NtUserSetMenu(long long) diff --git a/dlls/win32u/window.c b/dlls/win32u/window.c index 61d447e9f32..d573fbc1305 100644 --- a/dlls/win32u/window.c +++ b/dlls/win32u/window.c @@ -2552,6 +2552,21 @@ BOOL WINAPI NtUserSetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alph return ret; } +/***************************************************************************** + * NtUserSetWindowDwmConfig (win32u.@) + */ +BOOL WINAPI NtUserSetWindowDwmConfig( HWND hwnd, LONG command, const void *data ) +{ + TRACE( "(%p, %d, %p)\n", hwnd, (int)command, data ); + + if (user_driver && user_driver->pSetWindowDwmConfig) + { + return user_driver->pSetWindowDwmConfig( hwnd, command, data ); + } + + return FALSE; +} + /***************************************************************************** * UpdateLayeredWindow (win32u.@) */ diff --git a/dlls/wow64win/user.c b/dlls/wow64win/user.c index e5c8840c317..027c020c161 100644 --- a/dlls/wow64win/user.c +++ b/dlls/wow64win/user.c @@ -4446,6 +4446,15 @@ NTSTATUS WINAPI wow64_NtUserSetLayeredWindowAttributes( UINT *args ) return NtUserSetLayeredWindowAttributes( hwnd, key, alpha, flags ); } +NTSTATUS WINAPI wow64_NtUserSetWindowDwmConfig( UINT *args ) +{ + HWND hwnd = get_handle( &args ); + LONG command = get_ulong( &args ); + const void *data = get_ptr( &args ); + + return NtUserSetWindowDwmConfig( hwnd, command, data ); +} + NTSTATUS WINAPI wow64_NtUserSetMenu( UINT *args ) { HWND hwnd = get_handle( &args ); diff --git a/include/ntuser.h b/include/ntuser.h index ce1b18fdfd9..1d170a797c0 100644 --- a/include/ntuser.h +++ b/include/ntuser.h @@ -998,6 +998,7 @@ W32KAPI BOOL WINAPI NtUserSetForegroundWindow( HWND hwnd ); W32KAPI void WINAPI NtUserSetInternalWindowPos( HWND hwnd, UINT cmd, RECT *rect, POINT *pt ); W32KAPI BOOL WINAPI NtUserSetKeyboardState( BYTE *state ); W32KAPI BOOL WINAPI NtUserSetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWORD flags ); +W32KAPI BOOL WINAPI NtUserSetWindowDwmConfig( HWND hwnd, LONG command, const void *data ); W32KAPI BOOL WINAPI NtUserSetMenu( HWND hwnd, HMENU menu ); W32KAPI BOOL WINAPI NtUserSetMenuContextHelpId( HMENU handle, DWORD id ); W32KAPI BOOL WINAPI NtUserSetMenuDefaultItem( HMENU handle, UINT item, UINT bypos ); diff --git a/include/wine/gdi_driver.h b/include/wine/gdi_driver.h index f6390bce878..c472b896976 100644 --- a/include/wine/gdi_driver.h +++ b/include/wine/gdi_driver.h @@ -408,6 +408,7 @@ struct user_driver_funcs void (*pSetDesktopWindow)(HWND); void (*pActivateWindow)(HWND,HWND); void (*pSetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD); + BOOL (*pSetWindowDwmConfig)(HWND,INT,const void *); void (*pSetParent)(HWND,HWND,HWND); void (*pSetWindowRgn)(HWND,HRGN,BOOL); void (*pSetWindowIcons)(HWND,HICON,const ICONINFO*,HICON,const ICONINFO*); diff --git a/include/winuser.h b/include/winuser.h index 32b07b387a1..4039e0f47c8 100644 --- a/include/winuser.h +++ b/include/winuser.h @@ -2730,6 +2730,9 @@ typedef struct { #define LWA_COLORKEY 0x00000001 #define LWA_ALPHA 0x00000002 +/* SetWindowDwmConfig() flags */ +#define DWM_CONFIG_OPAQUE_REGION 0x00000001 + /* UpdateLayeredWindow() flags */ #define ULW_COLORKEY 0x00000001 #define ULW_ALPHA 0x00000002 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Implement the X11 driver callback for NtUserSetWindowDwmConfig to support DWM_CONFIG_OPAQUE_REGION. This translates DWM margins into XShape calls, effectively punching out the non-client area to support custom frame decorations. Glass effect (-1) is handled as a shape reset to prevent visibility issues on standard visuals. --- dlls/winex11.drv/init.c | 1 + dlls/winex11.drv/window.c | 76 +++++++++++++++++++++++++++++++++++++++ dlls/winex11.drv/x11drv.h | 4 +-- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/dlls/winex11.drv/init.c b/dlls/winex11.drv/init.c index 09ef2bf8664..b55c4b83840 100644 --- a/dlls/winex11.drv/init.c +++ b/dlls/winex11.drv/init.c @@ -697,6 +697,7 @@ static const struct user_driver_funcs x11drv_funcs = .pSetDesktopWindow = X11DRV_SetDesktopWindow, .pActivateWindow = X11DRV_ActivateWindow, .pSetLayeredWindowAttributes = X11DRV_SetLayeredWindowAttributes, + .pSetWindowDwmConfig = X11DRV_SetWindowDwmConfig, .pSetParent = X11DRV_SetParent, .pSetWindowIcons = X11DRV_SetWindowIcons, .pSetWindowRgn = X11DRV_SetWindowRgn, diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 26cb17a2c77..1d5f38e5a3a 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -50,6 +50,7 @@ #include "x11drv.h" #include "wingdi.h" #include "winuser.h" +#include <uxtheme.h> #include "wine/debug.h" #include "wine/server.h" @@ -3468,6 +3469,81 @@ void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWO } +/*********************************************************************** + * X11DRV_SetWindowDwmConfig (X11DRV.@) + * + * Handle DWM configuration requests. + */ +BOOL X11DRV_SetWindowDwmConfig( HWND hwnd, INT command, const void *data ) +{ + struct x11drv_win_data *data_ptr; + Window window; + const MARGINS *margins; + XRectangle opaque_rect; + int width, height; + + if (command != DWM_CONFIG_OPAQUE_REGION) return FALSE; + if (!(margins = data)) return FALSE; + + TRACE( "hwnd %p, margins %d,%d,%d,%d\n", hwnd, + margins->cxLeftWidth, margins->cxRightWidth, + margins->cyTopHeight, margins->cyBottomHeight ); + + if (!(data_ptr = get_win_data( hwnd ))) return FALSE; + + window = data_ptr->whole_window; + if (!window) + { + release_win_data( data_ptr ); + return FALSE; + } + + width = data_ptr->rects.visible.right - data_ptr->rects.visible.left; + height = data_ptr->rects.visible.bottom - data_ptr->rects.visible.top; + + X11DRV_expect_error( data_ptr->display, NULL, NULL ); + +#ifdef HAVE_LIBXSHAPE + /* -1 (Sheet of Glass) or 0 (Standard) resets the shape to the full window */ + if (margins->cxLeftWidth == -1 || + (margins->cxLeftWidth == 0 && margins->cxRightWidth == 0 && + margins->cyTopHeight == 0 && margins->cyBottomHeight == 0)) + { + XRectangle full_rect = { 0, 0, width, height }; + XShapeCombineRectangles( data_ptr->display, window, ShapeBounding, 0, 0, &full_rect, 1, ShapeSet, Unsorted ); + XShapeCombineRectangles( data_ptr->display, window, ShapeInput, 0, 0, &full_rect, 1, ShapeSet, Unsorted ); + } + /* Hole Punching (Custom Margins), calculates the region that should remain opaque */ + else + { + opaque_rect.x = margins->cxLeftWidth; + opaque_rect.y = margins->cyTopHeight; + opaque_rect.width = width - (margins->cxLeftWidth + margins->cxRightWidth); + opaque_rect.height = height - (margins->cyTopHeight + margins->cyBottomHeight); + + if (opaque_rect.width <= 0 || opaque_rect.height <= 0) + { + XShapeCombineMask( data_ptr->display, window, ShapeBounding, 0, 0, None, ShapeSet ); + XShapeCombineMask( data_ptr->display, window, ShapeInput, 0, 0, None, ShapeSet ); + } + else + { + XShapeCombineRectangles( data_ptr->display, window, ShapeBounding, 0, 0, &opaque_rect, 1, ShapeSet, Unsorted ); + XShapeCombineRectangles( data_ptr->display, window, ShapeInput, 0, 0, &opaque_rect, 1, ShapeSet, Unsorted ); + } + } +#else + WARN("XShape support not compiled in; DWM margins ignored.\n"); +#endif + + if (X11DRV_check_error()) + ERR("XShape error while applying DWM margins.\n"); + + release_win_data( data_ptr ); + return TRUE; +} + + /*********************************************************************** * UpdateLayeredWindow (X11DRV.@) */ diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 9f38ceba0df..3266ec305f8 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -235,8 +235,8 @@ extern void X11DRV_ReleaseDC( HWND hwnd, HDC hdc ); extern BOOL X11DRV_ScrollDC( HDC hdc, INT dx, INT dy, HRGN update ); extern void X11DRV_SetCapture( HWND hwnd, UINT flags ); extern void X11DRV_SetDesktopWindow( HWND hwnd ); -extern void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, - DWORD flags ); +extern void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWORD flags ); +extern BOOL X11DRV_SetWindowDwmConfig( HWND hwnd, INT command, const void *data ); extern void X11DRV_SetParent( HWND hwnd, HWND parent, HWND old_parent ); extern void X11DRV_SetWindowIcons( HWND hwnd, HICON icon, const ICONINFO *ii, HICON icon_small, const ICONINFO *ii_small ); extern void X11DRV_SetWindowRgn( HWND hwnd, HRGN hrgn, BOOL redraw ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Implement logic to select or upgrade to a 32-bit ARGB visual when transparency is requested via DWM or Layered Window attributes. - Select ARGB visual if layered or ControlParent. - Upgrade to 32-bit ARGB visual for DWM Glass effect dynamically. - Prevent reverting to 24-bit default if ARGB is already active. - Ensure child client windows inherit the ARGB visual and colormap from the parent to prevent black occlusion over the parent window. Note: This enables transparency for hardware-accelerated applications (OpenGL/Vulkan/DXGI). Native GDI drawing may still render opaque black backgrounds until generic ARGB surface support is implemented. --- dlls/winex11.drv/window.c | 109 +++++++++++++++++++++++++++++++------- 1 file changed, 91 insertions(+), 18 deletions(-) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 1d5f38e5a3a..3abc2acb00d 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -664,15 +664,21 @@ static void sync_window_region( struct x11drv_win_data *data, HRGN win_region ) */ static void sync_window_opacity( Display *display, Window win, BYTE alpha, DWORD flags ) { - unsigned long opacity = 0xffffffff; + unsigned long opacity = alpha * (0xffffffff / 255); - if (flags & LWA_ALPHA) opacity = (0xffffffff / 0xff) * alpha; - - if (opacity == 0xffffffff) + /* If alpha is 255 (Opaque) and we are using ARGB, delete the property + to let the compositor rely on the per-pixel ARGB values. */ + if ((flags & LWA_ALPHA) && alpha == 255) + { XDeleteProperty( display, win, x11drv_atom(_NET_WM_WINDOW_OPACITY) ); - else + return; + } + + if (flags & LWA_ALPHA) XChangeProperty( display, win, x11drv_atom(_NET_WM_WINDOW_OPACITY), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1 ); + else + XDeleteProperty( display, win, x11drv_atom(_NET_WM_WINDOW_OPACITY) ); } @@ -2359,16 +2365,31 @@ Window create_client_window( HWND hwnd, RECT client_rect, const XVisualInfo *vis XSetWindowAttributes attr; Window ret; int x, y, cx, cy; + Visual *client_visual_ptr = visual->visual; + int client_depth = visual->depth; if (!data) data = &dummy; /* use a dummy window data for HWND_MESSAGE and foreign windows, to create an offscreen client window */ detach_client_window( data, data->client_window ); - attr.colormap = colormap; + /* Inherit ARGB visual and colormap from the Whole window if active */ + if (data->whole_window && data->vis.visualid == argb_visual.visualid) + { + client_visual_ptr = argb_visual.visual; + client_depth = 32; + attr.colormap = data->whole_colormap; + attr.background_pixmap = None; + attr.border_pixel = 0; + } + else + { + attr.colormap = colormap; + attr.border_pixel = 0; + } + attr.bit_gravity = NorthWestGravity; attr.win_gravity = NorthWestGravity; attr.backing_store = NotUseful; - attr.border_pixel = 0; x = data->rects.client.left - data->rects.visible.left; y = data->rects.client.top - data->rects.visible.top; @@ -2376,11 +2397,14 @@ Window create_client_window( HWND hwnd, RECT client_rect, const XVisualInfo *vis cy = min( max( 1, client_rect.bottom - client_rect.top ), 65535 ); XSync( gdi_display, False ); /* make sure whole_window is known from gdi_display */ + + /* Use calculated visual/depth and include BackPixmap in mask if ARGB */ ret = data->client_window = XCreateWindow( gdi_display, data->whole_window ? data->whole_window : get_dummy_parent(), - x, y, cx, cy, 0, visual->depth, InputOutput, - visual->visual, CWBitGravity | CWWinGravity | - CWBackingStore | CWColormap | CWBorderPixel, &attr ); + x, y, cx, cy, 0, client_depth, InputOutput, + client_visual_ptr, CWBitGravity | CWWinGravity | + CWBackingStore | CWColormap | CWBorderPixel | + (client_depth == 32 ? CWBackPixmap : 0), &attr ); if (data->client_window) { XMapWindow( gdi_display, data->client_window ); @@ -2409,9 +2433,11 @@ static void create_whole_window( struct x11drv_win_data *data ) WCHAR text[1024]; COLORREF key; BYTE alpha; - DWORD layered_flags; + DWORD layered_flags = 0; HRGN win_rgn; POINT pos; + DWORD ex_style; + BOOL is_layered; if ((win_rgn = NtGdiCreateRectRgn( 0, 0, 0, 0 )) && NtUserGetWindowRgnEx( data->hwnd, win_rgn, 0 ) == ERROR) @@ -2421,6 +2447,20 @@ static void create_whole_window( struct x11drv_win_data *data ) } data->shaped = (win_rgn != 0); + /* Pre-emptive ARGB selection */ + ex_style = NtUserGetWindowLongW( data->hwnd, GWL_EXSTYLE ); + if (!NtUserGetLayeredWindowAttributes( data->hwnd, &key, &alpha, &layered_flags )) + layered_flags = 0; + + is_layered = (ex_style & WS_EX_LAYERED); + + /* Check for Layered or .NET ControlParent style */ + if ((is_layered || (ex_style & WS_EX_CONTROLPARENT)) && argb_visual.visualid) + { + TRACE( "Forcing ARGB visual for window %p\n", data->hwnd ); + data->vis = argb_visual; + } + if (data->vis.visualid != default_visual.visualid) data->whole_colormap = XCreateColormap( data->display, root_window, data->vis.visual, AllocNone ); @@ -2428,6 +2468,15 @@ static void create_whole_window( struct x11drv_win_data *data ) mask = get_window_attributes( data, &attr ) | CWOverrideRedirect; attr.override_redirect = !data->managed; + /* Force background pixmap to None for ARGB visuals */ + if (data->vis.visualid == argb_visual.visualid) + { + attr.background_pixmap = None; + attr.border_pixel = 0; + mask |= CWBackPixmap | CWBorderPixel; + mask &= ~CWBackPixel; + } + if (!(cx = data->rects.visible.right - data->rects.visible.left)) cx = 1; else if (cx > 65535) cx = 65535; if (!(cy = data->rects.visible.bottom - data->rects.visible.top)) cy = 1; @@ -2458,7 +2507,7 @@ static void create_whole_window( struct x11drv_win_data *data ) else if (win_rgn) sync_window_region( data, win_rgn ); /* set the window opacity */ - if (!NtUserGetLayeredWindowAttributes( data->hwnd, &key, &alpha, &layered_flags )) layered_flags = 0; + /* Note: variable 'layered_flags' is reused/set at the top of this function now */ sync_window_opacity( data->display, data->whole_window, alpha, layered_flags ); sync_window_input_shape( data ); @@ -3444,7 +3493,9 @@ void X11DRV_SetLayeredWindowAttributes( HWND hwnd, COLORREF key, BYTE alpha, DWO if (data) { - set_window_visual( data, &default_visual, FALSE ); + /* Only revert to default visual if we are NOT already ARGB */ + if (data->vis.visualid != argb_visual.visualid) + set_window_visual( data, &default_visual, FALSE ); if (data->whole_window) { @@ -3491,6 +3542,22 @@ BOOL X11DRV_SetWindowDwmConfig( HWND hwnd, INT command, const void *data ) if (!(data_ptr = get_win_data( hwnd ))) return FALSE; + /* Runtime Visual Upgrade */ + /* If the app requests "Sheet of Glass" (-1) but we are still using the default + 24-bit visual, we MUST upgrade to 32-bit ARGB now. */ + if (margins->cxLeftWidth == -1 && + data_ptr->vis.visualid != argb_visual.visualid && + argb_visual.visualid) + { + TRACE( "Upgrading hwnd %p to ARGB Visual for DWM Glass effect\n", hwnd ); + set_window_visual( data_ptr, &argb_visual, TRUE ); + + if (data_ptr->whole_window) + { + sync_window_opacity( data_ptr->display, data_ptr->whole_window, 255, 0 ); + } + } + window = data_ptr->whole_window; if (!window) { @@ -3504,16 +3571,22 @@ BOOL X11DRV_SetWindowDwmConfig( HWND hwnd, INT command, const void *data ) X11DRV_expect_error( data_ptr->display, NULL, NULL ); #ifdef HAVE_LIBXSHAPE - /* -1 (Sheet of Glass) or 0 (Standard) resets the shape to the full window */ - if (margins->cxLeftWidth == -1 || - (margins->cxLeftWidth == 0 && margins->cxRightWidth == 0 && - margins->cyTopHeight == 0 && margins->cyBottomHeight == 0)) + /* CASE 1: Sheet of Glass (-1) */ + if (margins->cxLeftWidth == -1) + { + /* Clear shapes to allow alpha-blended pixels to show through the full window */ + XShapeCombineMask( data_ptr->display, window, ShapeBounding, 0, 0, None, ShapeSet ); + XShapeCombineMask( data_ptr->display, window, ShapeInput, 0, 0, None, ShapeSet ); + } + /* CASE 2: Standard Opaque (0) */ + else if (margins->cxLeftWidth == 0 && margins->cxRightWidth == 0 && + margins->cyTopHeight == 0 && margins->cyBottomHeight == 0) { XRectangle full_rect = { 0, 0, width, height }; XShapeCombineRectangles( data_ptr->display, window, ShapeBounding, 0, 0, &full_rect, 1, ShapeSet, Unsorted ); XShapeCombineRectangles( data_ptr->display, window, ShapeInput, 0, 0, &full_rect, 1, ShapeSet, Unsorted ); } - /* Hole Punching (Custom Margins), calculates the region that should remain opaque */ + /* CASE 3: Hole Punching (Custom Margins) */ else { opaque_rect.x = margins->cxLeftWidth; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Implement "Ghost Window" semantics for DWM Glass windows (-1 margins) to support modern game overlay interaction. Introduces sync_ghost_shape to physically strip the X11 Input Region using XShape when a window combines WS_EX_LAYERED and WS_EX_TRANSPARENT. This ensures mouse events fall through to the underlying application as expected by Windows DWM logic. --- dlls/winex11.drv/window.c | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 3abc2acb00d..7750308d03b 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -2420,6 +2420,48 @@ Window create_client_window( HWND hwnd, RECT client_rect, const XVisualInfo *vis return ret; } +/********************************************************************** + * sync_ghost_shape + * + * Helper to manage X11 Input Shapes for "Ghost" windows + */ + +static void sync_ghost_shape( struct x11drv_win_data *data ) +{ +#ifdef HAVE_LIBXSHAPE + /* Standard XShape logic: + If the window is Layered (Visual) AND Transparent (Input), + we physically remove it from X11 input handling by setting an empty input shape. + */ + DWORD ex_style = NtUserGetWindowLongW( data->hwnd, GWL_EXSTYLE ); + BOOL is_layered = (ex_style & WS_EX_LAYERED); + BOOL is_transparent = (ex_style & WS_EX_TRANSPARENT); + + /* Check: Some apps set LWA_ALPHA without WS_EX_LAYERED in style bits initially */ + if (!is_layered) + { + COLORREF key; + BYTE alpha; + DWORD flags; + if (NtUserGetLayeredWindowAttributes( data->hwnd, &key, &alpha, &flags )) + is_layered = TRUE; + } + + if (is_layered && is_transparent) + { + /* CLICK-THROUGH: Set empty input region */ + static XRectangle empty_rect; + TRACE("Setting EMPTY Input Region for %p (Ghost Mode)\n", data->hwnd); + XShapeCombineRectangles( data->display, data->whole_window, ShapeInput, 0, 0, &empty_rect, 0, ShapeSet, Unsorted ); + } + else + { + /* INTERACTIVE: Reset input region to default (matches window bounds) */ + /* Only reset if we previously ghosted it, or to ensure consistency */ + XShapeCombineMask( data->display, data->whole_window, ShapeInput, 0, 0, None, ShapeSet ); + } +#endif +} /********************************************************************** * create_whole_window @@ -2510,6 +2552,7 @@ static void create_whole_window( struct x11drv_win_data *data ) /* Note: variable 'layered_flags' is reused/set at the top of this function now */ sync_window_opacity( data->display, data->whole_window, alpha, layered_flags ); sync_window_input_shape( data ); + sync_ghost_shape( data ); /* Apply Ghost Shape on Creation */ XFlush( data->display ); /* make sure the window exists before we start painting to it */ @@ -3371,6 +3414,12 @@ void X11DRV_WindowPosChanged( HWND hwnd, HWND insert_after, HWND owner_hint, UIN update_net_wm_states( data ); } + /* Dynamic Ghost Update */ + if (data->whole_window) + { + sync_ghost_shape( data ); + } + /* if window was fullscreen and is being hidden, release cursor clipping */ was_fullscreen &= data->desired_state.wm_state != NormalState; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Some overlays omit WS_EX_TOPMOST at creation, relying instead on late calls to SetWindowPos(HWND_TOPMOST). Standard X11 Window Managers often deprioritize transparent or input-less windows, ignoring these runtime requests and causing overlays to spawn behind the game. Track the DWM glass state in x11drv_win_data and strictly enforce _NET_WM_STATE_ABOVE in update_net_wm_states for any window identified as a DWM Overlay, regardless of the current window style bits. --- dlls/winex11.drv/window.c | 18 +++++++++++++++++- dlls/winex11.drv/x11drv.h | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dlls/winex11.drv/window.c b/dlls/winex11.drv/window.c index 7750308d03b..beaeec54cc5 100644 --- a/dlls/winex11.drv/window.c +++ b/dlls/winex11.drv/window.c @@ -1505,8 +1505,18 @@ static void update_net_wm_states( struct x11drv_win_data *data ) new_state |= (1 << NET_WM_STATE_MAXIMIZED); ex_style = NtUserGetWindowLongW( data->hwnd, GWL_EXSTYLE ); - if (ex_style & WS_EX_TOPMOST) + + /* Logic: + 1. Standard behavior: If WS_EX_TOPMOST is set, set _NET_WM_STATE_ABOVE. + 2. Strict Overlay behavior: If "Glass Mode" (-1) is active AND it is Transparent/Layered, + FORCE _NET_WM_STATE_ABOVE even if the app omits WS_EX_TOPMOST at creation. + */ + if ((ex_style & WS_EX_TOPMOST) || + (data->dwm_glass_state && (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT))) + { new_state |= (1 << NET_WM_STATE_ABOVE); + } + if (!data->add_taskbar) { if (data->skip_taskbar || (ex_style & WS_EX_NOACTIVATE) @@ -3591,6 +3601,12 @@ BOOL X11DRV_SetWindowDwmConfig( HWND hwnd, INT command, const void *data ) if (!(data_ptr = get_win_data( hwnd ))) return FALSE; + /* Store the intent so update_net_wm_states can see it later */ + if (margins->cxLeftWidth == -1) + data_ptr->dwm_glass_state = TRUE; + else + data_ptr->dwm_glass_state = FALSE; + /* Runtime Visual Upgrade */ /* If the app requests "Sheet of Glass" (-1) but we are still using the default 24-bit visual, we MUST upgrade to 32-bit ARGB now. */ diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index 3266ec305f8..c9550538c87 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -694,6 +694,8 @@ struct x11drv_win_data unsigned long wm_normal_hints_serial;/* serial of last pending WM_NORMAL_HINTS request */ unsigned long configure_serial; /* serial of last pending configure request */ unsigned long net_wm_icon_serial; /* serial of last pending _NET_WM_ICON request */ + + BOOL dwm_glass_state; /* Tracks if DwmExtendFrame(-1) is active */ }; extern struct x11drv_win_data *get_win_data( HWND hwnd ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Implement DWM Region masking utilizing a dual-path strategy (OpenGL and GDI) to support hardware-accelerated overlays, DWM glass, and late-bound transparency. Hardware (OpenGL) Path: - Always prefer ARGB-capable EGL configs during OpenGL surface creation to support runtime DWM and glass toggling. - Restrict EGL_PRESENT_OPAQUE_EXT to EGLConfigs with valid alpha channels to prevent EGL_BAD_MATCH compositor crashes. - Utilize targeted glScissor clears in wayland_drawable_swap to physically punch out transparent margins in hardware-accelerated buffers. Architectural Note on glScissor: Due to architectural limitations in combining GDI and OpenGL subsurfaces within copy_pixel_region, software-based alpha masking for hardware clients is unviable. Alternative implementations utilizing the Wayland wp-viewporter protocol introduced sizing and synchronization complexities. The glScissor implementation provides a performant, state-safe, and native OpenGL mechanism to cut out margins without desyncing the Wayland compositor state. Software (GDI) Path: - Implement an Alpha Matrix inside copy_pixel_region to clear GDI base layers. - When an OpenGL client is active alongside DWM margins, GDI yields the center of the surface while preserving GDI-rendered margins (e.g., titlebars). - Recreate the Wayland SHM buffer queue dynamically as ARGB upon transparency promotion. This purges initial XRGB buffers, preventing "black box" backgrounds during an application's late-binding initialization phase. Wayland State: - Dynamically recalculate and submit wl_surface_set_opaque_region based on DWM modes, WS_EX_LAYERED, and WS_EX_TRANSPARENT flags to ensure the compositor accurately blends or skips solid window segments. --- dlls/winewayland.drv/opengl.c | 98 +++++++++++++++- dlls/winewayland.drv/waylanddrv.h | 18 +++ dlls/winewayland.drv/waylanddrv_main.c | 1 + dlls/winewayland.drv/window.c | 98 ++++++++++++++++ dlls/winewayland.drv/window_surface.c | 153 ++++++++++++++++++++++--- 5 files changed, 344 insertions(+), 24 deletions(-) diff --git a/dlls/winewayland.drv/opengl.c b/dlls/winewayland.drv/opengl.c index 9a4b14ec7d9..3ed5fc9c641 100644 --- a/dlls/winewayland.drv/opengl.c +++ b/dlls/winewayland.drv/opengl.c @@ -83,17 +83,42 @@ static void wayland_gl_drawable_sync_size(struct wayland_gl_drawable *gl) static BOOL wayland_opengl_surface_create(HWND hwnd, int format, struct opengl_drawable **drawable) { - EGLConfig config = egl_config_for_format(format); + /* Always prefer ARGB to support runtime DWM/Glass toggling */ + EGLint argb_attribs[] = { EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE }; + EGLConfig config; struct wayland_client_surface *client; EGLint attribs[4], *attrib = attribs; struct opengl_drawable *previous; struct wayland_gl_drawable *gl; RECT rect; + int count = 0; + EGLint alpha_size = 0; + struct wayland_win_data *data; + BOOL is_dwm_active = FALSE; TRACE("hwnd=%p format=%d\n", hwnd, format); if ((previous = *drawable) && previous->format == format) return TRUE; + if ((data = wayland_win_data_get(hwnd))) + { + is_dwm_active = (data->dwm_mode != WAYLAND_DWM_EXTEND_NONE); + wayland_win_data_release(data); + } + + if (funcs->p_eglChooseConfig(egl->display, argb_attribs, &config, 1, &count) && count) + { + TRACE("Selected ARGB capable EGL config for window %p\n", hwnd); + } + else + { + WARN("ARGB config not found, falling back to requested format\n"); + config = egl_config_for_format(format); + } + + /* Query the selected config for an alpha channel */ + funcs->p_eglGetConfigAttrib(egl->display, config, EGL_ALPHA_SIZE, &alpha_size); + NtUserGetClientRect(hwnd, &rect, NtUserGetDpiForWindow(hwnd)); if (rect.right == rect.left) rect.right = rect.left + 1; if (rect.bottom == rect.top) rect.bottom = rect.top + 1; @@ -102,8 +127,19 @@ static BOOL wayland_opengl_surface_create(HWND hwnd, int format, struct opengl_d WARN("Missing EGL_EXT_present_opaque extension\n"); else { - *attrib++ = EGL_PRESENT_OPAQUE_EXT; - *attrib++ = EGL_TRUE; + /* Automatically enable EGL transparency if the config supports it (ARGB). + * Standard games are protected from accidental transparency by the full-window + * wl_surface_set_opaque_region protocol applied in window.c */ + if (alpha_size > 0 || is_dwm_active) + { + *attrib++ = EGL_PRESENT_OPAQUE_EXT; + *attrib++ = EGL_FALSE; + } + else + { + *attrib++ = EGL_PRESENT_OPAQUE_EXT; + *attrib++ = EGL_TRUE; + } } *attrib++ = EGL_NONE; @@ -139,6 +175,51 @@ static void wayland_init_egl_platform(struct egl_platform *platform) egl = platform; } +static void wayland_gl_clear_margin_regions(struct wayland_gl_drawable *gl) +{ + struct wayland_win_data *data; + RECT rect; + int w, h; + + if (!(data = wayland_win_data_get(gl->base.client->hwnd))) return; + + if (data->dwm_mode == WAYLAND_DWM_EXTEND_MARGINS) + { + NtUserGetClientRect(data->hwnd, &rect, NtUserGetDpiForWindow(data->hwnd)); + + w = rect.right; + h = rect.bottom; + + if (funcs->p_glEnable && funcs->p_glScissor && funcs->p_glClear && funcs->p_glClearColor) + { + funcs->p_glEnable(GL_SCISSOR_TEST); + funcs->p_glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + /* Top margin */ + funcs->p_glScissor(0, h - data->margins.cyTopHeight, w, data->margins.cyTopHeight); + funcs->p_glClear(GL_COLOR_BUFFER_BIT); + + /* Bottom margin */ + funcs->p_glScissor(0, 0, w, data->margins.cyBottomHeight); + funcs->p_glClear(GL_COLOR_BUFFER_BIT); + + /* Left margin */ + funcs->p_glScissor(0, data->margins.cyBottomHeight, data->margins.cxLeftWidth, + h - data->margins.cyTopHeight - data->margins.cyBottomHeight); + funcs->p_glClear(GL_COLOR_BUFFER_BIT); + + /* Right margin */ + funcs->p_glScissor(w - data->margins.cxRightWidth, data->margins.cyBottomHeight, data->margins.cxRightWidth, + h - data->margins.cyTopHeight - data->margins.cyBottomHeight); + funcs->p_glClear(GL_COLOR_BUFFER_BIT); + + funcs->p_glDisable(GL_SCISSOR_TEST); + } + } + + wayland_win_data_release(data); +} + static void wayland_drawable_flush(struct opengl_drawable *base, UINT flags) { struct wayland_gl_drawable *gl = impl_from_opengl_drawable(base); @@ -146,16 +227,23 @@ static void wayland_drawable_flush(struct opengl_drawable *base, UINT flags) TRACE("drawable %s, flags %#x\n", debugstr_opengl_drawable(base), flags); if (flags & GL_FLUSH_INTERVAL) funcs->p_eglSwapInterval(egl->display, abs(base->interval)); - /* Since context_flush is called from operations that may latch the native size, * perform any pending resizes before calling them. */ - if (flags & GL_FLUSH_UPDATED) wayland_gl_drawable_sync_size(gl); + if (flags & GL_FLUSH_UPDATED) + { + wayland_gl_drawable_sync_size(gl); + /* Ensure margins are cleared during the flush/resize cycle */ + wayland_gl_clear_margin_regions(gl); + } } static BOOL wayland_drawable_swap(struct opengl_drawable *base) { struct wayland_gl_drawable *gl = impl_from_opengl_drawable(base); + /* Apply hardware scissor to mask the dwm margins before swapping. */ + wayland_gl_clear_margin_regions(gl); + client_surface_present(base->client); funcs->p_eglSwapBuffers(egl->display, gl->base.surface); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index ad41a1b474e..e064d8cba6a 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -84,6 +84,21 @@ enum wayland_surface_role WAYLAND_SURFACE_ROLE_SUBSURFACE, }; +enum wayland_dwm_extend_mode +{ + WAYLAND_DWM_EXTEND_NONE = 0, + WAYLAND_DWM_EXTEND_MARGINS = 1, + WAYLAND_DWM_EXTEND_GLASS = 2 +}; + +struct wayland_dwm_margins +{ + int cxLeftWidth; + int cxRightWidth; + int cyTopHeight; + int cyBottomHeight; +}; + struct wayland_keyboard { struct wl_keyboard *wl_keyboard; @@ -372,6 +387,8 @@ struct wayland_win_data BOOL is_fullscreen; BOOL managed; BOOL layered_attribs_set; + struct wayland_dwm_margins margins; + int dwm_mode; }; struct wayland_win_data *wayland_win_data_get(HWND hwnd); @@ -446,6 +463,7 @@ BOOL WAYLAND_SetIMECompositionRect(HWND hwnd, RECT rect); void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor); BOOL WAYLAND_SetCursorPos(INT x, INT y); void WAYLAND_SetLayeredWindowAttributes(HWND hwnd, COLORREF key, BYTE alpha, DWORD flags); +BOOL WAYLAND_SetWindowDwmConfig(HWND hwnd, INT command, const void *data); void WAYLAND_SetWindowIcons(HWND hwnd, HICON icon, const ICONINFO *ii, HICON icon_small, const ICONINFO *ii_small); void WAYLAND_SetWindowStyle(HWND hwnd, INT offset, STYLESTRUCT *style); void WAYLAND_SetWindowText(HWND hwnd, LPCWSTR text); diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index cdb5dd8a956..d81009cb871 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -45,6 +45,7 @@ static const struct user_driver_funcs waylanddrv_funcs = .pSetCursor = WAYLAND_SetCursor, .pSetCursorPos = WAYLAND_SetCursorPos, .pSetLayeredWindowAttributes = WAYLAND_SetLayeredWindowAttributes, + .pSetWindowDwmConfig = WAYLAND_SetWindowDwmConfig, .pSetWindowIcons = WAYLAND_SetWindowIcons, .pSetWindowStyle = WAYLAND_SetWindowStyle, .pSetWindowText = WAYLAND_SetWindowText, diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 07e0858fb39..cb88ad4bddc 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -298,6 +298,11 @@ static void wayland_surface_update_state_toplevel(struct wayland_surface *surfac static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) { struct wayland_surface *surface = data->wayland_surface; + struct wayland_client_surface *client = data->client_surface; + DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + struct wl_region *opaque_region = NULL; + RECT rect; + int center_w, center_h; switch (surface->role) { @@ -316,6 +321,39 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) break; } + /* GLOBAL SCISSOR: Opaque Region Calculation */ + + /* Full Surface Alpha: DWM Glass or explicit click-through overlays */ + if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS || ((ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT))) + { + opaque_region = NULL; + } + /* Partial Surface Alpha: DWM Margins */ + else if (data->dwm_mode == WAYLAND_DWM_EXTEND_MARGINS) + { + NtUserGetClientRect(data->hwnd, &rect, NtUserGetDpiForWindow(data->hwnd)); + center_w = rect.right - data->margins.cxLeftWidth - data->margins.cxRightWidth; + center_h = rect.bottom - data->margins.cyTopHeight - data->margins.cyBottomHeight; + + opaque_region = wl_compositor_create_region(process_wayland.wl_compositor); + if (center_w > 0 && center_h > 0) + { + wl_region_add(opaque_region, data->margins.cxLeftWidth, data->margins.cyTopHeight, center_w, center_h); + } + } + /* Standard Opaque */ + else + { + opaque_region = wl_compositor_create_region(process_wayland.wl_compositor); + NtUserGetWindowRect(data->hwnd, &rect, NtUserGetDpiForWindow(data->hwnd)); + wl_region_add(opaque_region, 0, 0, rect.right, rect.bottom); + } + + wl_surface_set_opaque_region(surface->wl_surface, opaque_region); + if (client && client->wl_surface) wl_surface_set_opaque_region(client->wl_surface, opaque_region); + + if (opaque_region) wl_region_destroy(opaque_region); + wl_display_flush(process_wayland.wl_display); } @@ -648,6 +686,66 @@ void WAYLAND_SetLayeredWindowAttributes(HWND hwnd, COLORREF key, BYTE alpha, DWO wayland_win_data_release(data); } +/***************************************************************** + * WAYLAND_SetWindowDwmConfig + */ +BOOL WAYLAND_SetWindowDwmConfig(HWND hwnd, INT command, const void *data) +{ + struct wayland_win_data *data_ptr; + const struct wayland_dwm_margins *margins = data; + int mode = WAYLAND_DWM_EXTEND_NONE; + int width, height; + RECT rect; + + /* DWM_CONFIG_OPAQUE_REGION (1) is passed by NtUserSetWindowDwmConfig */ + if (command != 1 || !margins) return FALSE; + if (!(data_ptr = wayland_win_data_get(hwnd))) return FALSE; + + NtUserGetClientRect(hwnd, &rect, NtUserGetDpiForWindow(hwnd)); + width = rect.right - rect.left; + height = rect.bottom - rect.top; + + /* GLASS MODE: Full-surface composition. + * 1. 'Sheet of Glass': Triggered by the -1 magic value; DWM manages full coverage. + * 2. 'Saturated Margins': Manual insets that meet or exceed client dimensions, + * effectively covering the entire surface. */ + if (margins->cxLeftWidth == -1 || + (width > 0 && height > 0 && + margins->cxLeftWidth + margins->cxRightWidth >= width && + margins->cyTopHeight + margins->cyBottomHeight >= height)) + { + mode = WAYLAND_DWM_EXTEND_GLASS; + } + /* MARGINS MODE: Partial-surface composition. + * Triggered when at least one margin is non-zero, creating a partial + * glass frame, sidebar, or 'slice' while leaving the center opaque. */ + else if (margins->cxLeftWidth > 0 || margins->cxRightWidth > 0 || + margins->cyTopHeight > 0 || margins->cyBottomHeight > 0) + { + mode = WAYLAND_DWM_EXTEND_MARGINS; + } + + /* State Sync & Invalidation + * Only trigger visual flushes if the DWM state has actually changed. */ + if (data_ptr->dwm_mode != mode || memcmp(&data_ptr->margins, margins, sizeof(struct wayland_dwm_margins))) + { + TRACE("hwnd %p setting dwm_mode %d\n", hwnd, mode); + data_ptr->dwm_mode = mode; + data_ptr->margins = *margins; + + /* Invalidate to force a surface flush/re-creation with the new format + * and hardware blanking path (if transitioning to/from ARGB). */ + NtUserRedrawWindow(hwnd, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN); + + /* Immediately recalculate Wayland protocol regions (opaque_region) */ + if (data_ptr->wayland_surface) + wayland_win_data_update_wayland_state(data_ptr); + } + + wayland_win_data_release(data_ptr); + return TRUE; +} + static enum xdg_toplevel_resize_edge hittest_to_resize_edge(WPARAM hittest) { switch (hittest) diff --git a/dlls/winewayland.drv/window_surface.c b/dlls/winewayland.drv/window_surface.c index e7595b7ccb0..cb694802246 100644 --- a/dlls/winewayland.drv/window_surface.c +++ b/dlls/winewayland.drv/window_surface.c @@ -46,6 +46,7 @@ struct wayland_window_surface struct window_surface header; struct wayland_buffer_queue *wayland_buffer_queue; BOOL layered; + int dwm_mode; }; static struct wayland_window_surface *wayland_window_surface_cast( @@ -245,18 +246,22 @@ RGNDATA *get_region_data(HRGN region) */ static void copy_pixel_region(const char *src_pixels, RECT *src_rect, char *dst_pixels, RECT *dst_rect, - HRGN region, BOOL force_opaque) + HRGN region, BOOL force_opaque, + int dwm_mode, const struct wayland_dwm_margins *margins, + BOOL has_color_key, COLORREF color_key, BOOL layered_transparent, + BOOL has_client) { static const int bpp = WINEWAYLAND_BYTES_PER_PIXEL; RGNDATA *rgndata = get_region_data(region); - RECT *rgn_rect; - RECT *rgn_rect_end; - int src_stride, dst_stride; + RECT *rgn_rect, *rgn_rect_end; + int src_stride, dst_stride, win_w, win_h; if (!rgndata) return; src_stride = (src_rect->right - src_rect->left) * bpp; dst_stride = (dst_rect->right - dst_rect->left) * bpp; + win_w = src_rect->right - src_rect->left; + win_h = src_rect->bottom - src_rect->top; rgn_rect = (RECT *)rgndata->Buffer; rgn_rect_end = rgn_rect + rgndata->rdh.nCount; @@ -278,7 +283,57 @@ static void copy_pixel_region(const char *src_pixels, RECT *src_rect, width = rc.right - rc.left; height = rc.bottom - rc.top; - /* Fast path for full width rectangles. */ + /* Only enter the Alpha Matrix if transparency manipulation is explicitly required. */ + if (dwm_mode != WAYLAND_DWM_EXTEND_NONE || has_color_key || layered_transparent) + { + int rel_x, rel_y; + UINT32 pixel, rgb; + const UINT32 *src_ptr; + UINT32 *dst_ptr; + + for (y = 0; y < height; y++) + { + src_ptr = (const UINT32 *)src; + dst_ptr = (UINT32 *)dst; + rel_y = rc.top + y - src_rect->top; + + for (x = 0; x < width; x++) + { + pixel = src_ptr[x]; + rgb = pixel & 0x00FFFFFF; + rel_x = rc.left + x - src_rect->left; + + if (dwm_mode == WAYLAND_DWM_EXTEND_MARGINS && margins) + { + BOOL in_margin = (rel_y < margins->cyTopHeight || rel_y >= win_h - margins->cyBottomHeight || + rel_x < margins->cxLeftWidth || rel_x >= win_w - margins->cxRightWidth); + + if (in_margin) dst_ptr[x] = 0; /* Punch hole: yield center to GL subsurface */ + else if (has_client) dst_ptr[x] = pixel | 0xFF000000; /* Opaque GDI borders/titlebar */ + else dst_ptr[x] = pixel | 0xFF000000; /* Standard opaque GDI backing plate */ + } + else if (dwm_mode == WAYLAND_DWM_EXTEND_GLASS) + { + if (has_client) dst_ptr[x] = 0; /* Punch hole: yield entire surface to GL */ + else if (rgb == 0) dst_ptr[x] = 0; /* Transparent GDI Glass pixel */ + else dst_ptr[x] = pixel | 0xFF000000; /* Opaque GDI Glass pixel */ + } + else if (layered_transparent || (has_color_key && rgb == (color_key & 0x00FFFFFF))) + { + dst_ptr[x] = 0; /* Fully transparent layered or color-keyed pixel */ + } + else + { + dst_ptr[x] = pixel | 0xFF000000; /* Fallback: standard opaque GDI pixel */ + } + } + src += src_stride; + dst += dst_stride; + } + return; /* Exit after matrix processing */ + } + + /* Standard Opaque Fast Paths */ if (width * bpp == src_stride && src_stride == dst_stride) { if (force_opaque) @@ -315,26 +370,34 @@ static void copy_pixel_region(const char *src_pixels, RECT *src_rect, } /********************************************************************** - * wayland_shm_buffer_copy_data + * wayland_shm_buffer_copy_data */ static void wayland_shm_buffer_copy_data(struct wayland_shm_buffer *buffer, const char *bits, RECT *rect, - HRGN region, BOOL force_opaque) + HRGN region, BOOL force_opaque, + int dwm_mode, const struct wayland_dwm_margins *margins, + BOOL has_color_key, COLORREF color_key, BOOL layered_transparent, + BOOL has_client) { RECT buffer_rect = {0, 0, buffer->width, buffer->height}; TRACE("buffer=%p bits=%p rect=%s\n", buffer, bits, wine_dbgstr_rect(rect)); - copy_pixel_region(bits, rect, buffer->map_data, &buffer_rect, region, force_opaque); + copy_pixel_region(bits, rect, buffer->map_data, &buffer_rect, region, force_opaque, + dwm_mode, margins, has_color_key, color_key, layered_transparent, has_client); } static void wayland_shm_buffer_copy(struct wayland_shm_buffer *src, struct wayland_shm_buffer *dst, - HRGN region) + HRGN region, + int dwm_mode, const struct wayland_dwm_margins *margins, + BOOL has_color_key, COLORREF color_key, BOOL layered_transparent, + BOOL has_client) { RECT src_rect = {0, 0, src->width, src->height}; RECT dst_rect = {0, 0, dst->width, dst->height}; + BOOL force_opaque = (src->format == WL_SHM_FORMAT_XRGB8888 && dst->format == WL_SHM_FORMAT_ARGB8888); TRACE("src=%p dst=%p\n", src, dst); - copy_pixel_region(src->map_data, &src_rect, dst->map_data, &dst_rect, region, - src->format == WL_SHM_FORMAT_XRGB8888 && dst->format == WL_SHM_FORMAT_ARGB8888); + copy_pixel_region(src->map_data, &src_rect, dst->map_data, &dst_rect, region, force_opaque, + dwm_mode, margins, has_color_key, color_key, layered_transparent, has_client); } /********************************************************************** @@ -372,13 +435,48 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, const BITMAPINFO *color_info, const void *color_bits, BOOL shape_changed, const BITMAPINFO *shape_info, const void *shape_bits) { - RECT surface_rect = {.right = color_info->bmiHeader.biWidth, .bottom = abs(color_info->bmiHeader.biHeight)}; struct wayland_window_surface *wws = wayland_window_surface_cast(window_surface); struct wayland_shm_buffer *shm_buffer = NULL, *latest_buffer; + struct wayland_win_data *data; BOOL flushed = FALSE; HRGN surface_damage_region = NULL; - HRGN copy_from_window_region; + HRGN copy_from_window_region = NULL; uint32_t buffer_format; + RECT surface_rect = {.right = color_info->bmiHeader.biWidth, .bottom = abs(color_info->bmiHeader.biHeight)}; + + DWORD ex_style = NtUserGetWindowLongW(window_surface->hwnd, GWL_EXSTYLE); + DWORD layered_flags = 0; + COLORREF color_key = 0; + BYTE alpha = 0; + + int dwm_mode = WAYLAND_DWM_EXTEND_NONE; + struct wayland_dwm_margins margins = {0}; + + BOOL layered = (ex_style & WS_EX_LAYERED) != 0; + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + BOOL inherently_transparent = FALSE; + BOOL dwm_active = FALSE; + BOOL has_color_key = FALSE; + BOOL has_client = FALSE; + BOOL needs_alpha = FALSE; + BOOL force_opaque = FALSE; + + /* Sync DWM and Client state */ + if ((data = wayland_win_data_get(window_surface->hwnd))) + { + dwm_mode = data->dwm_mode; + margins = data->margins; + dwm_active = (dwm_mode != WAYLAND_DWM_EXTEND_NONE); + has_client = data->client_surface != NULL; + TRACE("surface_flush dwm_mode: %d\n", dwm_mode); + wayland_win_data_release(data); + } + + /* Extract Layered Attributes to detect LWA_COLORKEY */ + if (layered && NtUserGetLayeredWindowAttributes(window_surface->hwnd, &color_key, &alpha, &layered_flags)) + { + has_color_key = (layered_flags & LWA_COLORKEY) != 0; + } surface_damage_region = NtGdiCreateRectRgn(rect->left + dirty->left, rect->top + dirty->top, rect->left + dirty->right, rect->top + dirty->bottom); @@ -388,7 +486,20 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, goto done; } - buffer_format = (shape_bits || wws->layered) ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888; + /* Inherently transparent windows natively generate or handle an alpha channel. + * This includes DWM, all layered windows (per-pixel alpha, colorkey), and shaped windows. */ + inherently_transparent = dwm_active || layered || shape_bits != NULL; + + /* Allocate an ARGB buffer if the window is inherently transparent */ + needs_alpha = inherently_transparent; + + /* Decouple GDI alpha correction from the Wayland buffer format. + * Standard GDI dialogs/popups output 0x00 alpha pixels and must be forced opaque. + * Inherently transparent windows bypass this to preserve their native alpha. */ + force_opaque = !inherently_transparent; + + buffer_format = needs_alpha ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888; + if (wws->wayland_buffer_queue->format != buffer_format) { int width = wws->wayland_buffer_queue->width; @@ -422,8 +533,8 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, } NtGdiCombineRgn(copy_from_latest_region, shm_buffer->damage_region, surface_damage_region, RGN_DIFF); - wayland_shm_buffer_copy(latest_buffer, - shm_buffer, copy_from_latest_region); + wayland_shm_buffer_copy(latest_buffer, shm_buffer, copy_from_latest_region, + dwm_mode, &margins, has_color_key, color_key, layered_transparent, has_client); NtGdiDeleteObjectApp(copy_from_latest_region); } /* ... and use the window_surface as the source of pixel data contained @@ -439,8 +550,9 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, copy_from_window_region = shm_buffer->damage_region; } - wayland_shm_buffer_copy_data(shm_buffer, color_bits, &surface_rect, copy_from_window_region, - shape_bits && !wws->layered); + wayland_shm_buffer_copy_data(shm_buffer, color_bits, &surface_rect, copy_from_window_region, force_opaque, + dwm_mode, &margins, has_color_key, color_key, layered_transparent, has_client); + if (shape_bits) wayland_shm_buffer_copy_shape(shm_buffer, rect, shape_info, shape_bits); NtGdiSetRectRgn(shm_buffer->damage_region, 0, 0, 0, 0); @@ -498,10 +610,13 @@ static struct window_surface *wayland_window_surface_create(HWND hwnd, const REC if ((window_surface = window_surface_create(sizeof(*wws), &wayland_window_surface_funcs, hwnd, rect, info, 0))) { + DWORD ex_style = NtUserGetWindowLongW(hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + struct wayland_window_surface *wws = wayland_window_surface_cast(window_surface); wws->wayland_buffer_queue = wayland_buffer_queue_create(width, height, - layered ? WL_SHM_FORMAT_ARGB8888 : + (layered || layered_transparent) ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888); wws->layered = layered; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Map Windows transparency styles and DWM configurations to Wayland input regions to ensure correct mouse hit-testing for overlays. - Ghost Mode: Assign an empty wl_region for WS_EX_TRANSPARENT, ensuring the compositor ignores the window and allows clicks to pass through to underlying applications. - Margin/Region Mode: Construct a complex wl_region masking solid frame areas, punching a physical hole in the input shape for DWM margins to allow interaction with underlying OpenGL subsurfaces. - Synchronize input regions between toplevel and OpenGL subsurfaces. - Gate dynamic toggles (e.g., hover events changing EX_TRANSPARENT) behind current.serial to prevent committing unmapped surfaces during initial window creation, preventing protocol violations. - Force surface reconfiguration to acknowledge pending compositor configures, preventing state desynchronization during dynamic style toggles. Note: Dynamic hover-toggling of WS_EX_TRANSPARENT requires an underlying Wine surface to catch focus. If toggled to Ghost mode over the native desktop, the Wayland compositor will route all subsequent hover events to the desktop environment, preventing the window from toggling back. --- dlls/winewayland.drv/window.c | 100 ++++++++++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index cb88ad4bddc..f7b10eef58e 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -187,6 +187,71 @@ static void reapply_cursor_clipping(void) NtUserSetThreadDpiAwarenessContext(context); } +static void wayland_win_data_update_input_region(struct wayland_win_data *data) +{ + struct wayland_surface *surface = data->wayland_surface; + struct wayland_client_surface *client = data->client_surface; + struct wl_region *region = NULL; + DWORD ex_style; + RECT rect; + int width, height; + + if (!surface || !surface->wl_surface) return; + + ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + + /* Ghost Mode (WS_EX_TRANSPARENT) + * Empty input region so all clicks pass through to underlying windows. */ + if ((ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT)) + { + region = wl_compositor_create_region(process_wayland.wl_compositor); + } + /* DWM Region Mode (Physical Cutout) + * Margins are physically click-through. Center client area remains solid. */ + else if (data->dwm_mode == WAYLAND_DWM_EXTEND_MARGINS) + { + int top, bottom, left, right, center_w, center_h; + + NtUserGetClientRect(data->hwnd, &rect, NtUserGetDpiForWindow(data->hwnd)); + width = rect.right; + height = rect.bottom; + + top = data->margins.cyTopHeight; + bottom = data->margins.cyBottomHeight; + left = data->margins.cxLeftWidth; + right = data->margins.cxRightWidth; + + center_w = width - left - right; + center_h = height - top - bottom; + + region = wl_compositor_create_region(process_wayland.wl_compositor); + + if (center_w > 0 && center_h > 0) + { + wl_region_add(region, left, top, center_w, center_h); + } + } + /* Standard / DWM Glass defaults to NULL (Full Surface Input) */ + + wl_surface_set_input_region(surface->wl_surface, region); + + /* Safely commit to apply dynamic toggles. Gate behind current.serial to + * prevent committing unmapped surfaces during initial window creation. */ + if (surface->current.serial || surface->role == WAYLAND_SURFACE_ROLE_SUBSURFACE) + { + wl_surface_commit(surface->wl_surface); + } + + /* Synchronize to client surface. */ + if (region && client && client->wl_surface) + { + wl_surface_set_input_region(client->wl_surface, region); + wl_surface_commit(client->wl_surface); + } + + if (region) wl_region_destroy(region); +} + static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *data, struct wayland_surface *toplevel_surface) { struct wayland_client_surface *client = data->client_surface; @@ -194,7 +259,6 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat enum wayland_surface_role role; BOOL visible; DWORD exstyle = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); - struct wl_region *input_region; TRACE("hwnd=%p\n", data->hwnd); @@ -215,14 +279,6 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat if (!(surface = data->wayland_surface) && !(surface = wayland_surface_create(data->hwnd))) return FALSE; - /* Pass through mouse events for layered, transparent windows, to match - * Windows behavior. */ - input_region = ((exstyle & WS_EX_TRANSPARENT) && (exstyle & WS_EX_LAYERED)) ? - wl_compositor_create_region(process_wayland.wl_compositor) : - NULL; - wl_surface_set_input_region(surface->wl_surface, input_region); - if (input_region) wl_region_destroy(input_region); - /* If the window is a visible toplevel make it a wayland * xdg_toplevel. Otherwise keep it role-less to avoid polluting the * compositor with empty xdg_toplevels. */ @@ -242,6 +298,9 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat if (visible && client) wayland_client_surface_attach(client, data->hwnd); wayland_win_data_get_config(data, &surface->window); + /* Apply initial Input Region */ + wayland_win_data_update_input_region(data); + /* Size/position changes affect the effective pointer constraint, so update * it as needed. */ if (data->hwnd == NtUserGetForegroundWindow()) reapply_cursor_clipping(); @@ -354,6 +413,12 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) if (opaque_region) wl_region_destroy(opaque_region); + /* Force a reconfigure to ack any pending compositor configures. */ + wayland_surface_reconfigure(surface); + + /* Update Input Region to sync with DWM state changes */ + wayland_win_data_update_input_region(data); + wl_display_flush(process_wayland.wl_display); } @@ -799,9 +864,20 @@ void WAYLAND_SetWindowStyle(HWND hwnd, INT offset, STYLESTRUCT *style) if (hwnd == NtUserGetDesktopWindow()) return; if (!(data = wayland_win_data_get(hwnd))) return; - /* Changing WS_EX_LAYERED resets attributes */ - if (offset == GWL_EXSTYLE && (changed & WS_EX_LAYERED)) - data->layered_attribs_set = FALSE; + if (offset == GWL_EXSTYLE) + { + /* Changing WS_EX_LAYERED resets attributes */ + if (changed & WS_EX_LAYERED) + data->layered_attribs_set = FALSE; + + /* If transparency flags changed, immediately recalculate the input region */ + if (data->wayland_surface && (changed & (WS_EX_LAYERED | WS_EX_TRANSPARENT))) + { + TRACE("Transparency style changed, updating input region.\n"); + wayland_win_data_update_input_region(data); + wl_display_flush(process_wayland.wl_display); + } + } wayland_win_data_release(data); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Win32 overlays and games often run in isolated processes. To maintain correct Z-order and stacking under Wayland, the compositor must be explicitly informed of these parent-child relationships. Implement zxdg_foreign_unstable_v2 to export and import xdg_toplevel surfaces across process boundaries: - Primary windows export handles converted to 16-bit Win32 Global Atoms, which are attached as window properties. - Owned processes resolve the Atom and import the surface to establish the compositor-level parent relationship. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/wayland.c | 8 + dlls/winewayland.drv/wayland_surface.c | 118 +++++++++++ dlls/winewayland.drv/waylanddrv.h | 7 + dlls/winewayland.drv/window_surface.c | 14 +- .../xdg-foreign-unstable-v2.xml | 200 ++++++++++++++++++ 6 files changed, 346 insertions(+), 2 deletions(-) create mode 100644 dlls/winewayland.drv/xdg-foreign-unstable-v2.xml diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index 1f1b2ccf8af..de4bbdc031c 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -27,6 +27,7 @@ SOURCES = \ window.c \ window_surface.c \ wlr-data-control-unstable-v1.xml \ + xdg-foreign-unstable-v2.xml \ xdg-output-unstable-v1.xml \ xdg-shell.xml \ xdg-toplevel-icon-v1.xml diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index b164210adf0..32ccfd6d0ea 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -200,6 +200,14 @@ static void registry_handle_global(void *data, struct wl_registry *registry, wl_registry_bind(registry, id, &wp_cursor_shape_manager_v1_interface, version < 2 ? version : 2); } + else if (strcmp(interface, "zxdg_exporter_v2") == 0) + { + process_wayland.zxdg_exporter_v2 = wl_registry_bind(registry, id, &zxdg_exporter_v2_interface, 1); + } + else if (strcmp(interface, "zxdg_importer_v2") == 0) + { + process_wayland.zxdg_importer_v2 = wl_registry_bind(registry, id, &zxdg_importer_v2_interface, 1); + } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 2f8275ddb52..49f840d8ea8 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -212,6 +212,29 @@ void wayland_surface_destroy(struct wayland_surface *surface) wayland_surface_clear_role(surface); + /* Tear down cross-process bonds only on absolute surface destruction. + * This ensures the zxdg_imported_v2 bond survives transient SW_HIDE toggles. */ + if (surface->zxdg_exported_v2) + { + WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + HANDLE prop = NtUserRemoveProp(surface->hwnd, prop_name); + if (prop) + { + RTL_ATOM atom = (RTL_ATOM)(ULONG_PTR)prop; + NtDeleteAtom(atom); + TRACE("Deleted exported atom %x for hwnd %p\n", atom, surface->hwnd); + } + + zxdg_exported_v2_destroy(surface->zxdg_exported_v2); + surface->zxdg_exported_v2 = NULL; + } + + if (surface->zxdg_imported_v2) + { + zxdg_imported_v2_destroy(surface->zxdg_imported_v2); + surface->zxdg_imported_v2 = NULL; + } + if (surface->wp_viewport) { wp_viewport_destroy(surface->wp_viewport); @@ -241,6 +264,60 @@ void wayland_surface_destroy(struct wayland_surface *surface) free(surface); } +/* Helper to store Wayland string handle as a global 16-bit Atom */ +static RTL_ATOM add_global_atom(const char *str) +{ + RTL_ATOM atom = 0; + size_t len = strlen(str); + WCHAR *wstr = malloc((len + 1) * sizeof(WCHAR)); + if (!wstr) return 0; + for (size_t i = 0; i < len; i++) wstr[i] = str[i]; + wstr[len] = 0; + NtAddAtom(wstr, len * sizeof(WCHAR), &atom); + free(wstr); + return atom; +} + +/* Helper to retrieve the Wayland string handle from the Atom Table */ +char *get_global_atom_name(RTL_ATOM atom) +{ + ATOM_BASIC_INFORMATION *info; + ULONG size = 1024; + char *str = NULL; + int i, len; + + if (!(info = malloc(size))) return NULL; + if (!NtQueryInformationAtom(atom, AtomBasicInformation, info, size, NULL)) + { + len = info->NameLength / sizeof(WCHAR); + if ((str = malloc(len + 1))) + { + for (i = 0; i < len; i++) str[i] = (char)info->Name[i]; + str[len] = 0; + } + } + free(info); + return str; +} + +/* Callback triggered by compositor when surface export is successful */ +static void zxdg_exported_v2_handle(void *data, struct zxdg_exported_v2 *exported, const char *handle) +{ + struct wayland_surface *surface = data; + RTL_ATOM atom = add_global_atom(handle); + if (atom) + { + /* Attach the 16-bit Atom to the Game's HWND as a window property */ + WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + NtUserSetProp(surface->hwnd, prop_name, (HANDLE)(ULONG_PTR)atom); + TRACE("Exported surface %p (hwnd %p) with handle %s (atom %x)\n", surface, surface->hwnd, handle, atom); + } +} + +static const struct zxdg_exported_v2_listener zxdg_exported_v2_listener = { + zxdg_exported_v2_handle +}; + /********************************************************************** * wayland_surface_make_toplevel * @@ -249,6 +326,8 @@ void wayland_surface_destroy(struct wayland_surface *surface) void wayland_surface_make_toplevel(struct wayland_surface *surface) { WCHAR text[1024]; + HWND owner_hwnd; + WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; TRACE("surface=%p\n", surface); @@ -267,6 +346,34 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) if (!surface->xdg_toplevel) goto err; xdg_toplevel_add_listener(surface->xdg_toplevel, &xdg_toplevel_listener, surface->hwnd); + /* CROSS-PROCESS HIERARCHY: xdg_foreign Import / Export Routing */ + owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_OWNER); + + /* Restore global import/export for all standard owned windows and overlays */ + if (owner_hwnd && process_wayland.zxdg_importer_v2) + { + HANDLE prop = NtUserGetProp(owner_hwnd, prop_name); + if (prop) + { + RTL_ATOM atom = (RTL_ATOM)(ULONG_PTR)prop; + char *handle_str = get_global_atom_name(atom); + if (handle_str) + { + TRACE("Importing surface %p (hwnd %p) to parent %p via handle %s\n", surface, surface->hwnd, owner_hwnd, handle_str); + surface->zxdg_imported_v2 = zxdg_importer_v2_import_toplevel( + process_wayland.zxdg_importer_v2, handle_str); + zxdg_imported_v2_set_parent_of(surface->zxdg_imported_v2, surface->wl_surface); + free(handle_str); + } + } + } + else if (!owner_hwnd && process_wayland.zxdg_exporter_v2) + { + surface->zxdg_exported_v2 = zxdg_exporter_v2_export_toplevel( + process_wayland.zxdg_exporter_v2, surface->wl_surface); + zxdg_exported_v2_add_listener(surface->zxdg_exported_v2, &zxdg_exported_v2_listener, surface); + } + if (process_name) xdg_toplevel_set_app_id(surface->xdg_toplevel, process_name); @@ -344,6 +451,17 @@ void wayland_surface_clear_role(struct wayland_surface *surface) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: + if (surface->zxdg_exported_v2) + { + zxdg_exported_v2_destroy(surface->zxdg_exported_v2); + surface->zxdg_exported_v2 = NULL; + } + if (surface->zxdg_imported_v2) + { + zxdg_imported_v2_destroy(surface->zxdg_imported_v2); + surface->zxdg_imported_v2 = NULL; + } + if (surface->xdg_toplevel_icon) { xdg_toplevel_icon_manager_v1_set_icon( diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index e064d8cba6a..a64a52274bd 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -38,6 +38,7 @@ #include "xdg-shell-client-protocol.h" #include "wlr-data-control-unstable-v1-client-protocol.h" #include "xdg-toplevel-icon-v1-client-protocol.h" +#include "xdg-foreign-unstable-v2-client-protocol.h" #include "windef.h" #include "winbase.h" @@ -193,6 +194,8 @@ struct wayland struct wl_data_device_manager *wl_data_device_manager; struct xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager_v1; struct wp_cursor_shape_manager_v1 *wp_cursor_shape_manager_v1; + struct zxdg_exporter_v2 *zxdg_exporter_v2; + struct zxdg_importer_v2 *zxdg_importer_v2; struct wayland_seat seat; struct wayland_keyboard keyboard; struct wayland_pointer pointer; @@ -291,6 +294,8 @@ struct wayland_surface struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct xdg_toplevel_icon_v1 *xdg_toplevel_icon; + struct zxdg_exported_v2 *zxdg_exported_v2; + struct zxdg_imported_v2 *zxdg_imported_v2; }; struct { @@ -355,6 +360,8 @@ static inline BOOL wayland_surface_is_toplevel(struct wayland_surface *surface) return surface->role == WAYLAND_SURFACE_ROLE_TOPLEVEL && surface->xdg_toplevel; } +char *get_global_atom_name(RTL_ATOM atom); + /********************************************************************** * Wayland SHM buffer */ diff --git a/dlls/winewayland.drv/window_surface.c b/dlls/winewayland.drv/window_surface.c index cb694802246..3993a36b944 100644 --- a/dlls/winewayland.drv/window_surface.c +++ b/dlls/winewayland.drv/window_surface.c @@ -455,6 +455,7 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, BOOL layered = (ex_style & WS_EX_LAYERED) != 0; BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); BOOL inherently_transparent = FALSE; + BOOL zxdg_imported = FALSE; BOOL dwm_active = FALSE; BOOL has_color_key = FALSE; BOOL has_client = FALSE; @@ -468,6 +469,14 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, margins = data->margins; dwm_active = (dwm_mode != WAYLAND_DWM_EXTEND_NONE); has_client = data->client_surface != NULL; + + /* Detect cross-process parent bond. + * Once established, we MUST use ARGB buffers to ensure transparency. */ + if (data->wayland_surface && data->wayland_surface->zxdg_imported_v2) + { + zxdg_imported = TRUE; + } + TRACE("surface_flush dwm_mode: %d\n", dwm_mode); wayland_win_data_release(data); } @@ -490,8 +499,9 @@ static BOOL wayland_window_surface_flush(struct window_surface *window_surface, * This includes DWM, all layered windows (per-pixel alpha, colorkey), and shaped windows. */ inherently_transparent = dwm_active || layered || shape_bits != NULL; - /* Allocate an ARGB buffer if the window is inherently transparent */ - needs_alpha = inherently_transparent; + /* Allocate an ARGB buffer if the window is inherently transparent, + * or if it is bound cross-process via zxdg_imported (e.g., popups on overlays). */ + needs_alpha = inherently_transparent || zxdg_imported; /* Decouple GDI alpha correction from the Wayland buffer format. * Standard GDI dialogs/popups output 0x00 alpha pixels and must be forced opaque. diff --git a/dlls/winewayland.drv/xdg-foreign-unstable-v2.xml b/dlls/winewayland.drv/xdg-foreign-unstable-v2.xml new file mode 100644 index 00000000000..cc3271dca4d --- /dev/null +++ b/dlls/winewayland.drv/xdg-foreign-unstable-v2.xml @@ -0,0 +1,200 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="xdg_foreign_unstable_v2"> + + <copyright> + Copyright © 2015-2016 Red Hat Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <description summary="Protocol for exporting xdg surface handles"> + This protocol specifies a way for making it possible to reference a surface + of a different client. With such a reference, a client can, by using the + interfaces provided by this protocol, manipulate the relationship between + its own surfaces and the surface of some other client. For example, stack + some of its own surface above the other clients surface. + + In order for a client A to get a reference of a surface of client B, client + B must first export its surface using xdg_exporter.export_toplevel. Upon + doing this, client B will receive a handle (a unique string) that it may + share with client A in some way (for example D-Bus). After client A has + received the handle from client B, it may use xdg_importer.import_toplevel + to create a reference to the surface client B just exported. See the + corresponding requests for details. + + A possible use case for this is out-of-process dialogs. For example when a + sandboxed client without file system access needs the user to select a file + on the file system, given sandbox environment support, it can export its + surface, passing the exported surface handle to an unsandboxed process that + can show a file browser dialog and stack it above the sandboxed client's + surface. + + Warning! The protocol described in this file is experimental and backward + incompatible changes may be made. Backward compatible changes may be added + together with the corresponding interface version bump. Backward + incompatible changes are done by bumping the version number in the protocol + and interface names and resetting the interface version. Once the protocol + is to be declared stable, the 'z' prefix and the version number in the + protocol and interface names are removed and the interface version number is + reset. + </description> + + <interface name="zxdg_exporter_v2" version="1"> + <description summary="interface for exporting surfaces"> + A global interface used for exporting surfaces that can later be imported + using xdg_importer. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_exporter object"> + Notify the compositor that the xdg_exporter object will no longer be + used. + </description> + </request> + + <enum name="error"> + <description summary="error values"> + These errors can be emitted in response to invalid xdg_exporter + requests. + </description> + <entry name="invalid_surface" value="0" summary="surface is not an xdg_toplevel"/> + </enum> + + <request name="export_toplevel"> + <description summary="export a toplevel surface"> + The export_toplevel request exports the passed surface so that it can later be + imported via xdg_importer. When called, a new xdg_exported object will + be created and xdg_exported.handle will be sent immediately. See the + corresponding interface and event for details. + + A surface may be exported multiple times, and each exported handle may + be used to create an xdg_imported multiple times. Only xdg_toplevel + equivalent surfaces may be exported, otherwise an invalid_surface + protocol error is sent. + </description> + <arg name="id" type="new_id" interface="zxdg_exported_v2" + summary="the new xdg_exported object"/> + <arg name="surface" type="object" interface="wl_surface" + summary="the surface to export"/> + </request> + </interface> + + <interface name="zxdg_importer_v2" version="1"> + <description summary="interface for importing surfaces"> + A global interface used for importing surfaces exported by xdg_exporter. + With this interface, a client can create a reference to a surface of + another client. + </description> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_importer object"> + Notify the compositor that the xdg_importer object will no longer be + used. + </description> + </request> + + <request name="import_toplevel"> + <description summary="import a toplevel surface"> + The import_toplevel request imports a surface from any client given a handle + retrieved by exporting said surface using xdg_exporter.export_toplevel. + When called, a new xdg_imported object will be created. This new object + represents the imported surface, and the importing client can + manipulate its relationship using it. See xdg_imported for details. + </description> + <arg name="id" type="new_id" interface="zxdg_imported_v2" + summary="the new xdg_imported object"/> + <arg name="handle" type="string" + summary="the exported surface handle"/> + </request> + </interface> + + <interface name="zxdg_exported_v2" version="1"> + <description summary="an exported surface handle"> + An xdg_exported object represents an exported reference to a surface. The + exported surface may be referenced as long as the xdg_exported object not + destroyed. Destroying the xdg_exported invalidates any relationship the + importer may have established using xdg_imported. + </description> + + <request name="destroy" type="destructor"> + <description summary="unexport the exported surface"> + Revoke the previously exported surface. This invalidates any + relationship the importer may have set up using the xdg_imported created + given the handle sent via xdg_exported.handle. + </description> + </request> + + <event name="handle"> + <description summary="the exported surface handle"> + The handle event contains the unique handle of this exported surface + reference. It may be shared with any client, which then can use it to + import the surface by calling xdg_importer.import_toplevel. A handle + may be used to import the surface multiple times. + </description> + <arg name="handle" type="string" summary="the exported surface handle"/> + </event> + </interface> + + <interface name="zxdg_imported_v2" version="1"> + <description summary="an imported surface handle"> + An xdg_imported object represents an imported reference to surface exported + by some client. A client can use this interface to manipulate + relationships between its own surfaces and the imported surface. + </description> + + <enum name="error"> + <description summary="error values"> + These errors can be emitted in response to invalid xdg_imported + requests. + </description> + <entry name="invalid_surface" value="0" summary="surface is not an xdg_toplevel"/> + </enum> + + <request name="destroy" type="destructor"> + <description summary="destroy the xdg_imported object"> + Notify the compositor that it will no longer use the xdg_imported + object. Any relationship that may have been set up will at this point + be invalidated. + </description> + </request> + + <request name="set_parent_of"> + <description summary="set as the parent of some surface"> + Set the imported surface as the parent of some surface of the client. + The passed surface must be an xdg_toplevel equivalent, otherwise an + invalid_surface protocol error is sent. Calling this function sets up + a surface to surface relation with the same stacking and positioning + semantics as xdg_toplevel.set_parent. + </description> + <arg name="surface" type="object" interface="wl_surface" + summary="the child surface"/> + </request> + + <event name="destroyed"> + <description summary="the imported surface handle has been destroyed"> + The imported surface handle has been destroyed and any relationship set + up has been invalidated. This may happen for various reasons, for + example if the exported surface or the exported surface handle has been + destroyed, if the handle used for importing was invalid. + </description> + </event> + </interface> + +</protocol> -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Win32 overlay applications often execute rapid SW_HIDE/SW_SHOW toggles, minimize off-screen, and initialize without an explicit GW_OWNER. Under Wayland, these actions sever zxdg_foreign parent bonds and permanently suspend render loops. This isolates Wayland protocol integrity from Win32 state fluctuations: - Role Integrity: Gate role clearance for overlays to prevent the compositor from severing parent bonds during visibility toggles. - Absolute Teardown: Move zxdg_exported/imported handle destruction to final surface teardown, allowing protocol bonds to survive SW_HIDE. - Render Loop Preservation: Force 1x1 geometry at the origin when imported overlays attempt off-screen (-32000) minimization. This prevents Wayland from withholding frame callbacks, which would otherwise freeze the client. - Latched Dynamic Parenting: Gated behind explicit DWM requests, dynamically infer and latch the parent window via Z-order sniffing (GW_HWNDNEXT) to safely attach orphaned overlays without asynchronous race conditions. - Subsurface Stacking Guard: Bypass local wl_subsurface stacking logic for zxdg_imported surfaces to prevent null-object protocol violations. --- dlls/winewayland.drv/wayland_surface.c | 104 +++++++++++++++++++------ dlls/winewayland.drv/waylanddrv.h | 1 + dlls/winewayland.drv/window.c | 93 +++++++++++++++++++--- 3 files changed, 167 insertions(+), 31 deletions(-) diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 49f840d8ea8..b9d8ad21b3e 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -328,6 +328,8 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) WCHAR text[1024]; HWND owner_hwnd; WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + DWORD ex_style = NtUserGetWindowLongW(surface->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); TRACE("surface=%p\n", surface); @@ -349,6 +351,16 @@ void wayland_surface_make_toplevel(struct wayland_surface *surface) /* CROSS-PROCESS HIERARCHY: xdg_foreign Import / Export Routing */ owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_OWNER); + if (layered_transparent && !owner_hwnd) + { + /* Fallback heuristic for unowned overlays */ + owner_hwnd = NtUserGetForegroundWindow(); + if (owner_hwnd == surface->hwnd || !owner_hwnd) + { + owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_HWNDNEXT); + } + } + /* Restore global import/export for all standard owned windows and overlays */ if (owner_hwnd && process_wayland.zxdg_importer_v2) { @@ -443,6 +455,17 @@ err: */ void wayland_surface_clear_role(struct wayland_surface *surface) { + DWORD ex_style = NtUserGetWindowLongW(surface->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + + /* Prevent role destruction for overlays during Win32 HIDE/SHOW toggles. + * This keeps the zxdg_foreign parent relationship and wl_surface alive. */ + if (layered_transparent && surface->role != WAYLAND_SURFACE_ROLE_NONE) + { + TRACE("Gating role clearance for overlay surface %p to preserve parent bond\n", surface); + return; + } + TRACE("surface=%p\n", surface); switch (surface->role) @@ -451,17 +474,6 @@ void wayland_surface_clear_role(struct wayland_surface *surface) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: - if (surface->zxdg_exported_v2) - { - zxdg_exported_v2_destroy(surface->zxdg_exported_v2); - surface->zxdg_exported_v2 = NULL; - } - if (surface->zxdg_imported_v2) - { - zxdg_imported_v2_destroy(surface->zxdg_imported_v2); - surface->zxdg_imported_v2 = NULL; - } - if (surface->xdg_toplevel_icon) { xdg_toplevel_icon_manager_v1_set_icon( @@ -638,6 +650,9 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface { RECT rect; + /* Guard against invalid geometry dimensions causing xdg protocol violations */ + if (width <= 0 || height <= 0) return; + /* If the window size is bigger than the current state accepts, use the * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | @@ -794,24 +809,45 @@ static void wayland_surface_reconfigure_subsurface(struct wayland_surface *surfa struct wayland_surface *toplevel_surface; int local_x, local_y, x, y; + /* Gaurd: If this surface is associated with an imported handle + * it is strictly a Toplevel client. Any attempt to use subsurface + * stacking protocols is a fatal violation. */ + if (surface->zxdg_imported_v2 || surface->zxdg_exported_v2) + { + memset(&surface->processing, 0, sizeof(surface->processing)); + return; + } + if (surface->processing.serial && surface->processing.processed && (toplevel_data = wayland_win_data_get_nolock(surface->toplevel_hwnd)) && (toplevel_surface = toplevel_data->wayland_surface)) { + /* Final Protocol Check: Ensure valid local target */ + if (!toplevel_surface->wl_surface) + { + memset(&surface->processing, 0, sizeof(surface->processing)); + return; + } + local_x = surface->window.rect.left - toplevel_surface->window.rect.left; local_y = surface->window.rect.top - toplevel_surface->window.rect.top; wayland_surface_coords_from_window(surface, local_x, local_y, &x, &y); - TRACE("hwnd=%p pos=%d,%d\n", surface->hwnd, x, y); + TRACE("hwnd=%p pos=%d,%d subsurface stacking guard active\n", surface->hwnd, x, y); - wl_subsurface_set_position(surface->wl_subsurface, x, y); - if (toplevel_data->client_surface) - wl_subsurface_place_above(surface->wl_subsurface, toplevel_data->client_surface->wl_surface); - else - wl_subsurface_place_above(surface->wl_subsurface, toplevel_surface->wl_surface); + if (surface->wl_subsurface) + { + wl_subsurface_set_position(surface->wl_subsurface, x, y); + + /* Guard: Place local subsurfaces relative only to local parents */ + if (toplevel_data->client_surface && toplevel_data->client_surface->wl_surface) + wl_subsurface_place_above(surface->wl_subsurface, toplevel_data->client_surface->wl_surface); + else + wl_subsurface_place_above(surface->wl_subsurface, toplevel_surface->wl_surface); + } + wl_surface_commit(toplevel_surface->wl_surface); - memset(&surface->processing, 0, sizeof(surface->processing)); } } @@ -826,12 +862,36 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; int win_width, win_height, width, height; + BOOL zxdg_imported = (surface->zxdg_imported_v2 != NULL); - win_width = surface->window.rect.right - surface->window.rect.left; - win_height = surface->window.rect.bottom - surface->window.rect.top; + /* PRESERVE RENDER LOOP: Do not attach a NULL buffer for off-screen overlays. + * Wayland compositors suspend frame callbacks for unmapped surfaces, which + * permanently freezes the overlay's render loop. Force 1x1 geometry instead. */ + if (zxdg_imported && (window->rect.left <= -32000 || window->rect.top <= -32000)) + { + TRACE("Trapped overlay %p off-screen; forcing 1x1 geometry to sustain frame callbacks\n", surface->hwnd); + width = 1; + height = 1; - wayland_surface_coords_from_window(surface, win_width, win_height, - &width, &height); + wayland_surface_reconfigure_xdg(surface, width, height); + wayland_surface_reconfigure_size(surface, width, height); + return TRUE; + } + + /* Standard unmap behavior for normal applications */ + if (window->rect.left <= -32000 || window->rect.top <= -32000) + { + TRACE("Trapped off-screen position %d,%d for %p; unmapping Wayland surface\n", + window->rect.left, window->rect.top, surface->hwnd); + wl_surface_attach(surface->wl_surface, NULL, 0, 0); + wl_surface_commit(surface->wl_surface); + return TRUE; + } + + win_width = window->rect.right - window->rect.left; + win_height = window->rect.bottom - window->rect.top; + + wayland_surface_coords_from_window(surface, win_width, win_height, &width, &height); TRACE("hwnd=%p window=%dx%d,%#x processing=%dx%d,%#x current=%dx%d,%#x\n", surface->hwnd, win_width, win_height, window->state, diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index a64a52274bd..853ff4b599d 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -280,6 +280,7 @@ struct wayland_shm_buffer struct wayland_surface { HWND hwnd; + HWND dynamic_owner; /* Inferred parent for overlays lacking GW_OWNER */ struct wl_surface *wl_surface; struct wp_viewport *wp_viewport; diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index f7b10eef58e..55bae9e4a56 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -258,23 +258,39 @@ static BOOL wayland_win_data_create_wayland_surface(struct wayland_win_data *dat struct wayland_surface *surface; enum wayland_surface_role role; BOOL visible; - DWORD exstyle = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + DWORD style = NtUserGetWindowLongW(data->hwnd, GWL_STYLE); + DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); TRACE("hwnd=%p\n", data->hwnd); - visible = ((NtUserGetWindowLongW(data->hwnd, GWL_STYLE) & WS_VISIBLE) == WS_VISIBLE) && - (!(exstyle & WS_EX_LAYERED) || data->layered_attribs_set); + visible = ((style & WS_VISIBLE) == WS_VISIBLE) || layered_transparent; + + if (!layered_transparent) + visible = visible && (!(ex_style & WS_EX_LAYERED) || data->layered_attribs_set); if (!visible) role = WAYLAND_SURFACE_ROLE_NONE; - else if (toplevel_surface) role = WAYLAND_SURFACE_ROLE_SUBSURFACE; + /* GATE: Overlays MUST remain TOPLEVEL to sustain the zxdg_foreign handle. + * Never allow a downgrade to SUBSURFACE even if Win32 parenting suggests it. */ + else if (toplevel_surface && !layered_transparent) role = WAYLAND_SURFACE_ROLE_SUBSURFACE; else role = WAYLAND_SURFACE_ROLE_TOPLEVEL; /* we can temporarily clear the role of a surface but cannot assign a different one after it's set */ if ((surface = data->wayland_surface) && role && surface->role && surface->role != role) { - if (client) wayland_client_surface_attach(client, NULL); - wayland_surface_destroy(data->wayland_surface); - data->wayland_surface = NULL; + /* If an overlay already has a Toplevel role, refuse to clear it. + * This preserves the active parent bond during style/visibility toggles. */ + if (layered_transparent && surface->role == WAYLAND_SURFACE_ROLE_TOPLEVEL) + { + TRACE("Preserving established Toplevel role for overlay %p\n", data->hwnd); + role = WAYLAND_SURFACE_ROLE_TOPLEVEL; + } + else + { + if (client) wayland_client_surface_attach(client, NULL); + wayland_surface_destroy(data->wayland_surface); + data->wayland_surface = NULL; + } } if (!(surface = data->wayland_surface) && !(surface = wayland_surface_create(data->hwnd))) return FALSE; @@ -358,8 +374,12 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) { struct wayland_surface *surface = data->wayland_surface; struct wayland_client_surface *client = data->client_surface; - DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); struct wl_region *opaque_region = NULL; + + DWORD ex_style = NtUserGetWindowLongW(data->hwnd, GWL_EXSTYLE); + BOOL layered = (ex_style & WS_EX_LAYERED) != 0; + BOOL layered_transparent = (ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT); + RECT rect; int center_w, center_h; @@ -369,6 +389,61 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) break; case WAYLAND_SURFACE_ROLE_TOPLEVEL: if (!surface->xdg_surface) break; /* surface role has been cleared */ + + /* Dynamic Late-ARGB / Overlay Transition Handling + * Only execute for explicit DWM Glass requests */ + if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS && !surface->zxdg_imported_v2 && process_wayland.zxdg_importer_v2) + { + HWND owner_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_OWNER); + static int latch_retries = 0; + + /* Latch the target parent window */ + if (!owner_hwnd && latch_retries < 60) + { + if (!surface->dynamic_owner) + { + HWND next_hwnd = NtUserGetWindowRelative(surface->hwnd, GW_HWNDNEXT); + + /* Strict Guard: Must be a valid window, not ourselves, and not the desktop root */ + if (next_hwnd && next_hwnd != surface->hwnd && next_hwnd != NtUserGetDesktopWindow()) + { + surface->dynamic_owner = next_hwnd; + latch_retries = 0; + } + } + owner_hwnd = surface->dynamic_owner; + latch_retries++; + } + + if (owner_hwnd) + { + WCHAR prop_name[] = {'W','a','y','l','a','n','d','H','a','n','d','l','e',0}; + HANDLE prop = NtUserGetProp(owner_hwnd, prop_name); + if (prop) + { + RTL_ATOM atom = (RTL_ATOM)(ULONG_PTR)prop; + char *handle_str = get_global_atom_name(atom); + if (handle_str) + { + TRACE("LATCHED: Dynamically importing surface %p to parent %p (retry %d)\n", surface, owner_hwnd, latch_retries); + surface->zxdg_imported_v2 = zxdg_importer_v2_import_toplevel( + process_wayland.zxdg_importer_v2, handle_str); + zxdg_imported_v2_set_parent_of(surface->zxdg_imported_v2, surface->wl_surface); + free(handle_str); + + /* Force protocol state commit to map the overlay without requiring a geometry resize */ + wl_surface_commit(surface->wl_surface); + } + } + else if (latch_retries < 60) + { + /* ASYNC RACE FIX: The target exists, but hasn't exported its handle yet. + * Clear the latch so we try again on the next frame flush. */ + surface->dynamic_owner = NULL; + } + } + } + wayland_surface_update_state_toplevel(surface); break; case WAYLAND_SURFACE_ROLE_SUBSURFACE: @@ -383,7 +458,7 @@ static void wayland_win_data_update_wayland_state(struct wayland_win_data *data) /* GLOBAL SCISSOR: Opaque Region Calculation */ /* Full Surface Alpha: DWM Glass or explicit click-through overlays */ - if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS || ((ex_style & WS_EX_LAYERED) && (ex_style & WS_EX_TRANSPARENT))) + if (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS || layered || layered_transparent ) { opaque_region = NULL; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
From: Namo Nath <nn.git@tuta.io> Fixes late-bound transparency in frameworks like .NET/WinForms by allowing Vulkan swapchains to upgrade to ARGB modes when DWM glass effects are enabled post-initialization. - State Synchronization: Adds p_get_vulkan_surface_alpha_state to vulkan_driver_funcs to safely query DWM/Glass state from the window manager driver. - Late Invalidation & Upgrade: win32u now caches the DWM alpha requirement. win32u_vkAcquireNextImageKHR detects state mismatches via the non-blocking driver callback and returns VK_ERROR_OUT_OF_DATE_KHR. This forces client recreation, allowing win32u_vkCreateSwapchainKHR to override compositeAlpha to PRE_MULTIPLIED. --- dlls/win32u/vulkan.c | 79 +++++++++++++++++++++++++++++++++-- dlls/winewayland.drv/vulkan.c | 15 +++++++ dlls/winex11.drv/vulkan.c | 16 +++++++ include/wine/vulkan_driver.h | 1 + 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/dlls/win32u/vulkan.c b/dlls/win32u/vulkan.c index 13981410e94..09aec627634 100644 --- a/dlls/win32u/vulkan.c +++ b/dlls/win32u/vulkan.c @@ -143,6 +143,7 @@ struct surface struct vulkan_surface obj; struct client_surface *client; HWND hwnd; + BOOL dwm_alpha; }; static struct surface *surface_from_handle( VkSurfaceKHR handle ) @@ -156,6 +157,7 @@ struct swapchain struct vulkan_swapchain obj; struct surface *surface; VkExtent2D extents; + BOOL dwm_alpha; }; static struct swapchain *swapchain_from_handle( VkSwapchainKHR handle ) @@ -1616,6 +1618,21 @@ static VkResult win32u_vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevi res = instance->p_vkGetPhysicalDeviceSurfaceCapabilitiesKHR( physical_device->host.physical_device, surface->obj.host.surface, capabilities ); if (!res) adjust_surface_capabilities( instance, surface, capabilities ); + + /* Sync target state via asynchronous driver callback */ + if (!res && driver_funcs->p_get_vulkan_surface_alpha_state) + { + surface->dwm_alpha = driver_funcs->p_get_vulkan_surface_alpha_state(surface->hwnd); + + /* Advertise transparency support if DWM Glass is requested */ + if (surface->dwm_alpha) + { + capabilities->supportedCompositeAlpha |= VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR | + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + TRACE("Advertising supportedCompositeAlpha for HWND %p.\n", surface->hwnd); + } + } + return res; } @@ -1642,6 +1659,21 @@ static VkResult win32u_vkGetPhysicalDeviceSurfaceCapabilities2KHR( VkPhysicalDev res = instance->p_vkGetPhysicalDeviceSurfaceCapabilities2KHR( physical_device->host.physical_device, &surface_info_host, capabilities ); if (!res) adjust_surface_capabilities( instance, surface, &capabilities->surfaceCapabilities ); + + /* Sync target state via asynchronous driver callback */ + if (!res && driver_funcs->p_get_vulkan_surface_alpha_state) + { + surface->dwm_alpha = driver_funcs->p_get_vulkan_surface_alpha_state(surface->hwnd); + + /* Advertise transparency support if DWM Glass is requested */ + if (surface->dwm_alpha) + { + capabilities->surfaceCapabilities.supportedCompositeAlpha |= VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR | + VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + TRACE("Advertising v2 supportedCompositeAlpha for HWND %p.\n", surface->hwnd); + } + } + return res; } @@ -1763,7 +1795,7 @@ static VkResult win32u_vkGetPhysicalDeviceSurfaceFormatsKHR( VkPhysicalDevice cl struct vulkan_instance *instance = physical_device->instance; return instance->p_vkGetPhysicalDeviceSurfaceFormatsKHR( physical_device->host.physical_device, - surface->obj.host.surface, format_count, formats ); + surface->obj.host.surface, format_count, formats ); } static VkResult win32u_vkGetPhysicalDeviceSurfaceFormats2KHR( VkPhysicalDevice client_physical_device, const VkPhysicalDeviceSurfaceInfo2KHR *surface_info, @@ -1787,7 +1819,7 @@ static VkResult win32u_vkGetPhysicalDeviceSurfaceFormats2KHR( VkPhysicalDevice c surface_formats = calloc( *format_count, sizeof(*surface_formats) ); if (!surface_formats) return VK_ERROR_OUT_OF_HOST_MEMORY; - res = win32u_vkGetPhysicalDeviceSurfaceFormatsKHR( client_physical_device, surface_info->surface, format_count, surface_formats ); + res = win32u_vkGetPhysicalDeviceSurfaceFormatsKHR( client_physical_device, surface_info->surface, format_count, surface_formats ); if (!res || res == VK_INCOMPLETE) for (i = 0; i < *format_count; i++) formats[i].surfaceFormat = surface_formats[i]; free( surface_formats ); @@ -1797,7 +1829,7 @@ static VkResult win32u_vkGetPhysicalDeviceSurfaceFormats2KHR( VkPhysicalDevice c surface_info_host.surface = surface->obj.host.surface; return instance->p_vkGetPhysicalDeviceSurfaceFormats2KHR( physical_device->host.physical_device, - &surface_info_host, format_count, formats ); + &surface_info_host, format_count, formats ); } static VkBool32 win32u_vkGetPhysicalDeviceWin32PresentationSupportKHR( VkPhysicalDevice client_physical_device, uint32_t queue ) @@ -1855,6 +1887,27 @@ static VkResult win32u_vkCreateSwapchainKHR( VkDevice client_device, const VkSwa create_info_host.pNext = &scaling; } + /* Sync target state via asynchronous driver callback during creation */ + if (driver_funcs->p_get_vulkan_surface_alpha_state) + { + surface->dwm_alpha = driver_funcs->p_get_vulkan_surface_alpha_state(surface->hwnd); + } + + /* Force transparent composite alpha for Wayland overlay */ + if (surface->dwm_alpha && create_info_host.compositeAlpha == VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + { + if (capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) + { + create_info_host.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + TRACE("Upgraded swapchain compositeAlpha from OPAQUE to PRE_MULTIPLIED for HWND %p.\n", surface->hwnd); + } + else if (capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) + { + create_info_host.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + TRACE("Upgraded swapchain compositeAlpha from OPAQUE to POST_MULTIPLIED for HWND %p.\n", surface->hwnd); + } + } + if (!(swapchain = calloc( 1, sizeof(*swapchain) ))) return VK_ERROR_OUT_OF_HOST_MEMORY; if ((res = device->p_vkCreateSwapchainKHR( device->host.device, &create_info_host, NULL, &host_swapchain ))) @@ -1866,6 +1919,10 @@ static VkResult win32u_vkCreateSwapchainKHR( VkDevice client_device, const VkSwa vulkan_object_init( &swapchain->obj.obj, host_swapchain ); swapchain->surface = surface; swapchain->extents = create_info->imageExtent; + + /* Track if this swapchain was successfully built with alpha */ + swapchain->dwm_alpha = (create_info_host.compositeAlpha != VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + instance->p_insert_object( instance, &swapchain->obj.obj ); *ret = swapchain->obj.client.swapchain; @@ -1927,12 +1984,26 @@ static VkResult win32u_vkAcquireNextImageKHR( VkDevice client_device, VkSwapchai RECT client_rect; VkResult res; + /* Evaluate driver state without synchronous IPC blocking. + * The driver func should read an atomic flag or cached window state updated asynchronously. */ + if (!swapchain->dwm_alpha && driver_funcs->p_get_vulkan_surface_alpha_state) + { + surface->dwm_alpha = driver_funcs->p_get_vulkan_surface_alpha_state(surface->hwnd); + } + + /* Force DXVK to recreate swapchain if DWM transparency was late-bound */ + if (surface->dwm_alpha != swapchain->dwm_alpha) + { + TRACE("DWM alpha mismatch detected for HWND %p. Requesting recreation.\n", surface->hwnd); + return VK_ERROR_OUT_OF_DATE_KHR; + } + res = device->p_vkAcquireNextImageKHR( device->host.device, swapchain->obj.host.swapchain, timeout, semaphore ? semaphore->host.semaphore : 0, fence ? fence->host.fence : 0, image_index ); if (!res && get_surface_rect( surface->hwnd, &client_rect, NtUserGetDpiForWindow( surface->hwnd ) ) && - !extents_equals( &swapchain->extents, &client_rect )) + !extents_equals( &swapchain->extents, &client_rect )) { WARN( "Swapchain size %dx%d does not match client rect %s, returning VK_SUBOPTIMAL_KHR\n", swapchain->extents.width, swapchain->extents.height, wine_dbgstr_rect( &client_rect ) ); diff --git a/dlls/winewayland.drv/vulkan.c b/dlls/winewayland.drv/vulkan.c index f8fd372757d..bad058ed4a0 100644 --- a/dlls/winewayland.drv/vulkan.c +++ b/dlls/winewayland.drv/vulkan.c @@ -97,12 +97,27 @@ static void wayland_map_device_extensions(struct vulkan_device_extensions *exten if (extensions->has_VK_KHR_external_fence_fd) extensions->has_VK_KHR_external_fence_win32 = 1; } +static BOOL wayland_vulkan_get_surface_alpha_state(HWND hwnd) +{ + struct wayland_win_data *data; + BOOL is_alpha = FALSE; + + if ((data = wayland_win_data_get(hwnd))) + { + is_alpha = (data->dwm_mode == WAYLAND_DWM_EXTEND_GLASS); + wayland_win_data_release(data); + } + + return is_alpha; +} + static const struct vulkan_driver_funcs wayland_vulkan_driver_funcs = { .p_vulkan_surface_create = wayland_vulkan_surface_create, .p_get_physical_device_presentation_support = wayland_get_physical_device_presentation_support, .p_map_instance_extensions = wayland_map_instance_extensions, .p_map_device_extensions = wayland_map_device_extensions, + .p_get_vulkan_surface_alpha_state = wayland_vulkan_get_surface_alpha_state, }; /********************************************************************** diff --git a/dlls/winex11.drv/vulkan.c b/dlls/winex11.drv/vulkan.c index 421971178a7..642c1041b78 100644 --- a/dlls/winex11.drv/vulkan.c +++ b/dlls/winex11.drv/vulkan.c @@ -94,12 +94,28 @@ static void X11DRV_map_device_extensions( struct vulkan_device_extensions *exten if (extensions->has_VK_KHR_external_fence_fd) extensions->has_VK_KHR_external_fence_win32 = 1; } +static BOOL x11_vulkan_get_surface_alpha_state(HWND hwnd) +{ + struct x11drv_win_data *data; + BOOL is_alpha = FALSE; + + if ((data = get_win_data(hwnd))) + { + is_alpha = data->dwm_glass_state; + release_win_data(data); + } + + return is_alpha; +} + + static const struct vulkan_driver_funcs x11drv_vulkan_driver_funcs = { .p_vulkan_surface_create = X11DRV_vulkan_surface_create, .p_get_physical_device_presentation_support = X11DRV_get_physical_device_presentation_support, .p_map_instance_extensions = X11DRV_map_instance_extensions, .p_map_device_extensions = X11DRV_map_device_extensions, + .p_get_vulkan_surface_alpha_state = x11_vulkan_get_surface_alpha_state, }; UINT X11DRV_VulkanInit( UINT version, void *vulkan_handle, const struct vulkan_driver_funcs **driver_funcs ) diff --git a/include/wine/vulkan_driver.h b/include/wine/vulkan_driver.h index 9c18f6d20db..c69e37f2a7f 100644 --- a/include/wine/vulkan_driver.h +++ b/include/wine/vulkan_driver.h @@ -362,6 +362,7 @@ struct vulkan_driver_funcs VkBool32 (*p_get_physical_device_presentation_support)(struct vulkan_physical_device *, uint32_t); void (*p_map_instance_extensions)( struct vulkan_instance_extensions *extensions ); void (*p_map_device_extensions)( struct vulkan_device_extensions *extensions ); + BOOL (*p_get_vulkan_surface_alpha_state)(HWND); }; #endif /* WINE_UNIX_LIB */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10180
There was a minor regression in my rebase. Apologies, I have fixed it now. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10180#note_130533
@namoninja I just wanted to send some kudos your way. This feature is something I have wanted for a long time for the sake of better support for Aero Glass applications. I also have a bug that is affected by the lack of `DwmExtendFrameIntoClientArea`: [#53548](https://bugs.winehq.org/show_bug.cgi?id=53548). I expect that it will take quite some determination to get this feature integrated. The maintainers will set a very high bar for inclusion, and there will likely be multiple rounds of design review and rework. However, you have shown great tenacity in bringing the project this far, and I am very hopeful that you can bring it to completion. I would just advise that you take it step by step making self-contained submissions with lots of unit tests. Thank you again for your work on this. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10180#note_130621
participants (3)
-
Joel Holdsworth (@jhol) -
Namo Nath -
Namo Nath (@namoninja)