Search code examples
c#winapidisplaymonitornames

Valid friendly monitor names with C#


My main development machine is a laptop with 2 screens: an internal screen and an external Samsung monitor.

Generic PnP Monitor= 1366x768, Top: 0, Left: 1920 -> secondary display
SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)= 1920x1080, Top: 0, Left: 0 -> main display

My display configuration

And my codes are: Dispay.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

internal class Display
{
  private Rectangle _bounds;
  private DisplayOrientation _orientation;
  private Rectangle _workingArea;
  private string _name,_deviceId;
  private static Display[] _displays;

  public Rectangle Bounds
  {
     get
     {
       return _bounds;
     }
  }

  public DisplayOrientation Orientation
  {
    get
    {
        return _orientation;
    }
  }

  public Rectangle WorkingArea
  {
    get
    {
        return _workingArea;
    }
  }

  public string DeviceId
  {
    get
    {
        return _deviceId;
    }
  }

  public string Name
  {
    get
    {
        return _name;
    }
  }

  public static DisplayImpl[] Displays
  {
    get
    {

        if (_displays == null) QueryDisplayDevices();

        return _displays;
    }

    private static void QueryDisplayDevices()
    {
            List<Display> list = new List<Display>();
            WinApi.MonitorEnumDelegate MonitorEnumProc = new WinApi.MonitorEnumDelegate((IntPtr hMonitor, IntPtr hdcMonitor, ref WinApi.RECT lprcMonitor, IntPtr dwData) => {
                WinApi.MONITORINFOEX mi = new WinApi.MONITORINFOEX() { Size = Marshal.SizeOf(typeof(WinApi.MONITORINFOEX)) };

                if (WinApi.GetMonitorInfo(hMonitor, ref mi))
                {
                    WinApi.DISPLAY_DEVICE device = new WinApi.DISPLAY_DEVICE();
                    device.Initialize();
  
                    if (WinApi.EnumDisplayDevices(mi.DeviceName.ToLPTStr(), 0, ref device, 0))
                    {
                        Display display = new Display()
                        {
                            _name = device.DeviceString,
                            _deviceId = mi.DeviceName,
                            _bounds=new Rectangle(mi.Monitor.Left,mi.Monitor.Top,mi.Monitor.Right-mi.Monitor.Left,mi.Monitor.Bottom-mi.Monitor.Top),
                            _workingArea = new Rectangle(mi.WorkArea.Left, mi.WorkArea.Top, mi.WorkArea.Right - mi.WorkArea.Left, mi.WorkArea.Bottom - mi.WorkArea.Top),
                        };
                        list.Add(display);
                    }
                }
                
                return true;
            });

            WinApi.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
            _displays=list.ToArray();
    }
  }
}

WinApi.cs:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

internal class WinApi
{
#region DISPLAY_DEVICE struct
    [StructLayout(LayoutKind.Sequential)]
    internal struct DISPLAY_DEVICE
    {
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public DisplayDeviceStateFlags StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;

        public void Initialize()
        {
            cb = 0;
            DeviceName = new string((char)32, 32);
            DeviceString = new string((char)32, 128);
            DeviceID = new string((char)32, 128);
            DeviceKey = new string((char)32, 128);
            cb = Marshal.SizeOf(this);
        }
    }
#endregion

#region RECT struct
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
#endregion

#region MONITORINFOEX struct
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct MONITORINFOEX
    {
        public int Size;
        public RECT Monitor;
        public RECT WorkArea;
        public uint Flags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
    }
#endregion

#region DisplayDeviceStateFlags enum
    [Flags()]
    public enum DisplayDeviceStateFlags : int
    {
        /// <summary>The device is part of the desktop.</summary>
        AttachedToDesktop = 0x1,
        MultiDriver = 0x2,
        /// <summary>The device is part of the desktop.</summary>
        PrimaryDevice = 0x4,
        /// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
        MirroringDriver = 0x8,
        /// <summary>The device is VGA compatible.</summary>
        VGACompatible = 0x10,
        /// <summary>The device is removable; it cannot be the primary display.</summary>
        Removable = 0x20,
        /// <summary>The device has more display modes than its output devices support.</summary>
        ModesPruned = 0x8000000,
        Remote = 0x4000000,
        Disconnect = 0x2000000,
    }
#endregion

    public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

    [DllImport("user32.dll")]
    public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);

    [DllImport("User32.dll")]
    internal static extern bool EnumDisplayDevices(byte[] lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, int dwFlags);

    public static byte[] ToLPTStr(this string str)
    {
        var lptArray = new byte[str.Length + 1];

        var index = 0;
        foreach (char c in str.ToCharArray())
        lptArray[index++] = Convert.ToByte(c);

        lptArray[index] = Convert.ToByte('\0');

        return lptArray;
    }
}

Then I try to debug

if(Display.Displays !=null) { }

And I got these results:

Display 0:
    Name: Generic PnP Monitor
    DeviceId: \\\\.\\DISPLAY1
    Bounds:
    Top: 0
    Left: 0
    Width: 1920
    Height: 1080

Display 1:
    Name: SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)
    DeviceId: \\\\.\\DISPLAY2
    Bounds:
        Top: 0
        Left: 1920
        Width: 1366
        Height: 768

Based on screen resolution and top-left values, Display 0 should be "SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)", and why it got swapped with Display 1?


Solution

  • With @Dmo 's clues, I use this library

                Rectangle rect;
                Display display;
    
                foreach (PathInfo pi in PathInfo.GetActivePaths())
                {
                    if (!pi.TargetsInfo[0].DisplayTarget.IsAvailable) continue;
    
                    rect=System.Windows.Forms.Screen.GetWorkingArea(new Rectangle(pi.Position, pi.Resolution));
                    display = new DisplayImpl()
                    {
                        _name = string.IsNullOrEmpty(pi.TargetsInfo[0].DisplayTarget.FriendlyName)? "Generic PnP Monitor" : pi.TargetsInfo[0].DisplayTarget.FriendlyName,
                        _deviceId = pi.DisplaySource.DisplayName,
                        _devicePath=pi.TargetsInfo[0].DisplayTarget.DevicePath,
                        _bounds = new Rectangle(pi.Position,pi.Resolution),
                        _workingArea = rect,
                    };
    
                    list.Add(display);
                }
    

    It produces the correct monitor name and settings pair! 👍