[PATCH v8 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. -- v8: explorer: Update system work area rect to exclude taskbar and appbars explorer: Account for the actual size of the taskbar if it is visible 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 | 25 +++++++++++++++++++++---- programs/explorer/desktop.c | 3 +-- programs/explorer/explorer_private.h | 1 + programs/explorer/systray.c | 6 ++++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/programs/explorer/appbar.c b/programs/explorer/appbar.c index 18b125257ee..eb4aec42e8a 100644 --- a/programs/explorer/appbar.c +++ b/programs/explorer/appbar.c @@ -99,10 +99,30 @@ static void send_poschanged(HWND hwnd) } } +static RECT get_taskbar_rect(void) +{ + RECT rc; + int taskbar_height; + rc.left = 0; + rc.right = GetSystemMetrics(SM_CXSCREEN); + rc.bottom = GetSystemMetrics(SM_CYSCREEN); + taskbar_height = get_taskbar_height(); + if (taskbar_height == 0) + taskbar_height = 1; /* ensure taskbar has non-zero height */ + rc.top = rc.bottom - taskbar_height; + return rc; +} + /* appbar_cliprect: cut out parts of the rectangle that interfere with existing appbars */ static void appbar_cliprect( HWND hwnd, RECT *rect ) { + RECT taskbar_rect; struct appbar_data* data; + + /* move in the side that corresponds to the taskbar's top edge */ + taskbar_rect = get_taskbar_rect(); + rect->bottom = min(rect->bottom, taskbar_rect.top); + LIST_FOR_EACH_ENTRY(data, &appbars, struct appbar_data, entry) { if (data->hwnd == hwnd) @@ -203,10 +223,7 @@ static UINT_PTR handle_appbarmessage(DWORD msg, struct appbar_data_msg *abd) 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->rc = get_taskbar_rect(); abd->uEdge = ABE_BOTTOM; return TRUE; case ABM_ACTIVATE: 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 | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/programs/explorer/appbar.c b/programs/explorer/appbar.c index eb4aec42e8a..1e856240599 100644 --- a/programs/explorer/appbar.c +++ b/programs/explorer/appbar.c @@ -152,6 +152,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; @@ -186,6 +206,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; @@ -211,6 +233,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 { @@ -325,4 +349,6 @@ void initialize_appbar(void) ERR( "Could not create appbar message window\n" ); return; } + + update_work_area(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11699
This is getting better, at least on the work area override. Then looking at the following commits about appbars, it makes me think that the taskbar itself should probably register as an appbar, and update its position whenever it moves. The appbar / work area relation could be worth testing, to figure for instance how calling SPI_SETWORKAREA separately from appbar interacts with them, can it override the area reserved for appbars? For the taskbar itself? etc... I'm hoping in the end that we could make this work in an unified way, virtual desktop or not, with the only difference that in non-virtual desktop the taskbar window would be invisible and positioned where the host taskbar is located. The appbars / work area would still take it into account as it should, and things like ABM_GETTASKBARPOS could work too (right now we only include that space in monitor work area). -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11699#note_149533
On Fri Aug 21 15:44:33 2026 +0000, Rémi Bernon wrote:
This is getting better, at least on the work area override. Then looking at the following commits about appbars, it makes me think that the taskbar itself should probably register as an appbar[^1], and update its position whenever it moves. The appbar / work area relation could be worth testing, to figure for instance how calling SPI_SETWORKAREA separately from appbar interacts with them, can it override the area reserved for appbars? For the taskbar itself? etc... I'm hoping in the end that we could make this work in an unified way, virtual desktop or not, with the only difference that in non-virtual desktop the taskbar window would be invisible and positioned where the host taskbar is located. The appbars / work area would still take it into account as it should, and things like ABM_GETTASKBARPOS could work too (right now we only include that space in monitor work area). [^1]: Whether it should could checked with a test, but https://stackoverflow.com/questions/76010249/calling-shappbarmessage-to-set-... makes me believe it does. Having the taskbar register itself as an appbar is an interesting idea. I had simply assumed that isn't how Windows does it, but I didn't consider it would be possible to test. I'll poke around a bit.
Regarding unified behavior: this does seem like the ideal, but I'm a bit scared of how complicated things may become once we're trying to account for the host desktop environment and the many different shapes (GNOME, KDE, the macOS Dock, etc) and APIs (X11, Wayland, Cocoa) that might have. Sometimes there are several bars on-screen and I don't know how you'd decide which should be the “taskbar”, or if WINE would even have enough information available to make that decision. But seeing how many cues [the freedesktop model](https://specifications.freedesktop.org/wm/1.5/ar01s03.html) seems to take from the Windows one, I would nonetheless be interested in making _some_ attempt at better integration here in a later MR. For the time being I'm just hoping to not break anything outside of virtual desktop mode. For that reason, if I change it so the WINE taskbar registers itself as an appbar, I would want to maintain the current behavior of claiming to be 1 pixel high when not in virtual desktop mode. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11699#note_149537
participants (3)
-
Andrea Faulds -
Andrea Faulds (@hikari_no_yume) -
Rémi Bernon (@rbernon)