Skip to content

Commit

Permalink
Merge pull request #36 from vignetteapp/windows-display
Browse files Browse the repository at this point in the history
Add gdigrab display enumeration
  • Loading branch information
Speykious authored Oct 28, 2022
2 parents 51e1872 + ced0712 commit 67f5646
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 48 deletions.
177 changes: 129 additions & 48 deletions SeeShark/Device/DisplayManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using SeeShark.Interop.Windows;
using SeeShark.Interop.X11;

namespace SeeShark.Device
Expand Down Expand Up @@ -34,64 +36,143 @@ public override Display GetDevice(DisplayInfo info, VideoInputOptions? options =
/// </summary>
protected override DisplayInfo[] EnumerateDevices()
{
if (InputFormat == DeviceInputFormat.X11Grab)
switch (InputFormat)
{
unsafe
{
IntPtr display = XLib.XOpenDisplay(null);
IntPtr rootWindow = XLib.XDefaultRootWindow(display);
XRRMonitorInfo[] monitors = getXRandrDisplays(display, rootWindow);
case DeviceInputFormat.X11Grab:
return enumerateDevicesX11();
case DeviceInputFormat.GdiGrab:
return enumerateDevicesGdi();
default:
return base.EnumerateDevices();
}
}

DisplayInfo[] info = new DisplayInfo[monitors.Length + 1];
private DisplayInfo[] enumerateDevicesX11()
{
unsafe
{
IntPtr display = XLib.XOpenDisplay(null);
IntPtr rootWindow = XLib.XDefaultRootWindow(display);
XRRMonitorInfo[] monitors = getXRandrDisplays(display, rootWindow);

int compositeLeft = int.MaxValue;
int compositeRight = 0;
int compositeTop = int.MaxValue;
int compositeBottom = 0;
DisplayInfo[] info = new DisplayInfo[monitors.Length + 1];

for (int i = 0; i < monitors.Length; i++)
{
XRRMonitorInfo monitor = monitors[i];
info[i + 1] = new DisplayInfo
{
Name = $"Display {i}",
Path = ":0",
X = monitor.X,
Y = monitor.Y,
Width = monitor.Width,
Height = monitor.Height,
Primary = monitor.Primary > 0,
};

if (monitor.X < compositeLeft)
compositeLeft = monitor.X;

if (monitor.X + monitor.Width > compositeRight)
compositeRight = monitor.X + monitor.Width;

if (monitor.Y < compositeTop)
compositeTop = monitor.Y;

if (monitor.Y + monitor.Height > compositeBottom)
compositeBottom = monitor.Y + monitor.Height;
}

info[0] = new DisplayInfo
int compositeLeft = int.MaxValue;
int compositeRight = int.MinValue;
int compositeTop = int.MaxValue;
int compositeBottom = int.MinValue;

for (int i = 0; i < monitors.Length; i++)
{
XRRMonitorInfo monitor = monitors[i];
info[i + 1] = new DisplayInfo
{
Name = $"Composite X11 Display",
Name = $"Display {i}",
Path = ":0",
X = compositeLeft,
Y = compositeTop,
Width = compositeRight - compositeLeft,
Height = compositeBottom - compositeTop,
Primary = false,
IsComposite = true
X = monitor.X,
Y = monitor.Y,
Width = monitor.Width,
Height = monitor.Height,
Primary = monitor.Primary > 0,
};
return info;

if (monitor.X < compositeLeft)
compositeLeft = monitor.X;

if (monitor.X + monitor.Width > compositeRight)
compositeRight = monitor.X + monitor.Width;

if (monitor.Y < compositeTop)
compositeTop = monitor.Y;

if (monitor.Y + monitor.Height > compositeBottom)
compositeBottom = monitor.Y + monitor.Height;
}

info[0] = new DisplayInfo
{
Name = $"Composite X11 Display",
Path = ":0",
X = compositeLeft,
Y = compositeTop,
Width = compositeRight - compositeLeft,
Height = compositeBottom - compositeTop,
Primary = false,
IsComposite = true
};
return info;
}
}

private DisplayInfo[] enumerateDevicesGdi()
{
var displayInfo = new List<DisplayInfo>();

int count = 0;

int compositeLeft = int.MaxValue;
int compositeRight = int.MinValue;
int compositeTop = int.MaxValue;
int compositeBottom = int.MinValue;

bool MonitorDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData)
{
var monitorInfo = new MonitorInfoEx();
monitorInfo.size = (uint)Marshal.SizeOf(monitorInfo);

if (User32.GetMonitorInfo(hMonitor, ref monitorInfo))
{
var info = new DevMode();
User32.EnumDisplaySettings(monitorInfo.deviceName, -1, ref info);

var d = new DISPLAY_DEVICE();
d.cb = Marshal.SizeOf(d);
User32.EnumDisplayDevices(monitorInfo.deviceName, 0, ref d, 0);

displayInfo.Add(new DisplayInfo
{
Name = d.DeviceString,
Path = "desktop",
X = info.dmPositionX,
Y = info.dmPositionY,
Width = info.dmPelsWidth,
Height = info.dmPelsHeight,
Primary = count == 0
});
count++;

if (info.dmPositionX < compositeLeft)
compositeLeft = info.dmPositionX;

if (info.dmPositionX + info.dmPelsWidth > compositeRight)
compositeRight = (info.dmPositionX + info.dmPelsWidth);

if (info.dmPositionY < compositeTop)
compositeTop = info.dmPositionY;

if (info.dmPositionY + info.dmPelsHeight > compositeBottom)
compositeBottom = (info.dmPositionY + info.dmPelsHeight);

}
return true;
}

return base.EnumerateDevices();
User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorDelegate, IntPtr.Zero);

displayInfo.Insert(0, new DisplayInfo
{
Name = $"Composite GDI Display",
Path = "desktop",
X = compositeLeft,
Y = compositeTop,
Width = compositeRight - compositeLeft,
Height = compositeBottom - compositeTop,
Primary = false,
IsComposite = true
});

// TODO: using a list and converting to array is ugly, try to find alternative
return displayInfo.ToArray();
}

