I've made a program to change the colour filter of the screen similar to the way Flux does (the code shown to do this is in the main question from here). However, a couple of my users say it won't affect the other screen/s with two or more monitors. How would I modify the code so that it does?
You could do that by
- getting a hold of all connected monitors
- applying your get / set functions to the Graphics (or its Hdc).
- registering with the
MonitorInfoInvalidated
event to re-apply, if the monitor info gets invalidated.
If you already have a dependency on the Windows.Forms
dll, or don't mind taking on this dependency, you can use its Screen
class for this as @HansPassant pointed out in his answer. In this case you would register an eventhandler for the SystemEvents.DisplaySettingsChanged
to trigger re-applying your get/set functions, and you would use interop calls to CreateDC and DeleteDC to get/release a device context handle (IntPtr) from the Screen.DeviceName
property. The below code shows a wrapper around this class that helps with doing that:
/// <summary>
/// This is an alternative that uses the Windows.Forms Screen class.
/// </summary>
public static class FormsScreens
{
public static void ForAllScreens(Action<Screen, IntPtr> actionWithHdc)
{
foreach (var screen in Screen.AllScreens)
screen.WithHdc(actionWithHdc);
}
public static void WithHdc(this Screen screen, Action<Screen, IntPtr> action)
{
var hdc = IntPtr.Zero;
try
{
hdc = CreateDC(null, screen.DeviceName, null, IntPtr.Zero);
action(screen, hdc);
}
finally
{
if (!IntPtr.Zero.Equals(hdc))
DeleteDC(hdc);
}
}
private const string GDI32 = @"gdi32.dll";
[DllImport(GDI32, EntryPoint = "CreateDC", CharSet = CharSet.Auto)]
static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport(GDI32, CharSet = CharSet.Auto)]
private static extern bool DeleteDC([In] IntPtr hdc);
}
If you don't want to take on a new dependency on the Windows.Forms
dll, the ConnectedMonitors
class below provides the same kind of functionality:
/// <summary>
/// This is the version that is not dependent on Windows.Forms dll.
/// </summary>
public static class ConnectedMonitors
{
private static readonly bool _isSingleMonitor = GetSystemMetrics(SM_CMONITORS) == 0;
private static Lazy<List<MonitorInfo>> _monitors = new Lazy<List<MonitorInfo>>(GetMonitors, true);
public static event Action MonitorInfoInvalidated;
public class MonitorInfo
{
public readonly IntPtr MonitorHandle;
public readonly IntPtr DeviceContextHandle;
public readonly string DeviceName;
public readonly bool IsPrimary;
public readonly Rectangle Bounds;
public readonly Rectangle WorkArea;
public void WithMonitorHdc(Action<MonitorInfo, IntPtr> action)
{
var hdc = DeviceContextHandle;
var shouldDeleteDC = IntPtr.Zero.Equals(hdc);
try
{
if (shouldDeleteDC)
hdc = CreateDC(null, DeviceName, null, IntPtr.Zero);
action(this, hdc);
}
finally
{
if (shouldDeleteDC && !IntPtr.Zero.Equals(hdc))
DeleteDC(hdc);
}
}
internal MonitorInfo(
IntPtr hMonitor,
IntPtr hDeviceContext,
string deviceName,
bool isPrimary,
Rectangle bounds,
Rectangle workArea)
{
this.MonitorHandle = hMonitor;
this.DeviceContextHandle = hDeviceContext;
this.DeviceName = deviceName;
this.IsPrimary = isPrimary;
this.Bounds = bounds;
this.WorkArea = workArea;
}
}
public static void CaptureScreen(MonitorInfo mi, string fileName)
{
CaptureScreen(mi).Save(fileName);
}
public static Bitmap CaptureScreen(MonitorInfo mi)
{
Bitmap screenBmp = default(Bitmap);
mi.WithMonitorHdc((m, hdc) =>
{
screenBmp = new Bitmap(m.Bounds.Width, m.Bounds.Height, PixelFormat.Format32bppArgb);
using (var destGraphics = Graphics.FromImage(screenBmp))
{
var monitorDC = new HandleRef(null, hdc);
var destDC = new HandleRef(null, destGraphics.GetHdc());
var result = BitBlt(destDC, 0, 0, m.Bounds.Width, m.Bounds.Height, monitorDC, 0, 0, unchecked((int)BITBLT_SRCCOPY));
if (result == 0)
throw new Win32Exception();
}
});
return screenBmp;
}
public static IEnumerable<MonitorInfo> Monitors
{
get { return _monitors.Value; }
}
private static List<MonitorInfo> GetMonitors()
{
// Get info on all monitors
var cb = new EnumMonitorsCallback();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, cb.Callback, IntPtr.Zero);
// Register for events invalidating monitor info.
SystemEvents.DisplaySettingsChanging += OnDisplaySettingsChanging;
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
// Return result.
return cb.Monitors;
}
private class EnumMonitorsCallback
{
public List<MonitorInfo> Monitors = new List<MonitorInfo>();
public bool Callback(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr lparam)
{
// Get its info
var info = new MONITORINFOEX();
info.Size = Marshal.SizeOf(typeof(MONITORINFOEX));
GetMonitorInfo(hMonitor, ref info);
// Decode the info
var isPrimary = (hMonitor == (IntPtr)PRIMARY_MONITOR) || ((info.Flags & MONITORINFOF_PRIMARY) != 0);
var bounds = Rectangle.FromLTRB(info.Monitor.Left, info.Monitor.Top, info.Monitor.Right, info.Monitor.Bottom);
var workArea = Rectangle.FromLTRB(info.WorkArea.Left, info.WorkArea.Top, info.WorkArea.Right, info.WorkArea.Bottom);
var deviceName = info.DeviceName.TrimEnd('\0');
// Create info for this monitor and add it.
Monitors.Add(new MonitorInfo(hMonitor, hdcMonitor, deviceName, isPrimary, bounds, workArea));
return true;
}
}
private static void OnDisplaySettingsChanging(object sender, EventArgs e)
{
InvalidateInfo();
}
private static void OnUserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
InvalidateInfo();
}
private static void InvalidateInfo()
{
SystemEvents.DisplaySettingsChanging -= OnDisplaySettingsChanging;
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
var cur = _monitors;
_monitors = new Lazy<List<MonitorInfo>>(GetMonitors, true);
var notifyInvalidated = MonitorInfoInvalidated;
if (notifyInvalidated != null)
notifyInvalidated();
}
#region Interop
private const string USER32 = @"user32.dll";
private const string GDI32 = @"gdi32.dll";
private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D);
private const int MONITORINFOF_PRIMARY = 0x00000001;
private const int SM_CMONITORS = 80;
private const int BITBLT_SRCCOPY = 0x00CC0020;
private const int BITBLT_CAPTUREBLT = 0x40000000;
private const int BITBLT_CAPTURE = BITBLT_SRCCOPY | BITBLT_CAPTUREBLT;
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct MONITORINFOEX
{
public int Size;
public RECT Monitor;
public RECT WorkArea;
public uint Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
}
delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
[DllImport(USER32, CharSet=CharSet.Auto)]
private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
[DllImport(USER32, CharSet = CharSet.Auto)]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
[DllImport(USER32, CharSet = CharSet.Auto)]
private static extern int GetSystemMetrics(int nIndex);
[DllImport(GDI32, EntryPoint = "CreateDC", CharSet = CharSet.Auto)]
static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport(GDI32, CharSet = CharSet.Auto)]
private static extern bool DeleteDC([In] IntPtr hdc);
[DllImport(GDI32, CharSet = CharSet.Auto)]
public static extern int BitBlt(HandleRef hDC, int x, int y, int nWidth, int nHeight,
HandleRef hSrcDC, int xSrc, int ySrc, int dwRop);
#endregion
}
Usage examples for your use case, using a slightly modified SetLCDbrightness
method:
private bool SetLCDbrightness(IntPtr hdc, Color c)
{
short red = c.R;
short green = c.G;
short blue = c.B;
unsafe
{
short* gArray = stackalloc short[3 * 256];
short* idx = gArray;
short brightness = 0;
for (int j = 0; j < 3; j++)
{
if (j == 0) brightness = red;
if (j == 1) brightness = green;
if (j == 2) brightness = blue;
for (int i = 0; i < 256; i++)
{
int arrayVal = i * (brightness);
if (arrayVal > 65535) arrayVal = 65535;
*idx = (short)arrayVal;
idx++;
}
}
// For some reason, this always returns false?
bool retVal = SetDeviceGammaRamp(hdc, gArray);
}
return false;
}
Called like:
// ConnectedMonitors variant
public void SetBrightness(Color c)
{
foreach (var monitor in ConnectedMonitors.Monitors)
monitor.WithMonitorHdc((m, hdc) => SetLCDbrightness(hdc, c));
}
// Variant using the Windows.Forms Screen class
public void SetBrightness(Color c)
{
var setBrightness = new Action<Screen, IntPtr>((s, hdc) => SetLCDbrightness(hdc, c));
FormsScreens.ForAllScreens(setBrightness);
}
Note that it allows some other fun stuff like taking screenshots:
var n = 0;
foreach (var m in ConnectedMonitors.Monitors)
ConnectedMonitors.CaptureScreen(m, string.Format(@"c:\temp\screen{0}.bmp", n++));
System.Lazy<T>
can be found in the System
namespace. Make sure you have a using System;
statement. It is available since .NET 4. If you are using an older version of .NET, a simple replacement implementation should not be hard to create. –
Escobedo m
isn't used in your SetLCDbrightness() function. Shall we remove it? –
Sleuthhound Screen
class or the ConnectedMonitors
class, using the same strategy as outlined in the 3 bullet points at the top of my answer. –
Escobedo Screen
class? –
Escobedo hdc = gg.GetHdc();
call, and possibly, not doing a graphics.ReleaseDC
. I created an update that you could try in this gist. It also provides an implementation based on the Windows.Forms Screen
class. –
Escobedo true
parameter from the new Lazy<List<MonitorInfo>>(GetMonitors, true);
code, as the manual implementation of Lazy only takes a single parameter. –
Sleuthhound Your code cannot work when the machine has multiple display adapters, not entirely uncommon. So GetDC() is not correct, you need to instead pinvoke CreateDC(), passing the name of the screen (like @"\\.\DISPLAY1"
) as the 1st argument, rest null. Cleanup with DeleteDC().
Use the Screen.DeviceName property to get the device name, Screen.AllScreens() to enumerate the monitors. And you probably ought to subscribe SystemEvents.DisplaySettingsChanged event to detect that the user enabled a monitor, I don't have the hardware to check this.
Screens
class, which does mostly the same thing as the ConnectedMonitors
class in my answer. It was something I have used in a piece of software, that did not require any other dependencies on Windows Forms, and has additional functionality for figuring out whether display devices are detachable, active, what their physical size is, supported resolutions, ... If OP doesn't need to prevent a dependency on Windows Forms, a solution using its Screens
class would work equally well. I will update my answer accordingly. –
Escobedo © 2022 - 2024 — McMap. All rights reserved.