MartinGC94 / DisplayConfig

PowerShell module for configuring Windows display settings
MIT License
41 stars 2 forks source link

Error when loading serialized display configs #1

Closed Lucide closed 6 months ago

Lucide commented 6 months ago

I recently started to see the following error when trying to load any previously made serialized display config:

Import-Clixml .\60Hz.xml | Use-DisplayConfig
Use-DisplayConfig : The parameter is incorrect
At line:1 char:28
+ Import-Clixml .\60Hz.xml | Use-DisplayConfig
+                            ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (MartinGC94.Disp...I.DisplayConfig:DisplayConfig) [Use-DisplayConfig], Win32Exception
    + FullyQualifiedErrorId : UseDisplayConfigError,MartinGC94.DisplayConfig.Commands.UseDisplayConfigCommand

I've made no modifications to my physical display setup and the tool already survived several reboots when I implemented it on my automation script yesterday. I've also not seen any DisplayId reassignment issues:

Get-DisplayInfo     

 DisplayId DisplayName      Active  Primary Position    Mode                  ConnectionType
 --------- -----------      ------  ------- --------    ----                  --------------
         1 DP monitor        False    False 0 0                                          DVI
         2 ASUS MX239         True     True 0 0         1920x1080@60 Hz          DisplayPort
         3 MStar Demo         True    False 1920 264    1920x1080@60 Hz                 HDMI

The error also happens when the current setup and the one in the config file are the same, but something like Get-DisplayConfig | Use-DisplayConfig works.

I suspect I've stumbled upon the well known display identification problem that plagues tools like this. Something in the windows presentation stack is nondeterministic and causes identifiers to be invalid/reassigned. Here I'm seeing the same DisplayId tho. The only tool (that I know of) that managed to overcome the issue well enough is "DisplayMagician", but it does also talk to the gpu drivers directly.

Might also be a completely unrelated issue. I've tested the module successfully just yesterday, but I've also made zero physical changes to my setup. I did change display configuration in the windows settings prior to this behaviour. (nothing special, just enabled only one display and set a 1024x768 resolution).

Here an example config file: 60Hz.xml.txt.

windows 10, on windows powershell and powershell 7

MartinGC94 commented 6 months ago

What is that "DP monitor" display that you have? I see it's listed as a DVI connection and since that's not a particular popular connector these days + the generic name I'm guessing it's a synthetic display created with a tool like Nvidia surround. Am I correct?

Regarding this:

The error also happens when the current setup and the one in the config file are the same, but something like Get-DisplayConfig | Use-DisplayConfig works.

Just to confirm when you say that the current setup is the same in the config file do you mean that this works: Get-DisplayConfig | Use-DisplayConfig but this doesn't:

$Path = "$HOME\TestDisplayConfig.xml"
Get-DisplayConfig | Export-Clixml -LiteralPath $Path
Import-Clixml -LiteralPath $Path | Use-DisplayConfig

? If so then it's just a serialization/deserialization issue that should be fairly simple to fix.

Lucide commented 6 months ago

What is that "DP monitor" display that you have? I see it's listed as a DVI connection and since that's not a particular popular connector these days + the generic name I'm guessing it's a synthetic display created with a tool like Nvidia surround. Am I correct?

Good catch. That's not quite a virtual display but a hardware dummy plug, basically a displayport connector with an edid chip. I was puzzled too by the "DVI" label. My guess is that they put a DVI edid chip in a DP connector or something like that. But it's a physical display in all aspects.

Just to confirm when you say that the current setup is the same in the config file do you mean that this works: Get-DisplayConfig | Use-DisplayConfig but this doesn't: [...]

No, that works too. What I meant is that when loading a config file in a current configuration that is the same (i.e., should theoretically result in a no-op), the command still fails. The config file was made yesterday, if I put manually the pc in the same display configuration, and I load the conf file, it still crashes.

MartinGC94 commented 6 months ago

I see. If you manually change your config to what you had yesterday and then export a new config and then compare the 2 XML files in Notepad++ with the compare plugin or something similar maybe that will give us a clue about what has changed.

You can also try Use-DisplayConfig -AllowChanges and see if that helps. This allows the API to make small adjustments to the specified config to make it valid (for example changing a rounded 60 Hz value to the correct 59.951Hz).

Lucide commented 6 months ago

Here a git diff between the old conf file "60Hz.xml" and the current one: diff.diff.txt.

I see mostly changed Ids 🤔

Use-DisplayConfig -AllowChanges brings the same result.

I suspect I've stumbled upon the well known display identification problem that plagues tools like this. Something in the windows presentation stack is nondeterministic and causes identifiers to be invalid/reassigned. Here I'm seeing the same DisplayId tho. The only tool (that I know of) that managed to overcome the issue well enough is "DisplayMagician", but it does also talk to the gpu drivers directly.

For context, here I'm talking about what I've seen in the past years here. That is the tool I used to use before DisplayMagician and I remember the author having troubles identifying displays reliably (and most importantly, deterministically). Despite his efforts the tool still suffered occasionally this issue.

