Most users of my Lunar app (https://lunar.fyi) that were coming from Windows, have been praising the Windows multi monitor experience while the Mac felt limited in the handling of external monitors.
I admit that finding a way to change monitor brightness [1] or turn off individual displays [2] wasn’t as straightforward and needed a lot of reverse engineering. But macOS has a much nicer API [3] for configuring screen position, resolution, mirroring etc.
It’s not without its quirks however, I ran into a lot of bugs while developing the Blackout feature when having to create multiple mirror sets with multiple displays in each one. I finally had to reverse engineer the MonitorPanel.framework that’s used by System Preferences to get that functionality. [4]
From my understanding, Windows has a native way of disabling a display in software so this wouldn’t even be needed there.
It is macOS only, sorry if I wasn’t clear on that. I was talking about the “Lunar users that were coming from Windows” so I thought it implies that Lunar isn’t for Windows.
You can get TwinkleTray for Windows that can do most of what Lunar does: https://twinkletray.com/
The documentation--the very links that OP provided--does say all of this. Look in the "Remarks" section of EnumDisplayDevices and ChangeDisplaySettings.
Oh. This is embarrassing! I totally disregarded the remarks section when reading through all the different system calls. This example is actually quite helpful and would have saved me from hours of research. Lesson learned, i guess.
I don't blame them tbh, Microsoft seems to treat documentation that's not .NET as second class.
The Win32 documentation admittedly can be pretty good, when you can easily find what you're looking for. There's ben multiple times where I've gone looking for documentation for a still supported API only to be told "oh yeah the best option is to check out this old ass sample code from 2008"
A single code sample, that's probably poorly commented in my experience is hardly what I'd consider good documentation.
OTOH it's pretty easy to get help. People I know very skilled with the Windows API seems to be documentation themselves. I have seen RE guys casually bring up undocumented behavior of certain APIs off the top of their head.
I always found the msdn documentation quite good, in the sense that almost every aspect of each API is described. However you have to read it thoroughly, a quick skim often leaves you doing things subtly wrong. I think that's where most people fail.
The API is often complex because of how old some parts are and how often things got extended in backwards-compatible ways
It would be more interesting to find out why the order of monitors changes “randomly” (computers can't really do anything randomly unless asked specifically). It could be something simple, like background driver updates that reset options they shouldn't reset, or the powered down state of monitors on boot, or video card enumerating connected devices differently, etc.
I wrote an F# tool specifically for changing the primary display not too long ago. This is the code that calls the Win32 API to query the displays and set the primary one.
One thing I never figured out was how to map the displays you get from EnumDisplayDevices to the names you see in the display settings, or how to list them in a consistent order.
Whichever monitor's position is set to (0, 0) is the primary monitor. The following code assumes you have two monitors, and swaps the primary. Of course, to be more robust, you should not hard code the indices but do checking on the info types.
let mut num_paths = MaybeUninit::uninit();
let mut num_mode_infos = MaybeUninit::uninit();
GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, num_paths.as_mut_ptr(), num_mode_infos.as_mut_ptr());
let mut paths = vec![Default::default(); num_paths.assume_init() as _];
let mut mode_infos = vec![Default::default(); num_mode_infos.assume_init() as _];
QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, num_paths.as_mut_ptr(), paths.as_mut_ptr(), num_mode_infos.as_mut_ptr(), mode_infos.as_mut_ptr(), null_mut());
(mode_infos[1].Anonymous.sourceMode.position, mode_infos[3].Anonymous.sourceMode.position) = (mode_infos[3].Anonymous.sourceMode.position, mode_infos[1].Anonymous.sourceMode.position);
SetDisplayConfig(&paths, &mode_infos, SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES);
I have been thinking to automate "when an app enters fullscreen, disable all the non-primary screens". mostly for gaming. Thanks for the MultiMonitorTool hint as that will probably help in combination with AHK! I'm trying to avoid composite.
On a somehow related note, I can't recommend MouseUnSnag enough for multimonitors setups https://github.com/MouseUnSnag/MouseUnSnag
That's an interesting idea! I guess the problem would be that all other windows and icons would then be moved to the window that is running the fullscreen app.
MouseUnSnag solves a problem that annoyed me for a while now, but I wasn't aware that there is already a solution. Thanks!
When you have used the display config tool in Windows 9x to XP, you see why this API is like this.
The API lets you position each monitor independently on the 'virtual desktop', and sometimes there are constraints which mean certain positions aren't possible - and the API needs to give feedback on if a position, colour depth and resolution is allowed. Then you hit 'apply' and the change takes effect.
I don't really see any better way to implement the API without leaking implementation details which might change in future windows versions.
Respect to all those folks who seem to comfortably write Win32 code in Rust. I'm so used to the original C/C++ syntax I need to constantly translate between these in my brain, it hurts.
I admit that finding a way to change monitor brightness [1] or turn off individual displays [2] wasn’t as straightforward and needed a lot of reverse engineering. But macOS has a much nicer API [3] for configuring screen position, resolution, mirroring etc.
It’s not without its quirks however, I ran into a lot of bugs while developing the Blackout feature when having to create multiple mirror sets with multiple displays in each one. I finally had to reverse engineer the MonitorPanel.framework that’s used by System Preferences to get that functionality. [4]
From my understanding, Windows has a native way of disabling a display in software so this wouldn’t even be needed there.
[1] https://alinpanaitiu.com/blog/journey-to-ddc-on-m1-macs/
[2] https://lunar.fyi/#blackout
[3] https://developer.apple.com/documentation/coregraphics/quart...
[4] https://github.com/alin23/monitorpanel