Search code examples
c#windowswinapipinvokemultiple-monitors

Configs for SetDisplayConfig become invalid after computer restart


I'm using WINAPI to Enable/Disable my TV monitor by calling a method.

The method I found to work was saving the whole pathInfo and modeInfo arrays for both states
(Enabled/Disabled) and using them to restore to that state .

The method works properly but after restarting my computer, the settings become obsolete.
It works fine if I just restart the program, so the serialization is correct.

The goal was to Save/Serialize those settings ONCE on a computer, and be able to use them forever.

I've tried saving only the path and mode of the individual monitor that I'm interested on (by filtering the active displays only), but it turns out that the other screens' modes are affected as well when there is an extra display in the setup.

The main method is the method below, but you can find the whole class here!
(I'm using the User32 PInvoke lib on top of that)

[Serializable]
public class TVSettings {
    public DISPLAYCONFIG_PATH_INFO[] Path;
    public DISPLAYCONFIG_MODE_INFO[] Mode;
    public TVSettings(DISPLAYCONFIG_PATH_INFO[] pathArray, DISPLAYCONFIG_MODE_INFO[] modeArray) {
        Path = pathArray;
        Mode = modeArray;
    }
}
// The preset of the settings which I serialize.
public static TVSettings Enabled;
public static TVSettings Disabled;

// The main method. Merged the Save & ChangeState branches to save space.
public static void ChangeTVState(bool ChangeState = false, bool Save = false) {
    uint numPathArrayElements = 0;
    uint numModeInfoArrayElements = 0;
    uint id = QDC_ALL_PATHS; // Searching for ALL PATHS because I want the disabled screen inside the array after the Query.

    // Initialize and Query all the Display Config info.
    int bufferError = GetDisplayConfigBufferSizes(id, ref numPathArrayElements, ref numModeInfoArrayElements);
    DISPLAYCONFIG_PATH_INFO[] pathArray = new DISPLAYCONFIG_PATH_INFO[numPathArrayElements];
    DISPLAYCONFIG_MODE_INFO[] modeArray = new DISPLAYCONFIG_MODE_INFO[numModeInfoArrayElements];

    QueryDisplayConfig(id, ref numPathArrayElements, pathArray, ref numModeInfoArrayElements, modeArray, IntPtr.Zero);

    // Grab the active Screens -- was previously used for tests.
    var active_modeArray = modeArray.Where(x => x.targetMode.targetVideoSignalInfo.activeSize.cx != 0).ToArray();
    var active_pathArray = pathArray.Where(x => x.flags != 0).ToArray();

    bool ThirdScreenIsConnected = active_pathArray.Length >= 3 && active_modeArray.Length >= 3;

    if (Save) {
        // Save on the appropriate Preset field.
        if (ThirdScreenIsConnected) { Enabled = new TVSettings(pathArray, modeArray); }
        else { Disabled = new TVSettings(pathArray, modeArray); }
    }

    if (ChangeState) {
        // Safety measures because I don't wanna mess up the settings too much.
        if (Enabled == null || Disabled == null) {
            Console.WriteLine("Enabled & Disabled Settings are not configured properly.");
            Console.WriteLine("Please save both and try again.");
            return;
        }

        // Use the settings of the other state
        // eg: if 3rd monitor is currently disabled, we use the Disabled preset.
        var Settings = ThirdScreenIsConnected ? Disabled : Enabled;
        pathArray = Settings.Path;
        modeArray = Settings.Mode;

        // Call SetDisplayConfig to update the display config.
        // It works fine on a single windows boot, but the settings are not valid if I reboot.
        uint flag = (SDC_APPLY | SDC_SAVE_TO_DATABASE | SDC_ALLOW_CHANGES | SDC_USE_SUPPLIED_DISPLAY_CONFIG);
        int errorID = SetDisplayConfig((uint)pathArray.Length, pathArray, (uint)modeArray.Length, modeArray, flag);

        if (errorID == 0) { Console.WriteLine("Successfully updated Screen setup!"); }
        else { Console.WriteLine("ERROR: " + errorID); }
    }

}

I would expect the settings to be valid on multiple windows sessions, but they are being deemed invalid instead.

The error that appears after restarting is : 87 -- INVALID PARAMETERS, which also appeared when I attempted to alter the individual settings of the monitor (in pathArray and modeArray).

If you care enough to try this on your machine, here's some usable Save/Load functionality, together with a simple context menu for WPF

(You have to exit via the "Exit" button on the context menu for the serialization to occur)
(ps: you have to save twice -- once with 2nd screen Enabled and once with it Disabled)

Any help would be appreciated! :)


Solution

  • I found out that the issue was occuring because the adapterID of the GPU and motherboard were changing with each computer restart.

    I found a nice way to solve my issue without any serialization of the previous adapterID!
    Here's the code for it:

    static void UpdateAdapterID() {
    
        // Cache saved adapterIDs, for later comparison with the current ones.
        LUID savedAdapterID_GPU = Enabled.Path[0].sourceInfo.adapterId;
        LUID savedAdapterID_MB = Enabled.Path.First(x => x.sourceInfo.adapterId.LowPart != savedAdapterID_GPU.LowPart && x.sourceInfo.adapterId.LowPart != 0).sourceInfo.adapterId;
    
        bool isAdapterUpdated_GPU = savedAdapterID_GPU.LowPart == CurrentAdapterID_GPU.LowPart;
        bool isAdapterUpdated_MB = savedAdapterID_MB.LowPart == CurrentAdapterID_MB.LowPart;
    
        // Check if our saved states have already been updated.
        if (isAdapterUpdated_GPU && isAdapterUpdated_MB) { return; }
    
        for (int i = 0; i < Enabled.Path.Length; i++) {
            Update(ref Enabled.Path[i].sourceInfo.adapterId);
            Update(ref Enabled.Path[i].targetInfo.adapterId);
        }
        for (int i = 0; i < Disabled.Path.Length; i++) {
            Update(ref Disabled.Path[i].sourceInfo.adapterId);
            Update(ref Disabled.Path[i].targetInfo.adapterId);
        }
    
        for (int i = 0; i < Enabled.Mode.Length; i++) { Update(ref Enabled.Mode[i].adapterId); }
        for (int i = 0; i < Disabled.Mode.Length; i++) { Update(ref Disabled.Mode[i].adapterId); }
    
    
    
        void Update(ref LUID adapterID) {
            bool isInvalid = adapterID.LowPart == 0;
            bool isUpdated = adapterID.LowPart == CurrentAdapterID_GPU.LowPart || adapterID.LowPart == CurrentAdapterID_MB.LowPart;
    
            if (!isInvalid && !isUpdated) {
                bool adapterIsGPU = adapterID.LowPart == savedAdapterID_GPU.LowPart;
                if (adapterIsGPU) { adapterID = CurrentAdapterID_GPU; }
                else { adapterID = CurrentAdapterID_MB; }
            }
        }
    
        if (!isAdapterUpdated_GPU) { Console.WriteLine("Updated adapterID for GPU."); }
        if (!isAdapterUpdated_MB) { Console.WriteLine("Updated adapterID for MB."); }
    }
    
    

    Here's the whole script in case anyone can find it helpful :)