MartinGC94 commented 6 months ago

I managed to reproduce it on my system so my adapter ID also changed. I've added a new switch parameter to Use-DisplayConfig called UpdateAdapterIds when you use this it updates all the old ID references to the current IDs which fixes the issue. You can view the code change here: https://github.com/MartinGC94/DisplayConfig/commit/61957d4a3b6b55dbe6a363984e1f79c669e5aff4

If you can download and compile the module yourself and test out these changes that would be great, otherwise I'll publish the new version to the PS gallery tomorrow.

Lucide commented 6 months ago

So, after much tinkering I managed to build the module. I'm not a C# dev and my knowledge is limited to its interop features to wrap win32 calls (and Add-Type 😬). I've loaded the built module in a session with Import-Module .\DisplayConfig\1.0.6\DisplayConfig -Force, to override the PSGallery one. I see from

Get-Command -All Use-DisplayConfig

CommandType     Name                                               Version    Source
-----------     ----                                               -------    ------
Cmdlet          Use-DisplayConfig                                  1.0.6      DisplayConfig

that I'm using the new one. Even Get-Help Use-DisplayConfig shows the updated help. But for the life of me I can't figure out why I keep getting

Import-Clixml .\60Hz.xml | Use-DisplayConfig -UpdateAdapterIds
Use-DisplayConfig : A parameter cannot be found that matches parameter name 'UpdateAdapterIds'.
At line:1 char:46
+ Import-Clixml .\60Hz.xml | Use-DisplayConfig -UpdateAdapterIds
+                                              ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Use-DisplayConfig], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,MartinGC94.DisplayConfig.Commands.UseDisplayConfigCommand

In fact, even ReadLine doesn't see the new switch. I'm clueless

MartinGC94 commented 6 months ago

My bad, I assumed you were because of the well written issue and your use of Git. For future reference, I use Visual Studio community which is free for individuals for these kinds of open source projects. Then I just open the .sln file in VS and press the "Build solution" button which compiles the code and runs my post build script which creates the help file and module folder structure in the "Releases" directory.

I've published the latest version to the PS gallery, feel free to take a look and report back if it fixed your issue as well.

Lucide commented 6 months ago

Yes I had setup a minimal .NET toolchain to use in vscode. Eventually I found out that the issue was that powershell was still using the old PSGallery module. No matter of Import-Module .\DisplayConfig -Force, or Remove-Module -Name "DisplayConfig"; Import-Module .\DisplayConfig, or what Get-Module or Get-Command -All would report, it kept using the PSGallery one. I had to uninstall it. I don't understand this behaviour but ok.

And now I confirm it works in my use case 🙂 thank you!

MartinGC94 commented 5 months ago

@Lucide Quick unrelated question: How does the Windows Settings app order your monitors? I tried to find out what property or logic MS used but I couldn't figure it out and ended up just ordering them by the connector type and connector instance number (see: https://github.com/MartinGC94/DisplayConfig/blob/main/src/DisplayConfig/API/DisplayConfig.cs#L153 ) but I'm curious how close I got to the actual order. Does it list your dummy plug as display 1?

Lucide commented 5 months ago

No, display 1 is the displayport asus (an hdmi monitor on dp adapter), display 2 is the "mstar" tv I'm using as a 2nd monitor, and display three is the dp plug. They are connected to a nvidia gpu. Nvidia's control panel ui shows ids on enabled displays in the same way as windows.

I remember a dev working in a similar project saying that window's display numbering is very non-deterministic-ish and therefore not really usable to identify univocally displays between reboots/driver updates/configuration changes.

One thing I was wondering: my monitors all go to a "suspend" state when turned off, i.e., they remain visible to the display controller. I assume that if for some of them this wasn't the case, ids would get wrongly reassigned to the remaining monitors. But even in a setup like mine, it happens from time to time that a boot with the monitors off causes the os to only see the plug. Interestingly, in this case the module is still able to apply a profile and re-enable the real monitors.

If I remember correctly, there is no way to identify a monitor device univocally in windows (something like a queryable hw serial). But I also know that windows supports per-device EDID overrides..

A year or two ago I would've gladly embark to a trip to msdn and forums to help investigate, but I've made up my mind that the right time has come to switch to linux (desktop). My years on windows are numbered and I now spend my os-studying time sparingly🙇‍♂️.

MartinGC94 commented 5 months ago

In my experience the display number ordering has been 100% deterministic depending on the display output ports. Even if I swap the displayport ports and reinstall the OS, when I swap them back I get the same order I had before the OS reinstall. Annoyingly the firmware on my motherboard uses a different order than Windows so whichever display gets the boot logo ends up becoming display 2 in Windows.
It's too bad I didn't quite nail it but at least the displayport and HDMI order seemed to be as I had observed on my own test setups and my priority system should be deterministic and simple enough to understand. Thanks for checking it out for me.

I have no idea why Windows would randomly decide to only output to the dummy display but I'm glad to hear my module helps in that scenario.