Search code examples
c#windows-7multiple-monitors

Disconnect and Reconnect Displays Programmatically


Question: What is the best way to programmatically disconnect and reconnect displays programmatically?

The Goal: Kill the video output (black screen with no backlight) on a display and later turn it back on. Imagine unplugging the video cord from the monitor, then plugging it back in.

My Attempt:

// Get the monitor to disable
uint iDevNum = 0;
DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
displayDevice.cb = Marshal.SizeOf(displayDevice);
EnumDisplayDevices(null, iDevNum, ref displayDevice, 0))

DEVMODE devMode = new DEVMODE();
EnumDisplaySettings(displayDevice.DeviceName, 0, ref devMode);

//
// Do something here to disable this display device!
//

// Save the display settings
ChangeDisplaySettingsEx(displayDevice.DeviceName, ref devMode, 
    IntPtr.Zero, ChangeDisplaySettingsFlags.CDS_NONE, IntPtr.Zero);

I can interact with each display, but I can't figure out how to disconnect one.

It is similar to "Disconnect this display" in the Screen Resolution properties in Windows 7:

Windows 7 Screen Resolution Properties

Notes:

  • Turning off video output on all displays won't work because I need the other monitors to stay on.
  • The desktop area on the "dead" display does NOT need to be usable when it is off. Also, it is fine if windows move around.

References:

  1. SO: Enabling a Second Monitor
  2. How to Turn Off a Monitor

Solution

  • 1) Get MultiMonitorHelper from here: https://github.com/ChrisEelmaa/MultiMonitorHelper/tree/master

    2) Extend Win7Display to disconnect the display:

    using MultiMonitorHelper.DisplayModels.Win7.Enum;
    using MultiMonitorHelper.DisplayModels.Win7.Struct;
    
    /// <summary>
    /// Disconnect a display.
    /// </summary>
    public void DisconnectDisplay(int displayNumber)
    {
        // Get the necessary display information
        int numPathArrayElements = -1;
        int numModeInfoArrayElements = -1;
        StatusCode error = CCDWrapper.GetDisplayConfigBufferSizes(
            QueryDisplayFlags.OnlyActivePaths,
            out numPathArrayElements,
            out numModeInfoArrayElements);
    
        DisplayConfigPathInfo[] pathInfoArray = new DisplayConfigPathInfo[numPathArrayElements];
        DisplayConfigModeInfo[] modeInfoArray = new DisplayConfigModeInfo[numModeInfoArrayElements];
        error = CCDWrapper.QueryDisplayConfig(
            QueryDisplayFlags.OnlyActivePaths,
            ref numPathArrayElements,
            pathInfoArray,
            ref numModeInfoArrayElements,
            modeInfoArray,
            IntPtr.Zero);
        if (error != StatusCode.Success)
        {
            // QueryDisplayConfig failed
        }
    
        // Check the index
        if (pathInfoArray[displayNumber].sourceInfo.modeInfoIdx < modeInfoArray.Length)
        {
            // Disable and reset the display configuration
            pathInfoArray[displayNumber].flags = DisplayConfigFlags.Zero;
            error = CCDWrapper.SetDisplayConfig(
                pathInfoArray.Length,
                pathInfoArray,
                modeInfoArray.Length,
                modeInfoArray,
                (SdcFlags.Apply | SdcFlags.AllowChanges | SdcFlags.UseSuppliedDisplayConfig));
            if (error != StatusCode.Success)
            {
                // SetDisplayConfig failed
            }
        }
    }
    

    3) Extend Win7Display to reconnect the display using an answer from this post:

    using System.Diagnostics;
    
    /// <summary>
    /// Reconnect all displays.
    /// </summary>
    public void ReconnectDisplays()
    {
        DisplayChanger.Start();
    }
    
    private static Process DisplayChanger = new Process
    {
        StartInfo =
        {
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            FileName = "DisplaySwitch.exe",
            Arguments = "/extend"
        }
    };
    

    4) Update the methods in IDisplay.

    5) Implement the methods:

    IDisplayModel displayModel = DisplayFactory.GetDisplayModel();
    List<IDisplay> displayList = displayModel.GetActiveDisplays().ToList();
    
    displayList[0].DisconnectDisplay(0);
    displayList[0].ReconnectDisplays();