una-xiv / umbra

Umbra XIV - Adds quality of life improvements to the game and consolidates common UI elements and actions into a single uniform interface.
GNU Affero General Public License v3.0
79 stars 29 forks source link

[Bug] Multi-monitor setups do not work due to width being calculated using MainViewport #133

Closed NadyaNayme closed 4 weeks ago

NadyaNayme commented 1 month ago

Attempting to open an Umbra menu when the game client is not on the Primary Monitor results in Umbra not opening the menu at all - seemingly not working.

I know multi-monitor is a bit of a 2nd class citizen in ImGui so I'm not sure if this is even a fixable problem - at least not in a "clean" way. A really "dirty" way would be allowing users to specify an X,Y offset and do the necessary math themselves for wherever MainViewport() stuff gets called which is how I've (mostly) fixed it locally.

I think this is already a known issue per the Xumbra XIV thread - mostly adding it to the Github for posterity.

haroldiedema commented 1 month ago

Would you mind filing a PR for this once you get it fully working?

NadyaNayme commented 1 month ago

If I can find an acceptable way to do it - maybe. But I'm also not sure if you want my code quality polluting your codebase. :P

For now I'll jot down any discoveries if you don't mind.

Adding a reference to System.Windows.Forms allows us access to Screen and if we also extern GetForegroundWindow() we can get the window handle of the currently active window using Screen.FromHandle()

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

As long as we capture that at a time where we 100% know FFXIV is the focused window (eg. someone tries to click on the Umbra bar) and we assume the user doesn't switch which monitor they are using to play FFXIV on we can store the monitor name of their FFXIV window. Otherwise I guess you could try and capture it every time it is needed if you're concerned players are moving FFXIV to different monitors while playing. Which some users might since "Better borderless window mode" is a thing. God I hope people don't actually do that.

Now that we have the hwnd for the monitor FFXIV is on we can get the name and size of the correct monitor.

Screen.FromHandle(hwnd).DeviceName
vpSize.X = Screen.FromHandle(hwnd).WorkingArea.X // or Width;
vpSize.Y = Screen.FromHandle(hwnd).WorkingArea.Y // or Height;

Using Screen.AllScreens we can get all of the user's displays and sizes.

foreach (var screen in AllScreens)
{
    Logger.Debug(screen.WorkingArea.ToString());
    Logger.Debug(Screen.FromHandle(hwnd: GetForegroundWindow()).DeviceName);
}

DeviceName seems to always be \\.\DISPLAY1, \\.\DISPLAY2, \\.\DISPLAY3, etc. Or at least I couldn't find a way to change the device name and was half-expecting it to be the monitor brand name so was pleasantly surprised to see it was the Display#. There's probably a way to change these but the average user won't be changing these.

Now here comes a tricky part that can probably be ignored. If their displays aren't in numerical order from left to right (eg. "2 1 3") and they play on monitor 2 there is no way of knowing that it is actually a negative offset from Monitor 1 instead of a positive offset as I don't think Screen gives the order of the windows. Given how illogical this makes mouse movements between monitors I would hope people don't do this but I'm surprised by very little at this point and I'm sure there are people who do that. (I need to test to see if Screen.AllScreens[] always returns displays in order - if so then we can actually detect "213" instead of "123".)

Assuming we can just ignore those people (as I would only hope that is <1% of all multi-monitor users) then we can get the proper X coordinate by adding the width of all monitors of lower value. So if they are on DISPLAY3 then the starting X coordinate should be DISPLAY1.WorkingArea.X + DISPLAY2.WorkingArea.X.

I don't think support for Windowed mode is possible under all these assumptions either - does Umbra even work with Windowed mode currently? I haven't tested.

E:

After testing Screen.AllScreens returns them in name order not actual display order. So determining "123" vs "213" is not possible in-so-far as I can tell.

image