private unsafe XRRMonitorInfo[] getXRandrDisplays(IntPtr display, IntPtr rootWindow)
Expand Down
149 changes: 149 additions & 0 deletions SeeShark/Interop/Windows/User32.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright (c) The Vignette Authors
// This file is part of SeeShark.
// SeeShark is licensed under the BSD 3-Clause License. See LICENSE for details.

using System;
using System.Runtime.InteropServices;

namespace SeeShark.Interop.Windows
{
[StructLayout(LayoutKind.Sequential)]
internal struct DevMode
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
internal string dmDeviceName;
internal short dmSpecVersion;
internal short dmDriverVersion;
internal short dmSize;
internal short dmDriverExtra;
internal int dmFields;
internal int dmPositionX;
internal int dmPositionY;
internal int dmDisplayOrientation;
internal int dmDisplayFixedOutput;
internal short dmColor;
internal short dmDuplex;
internal short dmYResolution;
internal short dmTTOption;
internal short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
internal string dmFormName;
internal short dmLogPixels;
internal int dmBitsPerPel;
internal int dmPelsWidth;
internal int dmPelsHeight;
internal int dmDisplayFlags;
internal int dmDisplayFrequency;
internal int dmICMMethod;
internal int dmICMIntent;
internal int dmMediaType;
internal int dmDitherType;
internal int dmReserved1;
internal int dmReserved2;
internal int dmPanningWidth;
internal int dmPanningHeight;
}

[StructLayout(LayoutKind.Sequential)]
internal struct Rect
{
internal int left;
internal int top;
internal int right;
internal int bottom;
}

internal enum MONITORINFOF : ulong
{
PRIMARY = 1
}

[StructLayout(LayoutKind.Sequential)]
internal struct MonitorInfoEx
{
internal uint size;
internal Rect monitor;
internal Rect work;
internal uint flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string deviceName;
}

internal enum DpiType
{
Effective,
Angular,
Raw
}


[Flags]
internal 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
}


#pragma warning disable CA1815

// This is never compared in the code, so we can suppress the warning.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal struct DISPLAY_DEVICE
{
[MarshalAs(UnmanagedType.U4)]
internal int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
internal string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
internal string DeviceString;
[MarshalAs(UnmanagedType.U4)]
internal DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
internal string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
internal string DeviceKey;
}
#pragma warning restore CA1815

internal static partial class User32
{
internal delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);

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

[DllImport("user32.dll")]
internal static extern bool GetMonitorInfo(IntPtr hmon, ref MonitorInfoEx mi);

#pragma warning disable CA2101
[DllImport("user32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DevMode devMode);

[DllImport("user32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
internal static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

#pragma warning restore CA2101

[DllImport("Shcore.dll")]
internal static extern int GetDpiForMonitor(IntPtr hmon, DpiType dpiType, out uint dpiX, out uint dpiY);

[DllImport("Shcore.dll")]
internal static extern int SetProcessDpiAwareness(int awareness);
}
}

0 comments on commit 67f5646

Please sign in to comment.