Change the color screen filter for working with multiple monitors

I made a program to change the color filter on the screen, similar to how Flux does it (the code shown for this is in the main question here ). However, several of my users say that this will not affect other screens with two or more monitors. How do I change the code so that it does?

+5
source share
2 answers

You can do it with

  • capture all connected monitors
  • applying your get / set functions to Graphics (or its Hdc).
  • registration with the MonitorInfoInvalidated event for reuse if the monitor information becomes invalid.

If you already have a dependency on Windows.Forms dll or are not against using this dependency, you can use its Screen for this, as @HansPassant pointed out in the answer. In this case, you will register an event handler for SystemEvents.DisplaySettingsChanged to cause your get / set functions to be reused, and you will use the interop calls on CreateDC and DeleteDC to get / release the device context descriptor (IntPtr) from the Screen.DeviceName property. The code below shows the shell of this class that helps with this:

 /// <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 do not want to take a new dependency on the Windows.Forms dll, the ConnectedMonitors class below provides the same 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 the slightly modified SetLCDbrightness method:

 private bool SetLCDbrightness(IntPtr hdc, Color c) { short red = cR; short green = cG; short blue = cB; 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 as:

 // 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); } 

Please note that this allows some other fun things, such as taking screenshots:

 var n = 0; foreach (var m in ConnectedMonitors.Monitors) ConnectedMonitors.CaptureScreen(m, string.Format(@"c:\temp\screen{0}.bmp", n++)); 
+2
source

Your code may not work if there are several display adapters on the computer, and not quite unusual. Therefore, GetDC () is incorrect, you need to output CreateDC () instead, passing the screen name (for example, @"\\.\DISPLAY1" ) as the 1st argument, rest null. Cleanup with DeleteDC ().

Use the Screen.DeviceName property to get the device name, Screen .AllScreens () to list the monitors. And you probably should subscribe to the SystemEvents.DisplaySettingsChanged event to find that the user turned on the monitor, I don’t have the equipment to check this.

+3
source

All Articles