1

I would like to know if there is a way in Delphi to read the monitor name as I see it in the MultiMonitorTool program:

image

At the moment, I have tried to read it via the following methods:

  • MonitorInfo.szDevice (it returns "\\.\DISPLAY1")
  • monitorname(Screen.Monitors[i].MonitorNum) (it returns "Generic-Non-PnP-Monitor")
  • Screen.Monitors[i].FriendlyName (it returns "Generic-Non-PnP-Monitor")
procedure TMonitorOrientationDemoForm.RefreshMonitorsList;
var
  i: integer;
  li: TListItem;
  MonitorInfo: TMonitorInfoEx;
  LTmp: string;

  function ArrayToString(const a: array of Char): string;
  begin
    if Length(a) > 0 then
      Result := StrPas(PChar(@a[0]))
    else
      Result := '';
  end;

begin
  lvMonitors.Items.BeginUpdate;
  try
    lvMonitors.Items.Clear;
    for i := 0 to Screen.MonitorCount - 1 do
    begin
      li := lvMonitors.Items.Add;
      MonitorInfo.cbSize := SizeOf(MonitorInfo);
      if not GetMonitorInfo(Screen.Monitors[i].Handle, @MonitorInfo) then
        continue;
      LTmp := ArrayToString(MonitorInfo.szDevice);
      //Screen.Monitors[i].FriendlyName -> returns the string "Generic-Non-PnP-Monitor"
      //MonitorInfo.szDevice -> returns the string "\\.\DISPLAY1"
      //monitorname(Screen.Monitors[i].MonitorNum) -> returns the string "Generic-Non-PnP-Monitor"
      LI.Caption := Screen.Monitors[i].FriendlyName;
      li.SubItems.Add(MonitorOrientationToString(Screen.Monitors[i].Orientation));
      if Screen.Monitors[i].SupportsRotation then
        li.SubItems.Add('Yes')
      else
        li.SubItems.Add('No');
    end;
  finally
    lvMonitors.Items.EndUpdate;
  end;
end;

function TMonitorOrientationDemoForm.monitorname(numberof: integer): string;
Var
  Cntr: Cardinal;
  Info: TDisplayDevice;
  AdapterName: PChar;
  OldPos, j, i: integer;
  a: tstringlist;
Begin
  a := tstringlist.create;
  Cntr := numberof;
  Info.cb := SizeOf(Info);
  While EnumDisplayDevices(Nil, Cntr, Info, 0) Do
  Begin
    AdapterName := StrAlloc(SizeOf(Info.DeviceName));
    StrCopy(AdapterName, Info.DeviceName);
    EnumDisplayDevices(AdapterName, 0, Info, 0);
    a.Add(Info.DeviceString);
    for i := 1 to a.count - 1 do
    begin
      a.Delete(i);
      Result := a.Strings[0];
      StrDispose(AdapterName);
      Inc(Cntr);
    End;
  End;
