Skip to content

Fix window positioning on Windows when the taskbar is on the top or left#12933

Merged
rustdesk merged 4 commits into
rustdesk:masterfrom
logiclrd:window-position-relative-to-work-area
Sep 19, 2025
Merged

Fix window positioning on Windows when the taskbar is on the top or left#12933
rustdesk merged 4 commits into
rustdesk:masterfrom
logiclrd:window-position-relative-to-work-area

Conversation

@logiclrd
Copy link
Copy Markdown
Contributor

@logiclrd logiclrd commented Sep 16, 2025

In Windows versions prior to Windows 11, the Task Bar can be docked to any edge of the screen. Even with Windows 11, this can still be done with third-party tools such as "StartAllBack".

When RustDesk creates its main window, it (currently) supplies a hardcoded window position of (10, 10):

Win32Window::Point origin(10, 10);

If the Task Bar is on the top or left edge of the screen, the pixel offset (10, 10) might be covered by the task bar. Part of the RustDesk window is thus not visible and inaccessible:

image

With no visible title bar, the window cannot be dragged to reposition it, and window control widgets located in the title bar are inaccessible. (There is a non-obvious workaround; when a window resize operation completes, Windows automatically crops the resulting rectangle to the active work area, so using any of the visible edges to resize the window causes the top edge of the window to be moved down making the title bar visible.)

This PR addresses this by querying the work area. This indicates the exact coordinate range that is not covered by permanent fixtures such as the Task Bar. The origin and size are then computed based on this.

The work area is in virtual pixels. If the system desktop scale is not 100%, then the work area is correspondingly scaled. For instance, I'm presently using a 50" TV as my display, native resolution 1920x1080, and if I run it at the standard assumed 96 dpi, the text is difficult to read. So, my desktop scale ("Make everything bigger" in Display settings) is set to 150%. As a result, the work area is returned as (0, 40)-(1280, 720), instead of the actual literal pixel area of (0, 60)-(1920, 1080).

But, this is okay, because the Win32Window abstraction being used has, in its CreateAndShow method, code to find the monitor's DPI and scale the coordinates correspondingly. So, the coordinate system for origin is the same as the scaled frame of reference used for the work area (*as long as origin is on the primary monitor -- if not, they might be the same but they might not, but in any case the current hardcoded (10, 10) will always be on the primary monitor).

I wasn't able to get a build environment set up. The rustup_init.exe tool created a bin folder with all the requisite .exe files, except they're all 0-byte files. Not sure why. So, I tested this code piecemeal, by copy/pasting it into a smaller project that I can build. It worked there. But, it might require tweaks in situ. I know that's a pain in the butt, and if I figure out why Rust didn't install properly, I'll see if I can't build it locally. In the meantime, I'm hoping someone with a working build environment will take pity on me and do a local build and push any fixes. :-)

Added code to wWinMain in main.cpp to position the window relative to the work area, which may not be at (0, 0) depending on the user's configuration.
@logiclrd logiclrd changed the title Fix window positioning when the taskbar is on the top or left Fix window positioning on Windows when the taskbar is on the top or left Sep 16, 2025
@rustdesk rustdesk requested review from Copilot and fufesou September 16, 2025 14:49
@rustdesk
Copy link
Copy Markdown
Owner

@fufesou

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes window positioning on Windows when the taskbar is positioned on the top or left edges of the screen by dynamically querying the work area instead of using hardcoded coordinates.

  • Replaces hardcoded window position (10, 10) with dynamic positioning based on the system work area
  • Adds new Win32Desktop utility module to query the available work area using SystemParametersInfoA
  • Calculates window size to ensure it fits within the available work area boundaries

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
flutter/windows/runner/win32_desktop.h Header file defining the GetWorkArea function interface
flutter/windows/runner/win32_desktop.cpp Implementation of GetWorkArea to query system work area using Windows API
flutter/windows/runner/main.cpp Updates window positioning logic to use work area-based coordinates
flutter/windows/runner/CMakeLists.txt Adds the new win32_desktop.cpp source file to the build

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

