[PATCH v7 0/3] MR11699: win32u and explorer: Exclude taskbar and appbars from system work area rect
This is a package of changes that fix https://bugs.winehq.org/show_bug.cgi?id=59976 and possibly other issues. It is both a usability and a compatibility improvement. Please see the commit messages for further details. Feedback on if anything here is reasonable to write conformance tests for would be appreciated. I don't know if it makes sense for the IPC things. I did a bunch of manual testing of how Windows and WINE behave using two small test programs: [appbar.c](/uploads/49ed846bdbd32bfecdb0603304f5c332/testapp.c) and [workarea.c](/-/project/5/uploads/ab8f82c70d8df1e337e8b15be5f32cab/workarea.c). --- Before these changes: {width="729" height="600"} {width="729" height="600"} Note how: * The Office 97 Shortcut Bar is covering up part of the taskbar. This is worse if it's docked to the right (which is the default), because then it covers up system tray icons. * The maximized Word 97 window is covering up both the taskbar and the Shortcut Bar. After these changes: {width="729" height="600"} You'll notice that the Word window here is still overhanging both the taskbar and the Shortcut Bar. I believe that's correct behavior as maximized windows always overhang the screen, and it just looks weird because WINE doesn't support `WS_EX_TOPMOST` yet in desktop mode. -- v7: explorer: Update system work area rect to exclude taskbar and appbars explorer: Account for the actual size of the taskbar if it is visible win32u: Apply work area changes to virtual desktop monitor serverwide https://gitlab.winehq.org/wine/wine/-/merge_requests/11699
From: Andrea Faulds <ajf@ajf.me> Before this commit: - SPI_SETWORKAREA was only affecting the result of SPI_GETWORKAREA, which meant that, for example, it didn't affect the code in dlls/win32u/window.c which determines the size of a maximized window. - SPI_SETWORKAREA was only affecting the current process, rather than all processes on the same desktop. The multiprocess aspect is impractical to test with WINE's automated tests, but manual tests with a simple program found that SPI_SETWORKAREA does in fact affect the maximized size for itself and other processes on Windows 2000 and XP. A FIXME() is added for non-virtual-desktop use of SPI_SETWORKAREA. --- dlls/win32u/sysparams.c | 62 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index fa22d6294c0..cc1e5c1d0e5 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -294,7 +294,7 @@ union sysparam_all_entry static const struct ratio no_dpi; UINT system_dpi; -static RECT work_area; +static RECT work_area_override; static DWORD process_layout = ~0u; static HDC display_dc; @@ -2809,6 +2809,16 @@ static BOOL add_virtual_source( struct device_manager_ctx *ctx ) monitor.rc_monitor.bottom = current.dmPelsHeight; monitor.rc_work.right = current.dmPelsWidth; monitor.rc_work.bottom = current.dmPelsHeight; + + /* Work area can be modified desktop-wide with SPI_SETWORKAREA */ + if (!IsRectEmpty(&work_area_override)) + { + monitor.rc_work = work_area_override; + + /* Hygiene: don't apply this more than once */ + SetRect(&work_area_override, 0, 0, 0, 0); + } + add_monitor( &monitor, ctx ); /* Expose the virtual source display modes as physical modes, to avoid DPI scaling */ @@ -5738,6 +5748,8 @@ enum spi_index /* indicators whether system parameter value is loaded */ static char spi_loaded[SPI_INDEX_COUNT]; +static RECT spi_loaded_work_area; + static struct sysparam_rgb_entry system_colors[] = { #define RGB_ENTRY(name,val,reg) { { get_rgb_entry, set_rgb_entry, init_rgb_entry, COLORS_KEY, reg }, (val) } @@ -6364,8 +6376,41 @@ BOOL WINAPI NtUserSystemParametersInfo( UINT action, UINT val, void *ptr, UINT w { if (!ptr) return FALSE; spi_idx = SPI_SETWORKAREA_IDX; - work_area = *(RECT*)ptr; - spi_loaded[spi_idx] = TRUE; + if (is_virtual_desktop()) + { + struct monitor *monitor; + struct ratio dpi = get_thread_dpi(); + + if (!lock_display_devices( FALSE )) return FALSE; + + LIST_FOR_EACH_ENTRY( monitor, &monitors, struct monitor, entry ) + { + if (!is_monitor_primary( monitor )) continue; + work_area_override = map_monitor_rect( monitor, *(RECT *)ptr, dpi, MDT_DEFAULT, no_dpi, MDT_RAW_DPI ); + break; + } + + unlock_display_devices(); + + /* Force rebuilding the monitor cache. When the virtual desktop + * monitor is recreated (see add_virtual_source()), the new work + * area will be applied and written to the registry, from which it + * will propagate to other processes. + */ + NtUserCallNoParam( NtUserCallNoParam_DisplayModeChanged ); + } + else + { + /* It's not clear if actually modifying the work area rect for + * non-virtual desktops would be wise, because that should be + * managed by the user's desktop environment or WM (see e.g. + * freedesktop's _NET_WORKAREA). + */ + spi_loaded_work_area = *(RECT*)ptr; + spi_loaded[spi_idx] = TRUE; + FIXME("SPI_SETWORKAREA(%s) for non-virtual desktop is mostly unimplemented\n", + wine_dbgstr_rect( &spi_loaded_work_area )); + } ret = TRUE; break; } @@ -6377,7 +6422,10 @@ BOOL WINAPI NtUserSystemParametersInfo( UINT action, UINT val, void *ptr, UINT w if (!ptr) return FALSE; spi_idx = SPI_SETWORKAREA_IDX; - if (!spi_loaded[spi_idx]) + /* Don't use cached value in virtual desktop mode: the new work area is + * written to the monitor struct, and other processes can change it. + */ + if (is_virtual_desktop() || !spi_loaded[spi_idx]) { struct monitor *monitor; @@ -6387,16 +6435,16 @@ BOOL WINAPI NtUserSystemParametersInfo( UINT action, UINT val, void *ptr, UINT w { if (!is_monitor_primary( monitor )) continue; monitor_get_info( monitor, &info, dpi ); - work_area = info.rcWork; + spi_loaded_work_area = info.rcWork; break; } unlock_display_devices(); spi_loaded[spi_idx] = TRUE; } - *(RECT *)ptr = work_area; + *(RECT *)ptr = spi_loaded_work_area; ret = TRUE; - TRACE("work area %s\n", wine_dbgstr_rect( &work_area )); + TRACE("work area %s\n", wine_dbgstr_rect( &spi_loaded_work_area )); break; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11699
From: Andrea Faulds <ajf@ajf.me> This means that, in desktop mode, appbars like the Microsoft Office Shortcut Bar can avoid overlapping the taskbar. --- programs/explorer/appbar.c | 42 ++++++++++++++++++++++------ programs/explorer/desktop.c | 3 +- programs/explorer/explorer_private.h | 1 + programs/explorer/systray.c | 6 ++++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/programs/explorer/appbar.c b/programs/explorer/appbar.c index 18b125257ee..46a988ebbed 100644 --- a/programs/explorer/appbar.c +++ b/programs/explorer/appbar.c @@ -73,13 +73,19 @@ struct appbar_data static struct list appbars = LIST_INIT(appbars); +static struct appbar_data* get_taskbar(void) +{ + return (struct appbar_data*)list_head( &appbars ); +} + static struct appbar_data* get_appbar(HWND hwnd) { struct appbar_data* data; LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry) { - if (data->hwnd == hwnd) + /* ignore taskbar to prevent accidental modification */ + if (data->hwnd == hwnd && data != get_taskbar()) return data; } @@ -92,7 +98,7 @@ static void send_poschanged(HWND hwnd) struct appbar_data* data; LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry) { - if (data->hwnd != hwnd) + if (data->hwnd != hwnd && data != get_taskbar()) { PostMessageW(data->hwnd, data->callback_msg, ABN_POSCHANGED, 0); } @@ -202,12 +208,9 @@ static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd) return ABS_ALWAYSONTOP | ABS_AUTOHIDE; case ABM_GETTASKBARPOS: FIXME( "SHAppBarMessage(ABM_GETTASKBARPOS, hwnd=%p): stub\n", hwnd ); - /* Report the taskbar is at the bottom of the screen. */ - abd->rc.left = 0; - abd->rc.right = GetSystemMetrics(SM_CXSCREEN); - abd->rc.bottom = GetSystemMetrics(SM_CYSCREEN); - abd->rc.top = abd->rc.bottom-1; - abd->uEdge = ABE_BOTTOM; + data = get_taskbar(); + abd->rc = data->rc; + abd->uEdge = data->edge; return TRUE; case ABM_ACTIVATE: return TRUE; @@ -288,6 +291,8 @@ static LRESULT CALLBACK appbar_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARA void initialize_appbar(void) { WNDCLASSEXW class; + struct appbar_data* data; + int taskbar_height; /* register the appbar window class */ ZeroMemory(&class, sizeof(class)); @@ -308,4 +313,25 @@ void initialize_appbar(void) ERR( "Could not create appbar message window\n" ); return; } + + /* create appbar record for the taskbar (it has a privileged status and + * isn't registered like a normal appbar, this just simplifies accounting) + */ + data = calloc( 1, sizeof(struct appbar_data) ); + if (!data) + { + ERR( "out of memory\n" ); + return; + } + data->rc.left = 0; + data->rc.right = GetSystemMetrics(SM_CXSCREEN); + data->rc.bottom = GetSystemMetrics(SM_CYSCREEN); + taskbar_height = get_taskbar_height(); + if (taskbar_height == 0) + taskbar_height = 1; /* ensure taskbar has non-zero height */ + data->rc.top = data->rc.bottom - taskbar_height; + data->edge = ABE_BOTTOM; + data->space_reserved = TRUE; + + list_add_tail(&appbars, &data->entry); } diff --git a/programs/explorer/desktop.c b/programs/explorer/desktop.c index ce70c5dd79d..4f52b93afc7 100644 --- a/programs/explorer/desktop.c +++ b/programs/explorer/desktop.c @@ -1288,9 +1288,8 @@ void manage_desktop( WCHAR *arg ) SystemParametersInfoW( SPI_SETDESKWALLPAPER, 0, NULL, FALSE ); ClipCursor( NULL ); initialize_display_settings( width, height ); - initialize_appbar(); - initialize_systray( using_root, enable_shell, show_systray, no_tray_items ); + initialize_appbar(); if (!using_root && enable_launchers) initialize_launchers( hwnd ); if ((shell32 = LoadLibraryW( L"shell32.dll" )) && diff --git a/programs/explorer/explorer_private.h b/programs/explorer/explorer_private.h index 10e8aa0653f..443b8fcaee9 100644 --- a/programs/explorer/explorer_private.h +++ b/programs/explorer/explorer_private.h @@ -27,5 +27,6 @@ extern void initialize_appbar(void); extern void handle_parent_notify( HWND hwnd, WPARAM wp ); extern void do_startmenu( HWND owner ); extern LRESULT menu_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); +extern int get_taskbar_height(void); #endif /* __WINE_EXPLORER_PRIVATE_H */ diff --git a/programs/explorer/systray.c b/programs/explorer/systray.c index 69d3ec61b6a..175bc4d13a0 100644 --- a/programs/explorer/systray.c +++ b/programs/explorer/systray.c @@ -1097,6 +1097,12 @@ static void do_show_systray(void) sync_taskbar_buttons(); } +/* for use by appbar.c's accounting */ +int get_taskbar_height(void) +{ + return enable_taskbar ? tray_height : 0; +} + static LRESULT WINAPI shell_traywnd_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { switch (msg) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11699
From: Andrea Faulds <ajf@ajf.me> Without this, while appbars are positioned correctly, maximized windows will still cover them. Manual testing on Windows 2000 and XP found that the work area rect seems to be managed by explorer (it gets reset when explorer is started, registering appbars appears to change the work area, and killing explorer makes the work area stop changing). Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59976 --- programs/explorer/appbar.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/programs/explorer/appbar.c b/programs/explorer/appbar.c index 46a988ebbed..09d8ad1290c 100644 --- a/programs/explorer/appbar.c +++ b/programs/explorer/appbar.c @@ -111,7 +111,7 @@ static void appbar_cliprect( HWND hwnd, RECT *rect ) struct appbar_data* data; LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry) { - if (data->hwnd == hwnd) + if (hwnd != NULL && data->hwnd == hwnd) { /* we only care about appbars that were added before this one */ return; @@ -138,6 +138,26 @@ static void appbar_cliprect( HWND hwnd, RECT *rect ) } } +/* update the system work area rectangle to exclude the taskbar and appbars + * (affects what part of the screen maximized windows cover for example) + */ +static void update_work_area(void) +{ + RECT rc = { 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; + + /* When not running in desktop mode, don't set the work area in order to + * avoid interfering with the non-WINE desktop environment's window + * management. (See for example freedesktop's _NET_WORKAREA.) + */ + if (get_taskbar_height() == 0) + return; + + appbar_cliprect( NULL, &rc ); + SystemParametersInfoW( SPI_SETWORKAREA, 0, &rc, 0 ); + + /* TODO: move and resize existing windows to fit the new work area */ +} + static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd) { struct appbar_data* data; @@ -172,6 +192,8 @@ static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd) send_poschanged(hwnd); free( data ); + + update_work_area(); } else WARN( "removing hwnd %p not on the list\n", hwnd ); return TRUE; @@ -197,6 +219,8 @@ static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd) data->edge = abd->uEdge; data->rc = abd->rc; data->space_reserved = TRUE; + + update_work_area(); } else { @@ -334,4 +358,6 @@ void initialize_appbar(void) data->space_reserved = TRUE; list_add_tail(&appbars, &data->entry); + + update_work_area(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11699
On Thu Aug 20 22:47:16 2026 +0000, Andrea Faulds wrote:
changed this line in [version 7 of the diff](/wine/wine/-/merge_requests/11699/diffs?diff_id=292211&start_sha=c1a41f5d5af6bf4d9743fce9e7d0e820ab11c476#85770a8b187bd82db4dbb9a2b8a5f34616049d0f_6414_6395) I reworked it as you suggested and it works well. Thank you very much for the feedback, it helped me to finally understand how the monitor caching system is meant to work. I was quite confused by the registry aspect before.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11699#note_149481
participants (2)
-
Andrea Faulds -
Andrea Faulds (@hikari_no_yume)