End;

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Matteo Pasini
  • 1,787
  • 3
  • 13
  • 26
  • Maybe [this link](https://superuser.com/questions/1234471/how-can-i-easily-get-the-manufacturer-and-model-of-my-monitor) has the answer you look for but not using Delphi. – fpiette Jun 21 '21 at 08:22
  • With the wmic command I still get the same output, probably for this reason here: https://superuser.com/questions/1245250/why-is-my-monitor-showing-up-as-generic-non-pnp-monitor. So how is it possible that mooltimonitortool can read the model name of the monitor? Probably if I had manually installed the monitor drivers as suggested in the link, I would also have been able to obtain the desired result via delphi. I am able to read the model name even through a small test program written in C# using the WindowsDisplayAPI library – Matteo Pasini Jun 21 '21 at 08:34
  • Unfortunately the program in which I need that piece of code has to be distributed to several users, and it would be very inconvenient to have to manually set the drivers for each monitor. – Matteo Pasini Jun 21 '21 at 08:36
  • 1
    Why don't you look how the C# program find out the monitor name? The [source code of WindowsDisplayAPI](https://github.com/falahati/WindowsDisplayAPI/tree/master/WindowsDisplayAPI) is available. – fpiette Jun 21 '21 at 09:09
  • What's `FriendlyName`? – Andreas Rejbrand Jun 21 '21 at 13:13

3 Answers3

3

The standard approach, which is based on EnumDisplayDevices and has described in several previous Qs [1, 2], can be written like this in Delphi:

procedure TForm1.FormCreate(Sender: TObject);
var
  dd, md: TDisplayDevice;
begin

  ListBox1.Items.BeginUpdate;
  try
    ListBox1.Clear;
    FillChar(dd, SizeOf(dd), 0);
    dd.cb := SizeOf(dd);
    FillChar(md, SizeOf(md), 0);
    md.cb := SizeOf(md);
    var i := 0;
    while EnumDisplayDevices(nil, i, dd, 0) do
    begin
      var j := 0;
      while EnumDisplayDevices(@dd.DeviceName[0], j, md, 0) do
      begin
        ListBox1.Items.Add(md.DeviceString);
        Inc(j);
      end;
      Inc(i);
    end;
  finally
    ListBox1.Items.EndUpdate;
  end;

end;

On my system, this correctly identifies my three Dell monitors but fails to identify my Samsung wall-mounted TV ("Generic PnP Monitor").

Screenshot of a Delphi application showing my four displays in a list box: Dell U2211H (Digital), Allmän PnP-bildskärm, Dell UP2716D (DP), and Dell UP2716D (DP)

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
3

This is quite new to me and either because I execute it on Windows 7 or because I also have a TV instead of a monitor I also only get a generic "PnP-Monitor (Standard)" with the usual approach. But the code below gives me a helpful result:

program stackoverflow68064094;
{$APPTYPE CONSOLE}

const
  user32= 'user32.dll';
  QDC_ALL_PATHS=                                1;
  DISPLAYCONFIG_MODE_INFO_TYPE_TARGET=          2;
  DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME=    2;
  ERROR_SUCCESS=                                0;

type
  LUID= Int64;
  LONG= LongInt;  // Just anything 32bit
  BOOL= LongBool;

  // ENUMs, all starting with 1
  DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS=   Cardinal;
  DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY=    Cardinal;
  DISPLAYCONFIG_ROTATION=                   Cardinal;
  DISPLAYCONFIG_SCALING=                    Cardinal;
  DISPLAYCONFIG_SCANLINE_ORDERING=          Cardinal;
  DISPLAYCONFIG_MODE_INFO_TYPE=             Cardinal;
  DISPLAYCONFIG_PIXELFORMAT=                Cardinal;
  DISPLAYCONFIG_TOPOLOGY_ID=                Cardinal;

  PDISPLAYCONFIG_DEVICE_INFO_HEADER= ^DISPLAYCONFIG_DEVICE_INFO_HEADER;
  DISPLAYCONFIG_DEVICE_INFO_HEADER= packed record
    typ, size: Cardinal;
    adapterId: LUID;
    id: Cardinal;
  end;

  DISPLAYCONFIG_TARGET_DEVICE_NAME= packed record
    header: DISPLAYCONFIG_DEVICE_INFO_HEADER;
    flags: DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS;
    outputTechnology: DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;
    edidManufactureId, edidProductCodeId: Word;
    connectorInstance: Cardinal;
    monitorFriendlyDeviceName: Array[0.. 64- 1] of WideChar;
    monitorDevicePath: Array[0.. 128- 1] of WideChar;
  end;

  DISPLAYCONFIG_PATH_SOURCE_INFO= packed record
    adapterId: LUID;
    id, modeInfoIdx, statusFlags: Cardinal;
  end;

  DISPLAYCONFIG_RATIONAL= packed record
    Numerator, Denominator: Cardinal;
  end;

  DISPLAYCONFIG_PATH_TARGET_INFO= packed record
    adapterId: LUID;
    id, modeInfoIdx: Cardinal;
    outputTechnology: DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;
    rotation: DISPLAYCONFIG_ROTATION;
    scaling: DISPLAYCONFIG_SCALING;
    refreshRate: DISPLAYCONFIG_RATIONAL;
    scanLineOrdering: DISPLAYCONFIG_SCANLINE_ORDERING;
    targetAvailable: BOOL;
    statusFlags: Cardinal;
  end;

  PDISPLAYCONFIG_PATH_INFO= ^DISPLAYCONFIG_PATH_INFO;
  DISPLAYCONFIG_PATH_INFO= packed record
    sourceInfo: DISPLAYCONFIG_PATH_SOURCE_INFO;
    targetInfo: DISPLAYCONFIG_PATH_TARGET_INFO;
    flags: Cardinal;
  end;

  DISPLAYCONFIG_2DREGION= packed record
    cx, cy: Cardinal;
  end;

  DISPLAYCONFIG_VIDEO_SIGNAL_INFO= packed record
    pixelRate: UInt64;
    hSyncFreq, vSyncFreq: DISPLAYCONFIG_RATIONAL;
    activeSize, totalSize: DISPLAYCONFIG_2DREGION;
    videoStandard: Cardinal;
  end;

  DISPLAYCONFIG_TARGET_MODE= packed record
    targetVideoSignalInfo: DISPLAYCONFIG_VIDEO_SIGNAL_INFO;
  end;

  POINTL= packed record  // Or TPOINT right away
    x, y: LONG;
  end;

  DISPLAYCONFIG_SOURCE_MODE= packed record
    width, height: Cardinal;
    pixelFormat: DISPLAYCONFIG_PIXELFORMAT;
    position: POINTL;
  end;

  RECTL= packed record  // Or TRECT right away
    left, top, right, bottom: LONG;
  end;

  DISPLAYCONFIG_DESKTOP_IMAGE_INFO= packed record
    PathSourceSize: POINTL;
    DesktopImageRegion, DesktopImageClip: RECTL;
  end;

  PDISPLAYCONFIG_MODE_INFO= ^DISPLAYCONFIG_MODE_INFO;
  DISPLAYCONFIG_MODE_INFO= packed record
    infoType: DISPLAYCONFIG_MODE_INFO_TYPE;
    id: Cardinal;
    adapterId: LUID;
    case Byte of
    1: ( targetMode: DISPLAYCONFIG_TARGET_MODE );
    2: ( sourceMode: DISPLAYCONFIG_SOURCE_MODE );
    3: ( desktopImageInfo: DISPLAYCONFIG_DESKTOP_IMAGE_INFO );
  end;

  PDISPLAYCONFIG_TOPOLOGY_ID= ^DISPLAYCONFIG_TOPOLOGY_ID;


function GetDisplayConfigBufferSizes( flags: Cardinal;
  numPathArrayElements, numModeInfoArrayElements: PCardinal ): LONG; stdcall; external user32;  // Vista

function QueryDisplayConfig( flags: Cardinal; numPathArrayElements: PCardinal;
  pathArray: PDISPLAYCONFIG_PATH_INFO; numModeInfoArrayElements: PCardinal;
  modeInfoArray: PDISPLAYCONFIG_MODE_INFO;
  currentTopologyId: PDISPLAYCONFIG_TOPOLOGY_ID ): LONG; stdcall; external user32;  // Windows 7

function DisplayConfigGetDeviceInfo(
  requestPacket: PDISPLAYCONFIG_DEVICE_INFO_HEADER ): LONG; stdcall; external user32;  // Vista


// Actually using all this
procedure GetFriendlyMonitorNames();
var
  vName: DISPLAYCONFIG_TARGET_DEVICE_NAME;
  iPath, iMode: Cardinal;
  aPath: Array of DISPLAYCONFIG_PATH_INFO;
  aMode: Array of DISPLAYCONFIG_MODE_INFO;
  i: Integer;
begin
  if not GetDisplayConfigBufferSizes( QDC_ALL_PATHS, @iPath, @iMode )= ERROR_SUCCESS then exit;
  SetLength( aPath, iPath );  // For me: 104...
  SetLength( aMode, iMode );  // ...and 30

  if not QueryDisplayConfig
  ( QDC_ALL_PATHS
  , @iPath, @aPath[0]
  , @iMode, @aMode[0]
  , nil
  )= ERROR_SUCCESS then exit;

  for i:= 0 to Integer(iMode)- 1 do begin
    case aMode[i].infoType of
      DISPLAYCONFIG_MODE_INFO_TYPE_TARGET: begin
        vName.header.size:= SizeOf( DISPLAYCONFIG_TARGET_DEVICE_NAME );
        vName.header.adapterId:= aMode[i].adapterId;
        vName.header.id:= aMode[i].id;
        vName.header.typ:= DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
        if( DisplayConfigGetDeviceInfo( @vName ) )= ERROR_SUCCESS then begin
          Writeln( vName.monitorFriendlyDeviceName );  // For me: M237WD
        end;
      end;
    end;
  end;
end;

begin
  GetFriendlyMonitorNames();
end.

On an older netbook the friendly name is empty (successfull function call, tho), while the generic .DeviceString approach gives me "Digital-Flachbildschirm (1280x1024 60Hz)", which is more helpful. I guess one has to consider at least both approaches.

Edit: including all definitions translated from C++, written by hand. Now it's a complete program, running fine with Delphi 7. Needs at least Windows 7.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
  • Could you mention, which units were used in uses section. – AliReza Jun 21 '21 at 20:47
  • @AliReza: The declarations (types, constants, and functions) needed here aren't available in a new RAD Studio setup, so AmigoJack probably translated them himself or found some translation somewhere. – Andreas Rejbrand Jun 21 '21 at 22:16
  • @AliReza: I think all you need is here: https://stackoverflow.com/a/26406082/282848. I started translating it but it is very late now. – Andreas Rejbrand Jun 21 '21 at 22:23
  • @AliReza none. I guess you want the definitions, so I added them to my code. – AmigoJack Jun 21 '21 at 22:32
  • Now I can correctly read the monitor model, but the problem is that I can only identify one. I have tried to run it on several test PCs and I always get the same behavior. – Matteo Pasini Jun 22 '21 at 06:02
  • I noticed that inside the `for i loop:= 0 to Integer (iMode) - 1 do` only one monitor enters the case with `aMode[i].infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET` – Matteo Pasini Jun 22 '21 at 06:13
1

First you must know some monitor doesn't have registered name in windows but if they have a name and you can read it with a program like MultiMonitorTool, So you can read it with Delphi too.

You can read EDID key value from the registry. Value of EDID key create when monitor installed and can be overwritten by monitor driver.

This program read EDID for the active monitors.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Win.Registry, Vcl.ComCtrls, Winapi.MultiMon,
   Winapi.Windows, Vcl.Forms, System.Classes, System.StrUtils,
   System.Character;

function GetMonitorName(index : Integer) : String;
const
  Key = '\SYSTEM\CurrentControlSet\Enum\DISPLAY\';
type
  IBMString = type Ansistring(1253);
var
  Handle: HMONITOR;
  i, j, k : Integer;
  Registry: TRegistry;
  MonitorName : IBMString;
  DispDev : TDisplayDevice;
  subKeysNames : TStringList;
  MonitorInfo : TMonitorInfoEx;
  DeviceIDSplit : TArray<String>;
  EDID : array [0 .. 127] of Byte;
  DeviceName, DeviceString, DeviceID, DeviceKey, Driver : string;
begin
  Result := '';

  Handle := Screen.Monitors[index].Handle;
  MonitorInfo.cbSize := sizeof(MonitorInfo);
  if GetMonitorInfo(Handle, @MonitorInfo) then
  begin
    DispDev.cb := sizeof(DispDev);
    EnumDisplayDevices(@MonitorInfo.szDevice, 0, DispDev, 0);
    DeviceName   := StrPas(DispDev.DeviceName);     //'\\.\DISPLAY1\Monitor0'    //This Line Can Be Removed
    DeviceString := StrPas(DispDev.DeviceString);   //'Generic PnP Monitor'      //This Line Can Be Removed
    DeviceID     := StrPas(DispDev.DeviceID);
    DeviceKey    := StrPas(DispDev.DeviceKey);                                   //This Line Can Be Removed

    DeviceIDSplit := DeviceID.Split(['\']);
    if Length(DeviceIDSplit) < 3 then Exit;
    Driver := '';
    for i := 2 to High(DeviceIDSplit) do
      Driver := Driver + '\' + DeviceIDSplit[i];
    System.Delete(Driver, 1, 1);

    subKeysNames := TStringList.Create;
    Registry := TRegistry.Create(KEY_READ);
    Registry.RootKey := HKEY_LOCAL_MACHINE;
    try
      try
        Registry.OpenKeyReadOnly(Key);
        Registry.GetKeyNames(subKeysNames);
      finally
        Registry.CloseKey;
      end;
      if subKeysNames.IndexOf(DeviceIDSplit[1]) < 0 then Exit;
      try
        Registry.OpenKeyReadOnly(Key + DeviceIDSplit[1]);
        Registry.GetKeyNames(subKeysNames);
      finally
        Registry.CloseKey;
      end;

      for i := 0 to subKeysNames.Count - 1 do
      begin
        try
          if registry.OpenKeyReadOnly(Key + DeviceIDSplit[1] + '\' + subKeysNames[i]) then
          begin
            if Registry.ReadString('Driver') <> Driver then Continue;
            Registry.CloseKey;
            if registry.OpenKeyReadOnly(Key + DeviceIDSplit[1] + '\' + subKeysNames[i] + '\' + 'Device Parameters') then
            begin
              Registry.ReadBinaryData('EDID', EDID, 128);
              Registry.CloseKey;
            end;
            for j := 0 to 3 do
            begin
              if (EDID[54 + 18 * j] = 0) and
                 (EDID[55 + 18 * j] = 0) and
                 (EDID[56 + 18 * j] = 0) and
                 (EDID[57 + 18 * j] = $FC) and
                 (EDID[58 + 18 * j] = 0) then
              begin
                k := 0;
                while (EDID[59 + 18 * j + k] <> $A) and (k < 13) do
                  Inc(k);
                SetString(MonitorName, PAnsiChar(@EDID[59 + 18 * j]), k);
                Result := MonitorName;
                Break;
              end;
            end;
          end;
        finally
          Registry.CloseKey;
        end;
      end;
    finally
      Registry.Free;
      subKeysNames.Free;
    end;
  end;
end;

var
  i : Integer;

begin
  for i := 0 to Screen.MonitorCount-1 do
  begin
    Writeln(GetMonitorName(i));
  end;
  Readln;
end.

and this program read all of monitor names, some of them may don't have a name.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, System.Win.Registry, Vcl.ComCtrls, Winapi.MultiMon,
   Winapi.Windows, Vcl.Forms, System.Classes, System.StrUtils,
   System.Character;

function GetAllMonitorName : TStrings;
const
  Key = '\SYSTEM\CurrentControlSet\Enum\DISPLAY\';
type
  IBMString = type Ansistring(1253);
var
  Registry: TRegistry;
  H, i, j, k : Integer;
  MonitorName : IBMString;
  EDID : array [0 .. 127] of Byte;
  subKeysNames, subKeys : TStringList;
begin
  Result := TStringList.Create;

  subKeys := TStringList.Create;
  subKeysNames := TStringList.Create;
  Registry := TRegistry.Create(KEY_READ);
  Registry.RootKey := HKEY_LOCAL_MACHINE;
  try
    try
      Registry.OpenKeyReadOnly(Key);
      Registry.GetKeyNames(subKeysNames);
    finally
      Registry.CloseKey;
    end;

    for h := 0 to subKeysNames.Count - 1 do
    begin
      try
        Registry.OpenKeyReadOnly(Key + subKeysNames[h]);
        Registry.GetKeyNames(subKeys);
      finally
        Registry.CloseKey;
      end;

      for i := 0 to subKeys.Count - 1 do
      begin
        try
          if registry.OpenKeyReadOnly(Key + subKeysNames[h] + '\' + subKeys[i]) then
          begin
            if registry.OpenKeyReadOnly(Key + subKeysNames[h] + '\' + subKeys[i] + '\' + 'Device Parameters') then
            begin
              Registry.ReadBinaryData('EDID', EDID, 128);
              Registry.CloseKey;
            end;
            MonitorName := '';
            for j := 0 to 3 do
            begin
              if (EDID[54 + 18 * j] = 0) and
                 (EDID[55 + 18 * j] = 0) and
                 (EDID[56 + 18 * j] = 0) and
                 (EDID[57 + 18 * j] = $FC) and
                 (EDID[58 + 18 * j] = 0) then
              begin
                k := 0;
                while (EDID[59 + 18 * j + k] <> $A) and (k < 13) do
                  Inc(k);
                SetString(MonitorName, PAnsiChar(@EDID[59 + 18 * j]), k);
                Break;
              end;
            end;
            Result.Add(MonitorName);
          end;
        finally
          Registry.CloseKey;
        end;
      end;
    end;
  finally
    subKeys.Free;
    Registry.Free;
    subKeysNames.Free;
  end;
end;

var
  i : Integer;

begin
  Writeln(GetAllMonitorName.Text);
  Readln;
end.

And if you want write a program like MultiMonitorTool, you can read more information from EDID, for more information read Extended Display Identification Data

AliReza
  • 106
  • 1
  • 7
  • This only lists monitors which were ever connected in the past, not now - it's a history, not a snapshot. And for my ID `MONITOR\MS_0005\...` no key exists at `SYSTEM\CurrentControlSet\Enum\DISPLAY\` either, only 5 others from the past. – AmigoJack Jun 21 '21 at 16:37
  • There is another path check 'SYSTEM\ControlSet001\Enum\DISPLAY' – AliReza Jun 21 '21 at 17:01
  • Not in your code. And it wouldn't help either, see [How does CurrentControlSet differ from ControlSet001 and ControlSet002?](https://stackoverflow.com/a/291528/4299358) – AmigoJack Jun 21 '21 at 18:46