{
RECT workAreaRect;

if (!SystemParametersInfoA(SPI_GETWORKAREA, 0, &workAreaRect, 0))
Copy link

Copilot AI Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using SystemParametersInfoA (ANSI version) is unnecessary here since no string parameters are involved. Consider using SystemParametersInfo or SystemParametersInfoW for consistency with modern Windows development practices.

Suggested change
if (!SystemParametersInfoA(SPI_GETWORKAREA, 0, &workAreaRect, 0))
if (!SystemParametersInfo(SPI_GETWORKAREA, 0, &workAreaRect, 0))

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no function SystemParametersInfo. It's just a macro that calls either SystemParametersInfoA or SystemParametersInfoW. The parameter in this instance doesn't involve any string values at all, so it is irrelevant which entrypoint is used. I noticed that other existing code uses the ANSI functions for Win32 API calls, such as:

  • LoadLibraryA in main.cpp, win32_window.cpp
  • GetEnvironmentVariableA in wf_cliprdr.c
  • LoadLibraryExA, GetModuleHandleExA, LoadCursorA, RegisterClassExA, CreateWindowExA, FindWindowExA in mag.rs
  • FormatMessageA in lddController.c
  • ShellExecuteA in virtual_display_manager.rs

These existing ANSI calls were what I immediately saw in the surrounding code, so I used SystemParametersInfoA to follow suit.

But, I do recognize that using the wide-character functions is generally preferable. It avoids an extra layer of indirection in the use of the thunk, and if actual character strings are involved, it also avoids issues where the ANSI code page might not support characters being used, making it impossible to access certain files, properly display messages, etc. Those concerns are irrelevant to a call to SystemParametersInfo with action SPI_GETWORKAREA.

Should I change this call to use the wide-character entrypoint, or leave it using the ANSI endpoint for consistency with adjacent code?

Comment thread flutter/windows/runner/main.cpp Outdated
@fufesou
Copy link
Copy Markdown
Collaborator

fufesou commented Sep 16, 2025

Maybe a better way would be to center the main window on the first run?

Because the other windows are initially centered.

flutter/lib/common.dart

--- a/flutter/lib/common.dart
+++ b/flutter/lib/common.dart
@@ -1949,8 +1949,18 @@ Future<bool> restoreWindowPosition(WindowType type,

   var lpos = LastWindowPosition.loadFromString(pos);
   if (lpos == null) {
-    debugPrint("no window position saved, ignoring position restoration");
-    return false;
+    debugPrint("no window position saved, try to center the window");
+    switch (type) {
+      case WindowType.Main:
+        await windowManager.center();
+        break;
+      default:
+        // No need to change the position of sub window if no position is saved.
+        // Because the default position is already center.
+        // https://github.com/rustdesk/rustdesk/blob/317639169359936f7f9f85ef445ec9774218772d/flutter/lib/utils/multi_window_manager.dart#L163
+        break;
+    }
+    return true;
   }
   if (type == WindowType.RemoteDesktop || type == WindowType.ViewCamera) {
     if (!isRemotePeerPos && windowId != null) {

@logiclrd
Copy link
Copy Markdown
Contributor Author

logiclrd commented Sep 16, 2025

Maybe a better way would be to center the main window on the first run?

Property centering the window still requires calculations based on the work area. It's not a bad idea, but it does feel out-of-scope for this PR.

@logiclrd
Copy link
Copy Markdown
Contributor Author

logiclrd commented Sep 16, 2025

    await windowManager.center();

For what it's worth, the window_manager component's center function also gets this wrong. It doesn't factor encroachments into the work area into the calculation. Unless I'm misreading the code, it centres the window relative to the display's raw bounds, without any consideration for space permanently subtracted from the usable area by the taskbar and anything else docked at the edges.

https://github.com/leanflutter/window_manager/blob/625d44d2746bfbf1e843321b066d8e9f77951e05/packages/window_manager/lib/src/utils/calc_window_position.dart#L12-L25

The resulting offset is then passed to an API function that expects coordinates that are relative to the work area. The result depends, then, on whether the work area is offset or not.

In the common case, where the work area is at (0, 0) and the task bar is at the bottom of the screen, the centring simply ignores the task bar. The space above the window is larger than the space below the window.

If the task bar is moved to the top edge of the screen, then it computes an offset as though the work area is at (0, 0), and then passes it into a Win32 API function that treats the coordinates it gets as being relative to the work area, not the raw display bounds. The result is that, since the work area actually starts at e.g. (0, 40), the window is pushed down by 40 pixels (or whatever the task bar's encroachment is). Once again, the space above the window is larger than the space below it, because the window is much lower on the screen than it should be.

If the task bar is on the left or right edge, then the same effects happen but along the X axis instead of the Y axis.

This is, of course, not a bug in RustDesk.

@fufesou
Copy link
Copy Markdown
Collaborator

fufesou commented Sep 17, 2025

Property centering the window still requires calculations based on the work area. It's not a bad idea, but it does feel out-of-scope for this PR.

Yes. But maybe there is no need to calculate the work area.

RustDesk will call restoreWindowPosition() after the window is initialized.

If the patch is applied #12933 (comment) , then no need to do the calculation in rustdesk/flutter/windows/runner/main.cpp.

@logiclrd
Copy link
Copy Markdown
Contributor Author

Though, the user could reconfigure their desktop between runs. If they launch it with the taskbar on the bottom of the screen, then move the window near the top of the screen, then exit, then move the taskbar to the top of the screen, on the next launch, it will be behind the taskbar. Contrived, yes, but tangible proof of a flaw in the algorithm.

@fufesou
Copy link
Copy Markdown
Collaborator

fufesou commented Sep 17, 2025

Though, the user could reconfigure their desktop between runs. If they launch it with the taskbar on the bottom of the screen, then move the window near the top of the screen, then exit, then move the taskbar to the top of the screen, on the next launch, it will be behind the taskbar. Contrived, yes, but tangible proof of a flaw in the algorithm.

I don't think there's any need to care about complex cases.

RustDesk will save the position and size of the window, and it will restore the window on the next start.

All that needs to be considered is:

  1. The position and size of the first run.
  2. Whether the logic for restoring the position and size is correct.

@logiclrd
Copy link
Copy Markdown
Contributor Author

The logic for saving and restoring the window position also needs to take the work area into account, because the meaningful numbers are relative to the work area, not relative to the monitor boundaries. If the window is at (10, 70) because the taskbar is using the first 60 pixels, then the saved origin should be (10, 10), and then when it's being restored, if the taskbar is still using the first 60 pixels, it gets added back in to produce (10, 70). If the taskbar has been changed in height or moved elsewhere, then the computed offset is different, but the window is still in the same place relative to the desktop. The desktop icons will also move around with the work area.

@fufesou
Copy link
Copy Markdown
Collaborator

fufesou commented Sep 17, 2025

This might be a bit of an overkill.
The taskbar's position on the desktop doesn't change often. We typically just remember its last position and restore it to the same location next time.
Your concern is certainly valid; it would be bad if the taskbar's position changes obscured some windows, or even made them impossible to move. However, this would also add more complex logic to the Flutter code, requiring the workspace offset to be retrieved each time a task is saved and restored. As you said, window_manager https://github.com/leanflutter/window_manager does not take it into acount.
This might not be worth the effort.

I think we are currently just considering centering the window, just like the install window and remote window by default.

@logiclrd
Copy link
Copy Markdown
Contributor Author

From where I'm sitting, calling a 20-line function that calls a system API that only deals in numbers and (probably) can't fail "overkill" is a bit weird, but okay. :-) I am already pursuing correcting WindowManager::center upstream. At present, they aren't centered correctly, but that isn't the fault of this codebase.

@fufesou
Copy link
Copy Markdown
Collaborator

fufesou commented Sep 18, 2025

Win7, Win10, Win11 are tested, the work areas are correct.

Please:

  1. Fix the build issue, min() should be std::min().
  2. Maybe centering the main window is also needed. Fix window positioning on Windows when the taskbar is on the top or left #12933 (comment)

@logiclrd
Copy link
Copy Markdown
Contributor Author

My feeling is that centering is outside of the scope of this PR. What I'll do is, I'll make a branch off of this branch and add centering to that, and then I'll make a PR based on that. Then, if you merge this PR, I can rebase that PR as needed, or if you just merge that PR it'll include the work area-related enhancements anyway, either way.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread flutter/windows/runner/win32_desktop.cpp
Comment thread flutter/windows/runner/main.cpp Outdated
…n and size as containing an existing window rectangle, and to find the monitor that contains or is closest to that window.

Added function FitToWorkArea to win32_desktop.cpp/.h.
Updated main.cpp to use Win32Desktop::FitToWorkArea instead of explicitly constraining the size.
@logiclrd
Copy link
Copy Markdown
Contributor Author

In consideration of the distinct possibility of adding code to persist the window bounds in the future, I've made some architectural changes. It's still within the bounds of scope for this PR, but there is now a function Win32Desktop::FitToWorkArea that encapsulates exactly the work needed to take a previously-saved set of bounds and make sure they're still sane.

I will shortly rebase the centre-main-window branch ontop of this.

@rustdesk rustdesk merged commit 2d1c94f into rustdesk:master Sep 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants