-
-
Notifications
You must be signed in to change notification settings - Fork 156
Adjust the physical monitor brightness directly instead of multiplying gamma #387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,11 +1,16 @@ | ||||||
| using System.Diagnostics; | ||||||
| using System; | ||||||
| using System.Collections.Generic; | ||||||
| using System.Diagnostics; | ||||||
| using System.Linq; | ||||||
| using System.Runtime.InteropServices; | ||||||
| using LightBulb.PlatformInterop.Internal; | ||||||
|
|
||||||
| namespace LightBulb.PlatformInterop; | ||||||
|
|
||||||
| public partial class DeviceContext(nint handle) : NativeResource(handle) | ||||||
| public partial class DeviceContext(nint handle, IReadOnlyList<PhysicalMonitor> physicalMonitors) | ||||||
| : NativeResource(handle) | ||||||
| { | ||||||
| private readonly IReadOnlyList<PhysicalMonitor> _physicalMonitors = physicalMonitors; | ||||||
| private int _gammaChannelOffset; | ||||||
|
|
||||||
| private void SetGammaRamp(GammaRamp ramp) | ||||||
|
|
@@ -48,10 +53,46 @@ public void SetGamma(double redMultiplier, double greenMultiplier, double blueMu | |||||
| SetGammaRamp(ramp); | ||||||
| } | ||||||
|
|
||||||
| public void SetBrightness(double brightness) | ||||||
| { | ||||||
| // Brightness is a value from 0.0 to 1.0, convert to 0-100 | ||||||
| var brightnessPercentage = (uint)Math.Clamp(brightness * 100, 0, 100); | ||||||
|
|
||||||
| foreach (var physicalMonitor in _physicalMonitors) | ||||||
| { | ||||||
| if (!NativeMethods.SetMonitorBrightness(physicalMonitor.Handle, brightnessPercentage)) | ||||||
| { | ||||||
| Debug.WriteLine( | ||||||
| $"Failed to set brightness on monitor #{physicalMonitor.Handle}. " | ||||||
| + $"Error {Marshal.GetLastWin32Error()}." | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public void ResetGamma() => SetGamma(1, 1, 1); | ||||||
|
|
||||||
| protected override void Dispose(bool disposing) | ||||||
| { | ||||||
| if (_physicalMonitors.Any()) | ||||||
| { | ||||||
| // This is a bit of a hack, but we need to convert the read-only list to an array | ||||||
| var physicalMonitorsArray = _physicalMonitors.ToArray(); | ||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more efficient way to convert would be
Suggested change
|
||||||
|
|
||||||
| if ( | ||||||
| !NativeMethods.DestroyPhysicalMonitors( | ||||||
| (uint)physicalMonitorsArray.Length, | ||||||
| physicalMonitorsArray | ||||||
Tyrrrz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| ) | ||||||
| ) | ||||||
| { | ||||||
| Debug.WriteLine( | ||||||
| "Failed to destroy physical monitors. " | ||||||
| + $"Error {Marshal.GetLastWin32Error()}." | ||||||
| ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Don't reset gamma during dispose because this method is also called whenever | ||||||
| // the device context gets invalidated. | ||||||
| // Resetting gamma in such cases will cause unwanted flickering. | ||||||
|
|
@@ -68,7 +109,10 @@ protected override void Dispose(bool disposing) | |||||
|
|
||||||
| public partial class DeviceContext | ||||||
| { | ||||||
| public static DeviceContext? TryCreate(string deviceName) | ||||||
| public static DeviceContext? TryCreate( | ||||||
| string deviceName, | ||||||
| IReadOnlyList<PhysicalMonitor> physicalMonitors | ||||||
| ) | ||||||
| { | ||||||
| var handle = NativeMethods.CreateDC(deviceName, deviceName, null, 0); | ||||||
| if (handle == 0) | ||||||
|
|
@@ -80,6 +124,6 @@ public partial class DeviceContext | |||||
| return null; | ||||||
| } | ||||||
|
|
||||||
| return new DeviceContext(handle); | ||||||
| return new DeviceContext(handle, physicalMonitors); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| using System; | ||
| using System.Runtime.InteropServices; | ||
|
|
||
| namespace LightBulb.PlatformInterop.Internal; | ||
|
|
||
| [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] | ||
| public struct PhysicalMonitor | ||
| { | ||
| public nint Handle; | ||
|
|
||
| [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] | ||
| public string Description; | ||
| } | ||
|
|
||
| internal static partial class NativeMethods | ||
| { | ||
| [DllImport("dxva2.dll", SetLastError = true)] | ||
| public static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR( | ||
| nint hMonitor, | ||
| out uint pdwNumberOfPhysicalMonitors | ||
| ); | ||
|
|
||
| [DllImport("dxva2.dll", SetLastError = true)] | ||
| public static extern bool GetPhysicalMonitorsFromHMONITOR( | ||
| nint hMonitor, | ||
| uint dwPhysicalMonitorArraySize, | ||
| [Out] PhysicalMonitor[] pPhysicalMonitorArray | ||
| ); | ||
|
|
||
| [DllImport("dxva2.dll", SetLastError = true)] | ||
| public static extern bool SetMonitorBrightness(nint hMonitor, uint dwNewBrightness); | ||
|
|
||
| [DllImport("dxva2.dll", SetLastError = true)] | ||
| public static extern bool DestroyPhysicalMonitors( | ||
| uint dwPhysicalMonitorArraySize, | ||
| [In] PhysicalMonitor[] pPhysicalMonitorArray | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -167,10 +167,12 @@ public void SetGamma(ColorConfiguration configuration) | |
| foreach (var deviceContext in _deviceContexts) | ||
| { | ||
| deviceContext.SetGamma( | ||
| GetRed(configuration) * configuration.Brightness, | ||
| GetGreen(configuration) * configuration.Brightness, | ||
| GetBlue(configuration) * configuration.Brightness | ||
| GetRed(configuration), | ||
| GetGreen(configuration), | ||
| GetBlue(configuration) | ||
| ); | ||
|
|
||
| deviceContext.SetBrightness(configuration.Brightness); | ||
|
Comment on lines
169
to
+175
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be prudent to revert to the current behavior if the monitor brightness cannot be directly adjusted. Mainly because of this warning: I see two options:
Not sure how reliable the second approach is. It's theoretically possible that the WinAPI method may succeed, without actually updating the monitor brightness. So I think the first approach is better. Also, in case we do fall back to the original/previous/classic behavior, we need to clamp the brightness to >10% or something because lower values essentially make the pixels so dark (or completely black) that they are not discernible. But this is probably more related to #388 than this PR.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you need this to be more compatible, my suggestion would be to fall back to I guess we could also have a second fallback to the gamma multiplication, but I don't think that should be the default.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assuming we'd use
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use |
||
| } | ||
|
|
||
| _isUpdatingGamma = false; | ||
|
|
||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move the brightness control to the
Monitorclass and have theGammaServicecall that directly. TheMonitorclass is already a wrapper around anhMonitorhandle, which is the same as yourPhysicalMonitor, if I understand correctly.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding of the API's are that
physical monitors != hMonitor. TheGetPhysicalMonitorsFromHMONITORfunction takes an hMonitor pointer and returns an array of physical monitor structs per hMonitor. I'd be lying if I said I know exactly how it works, but the reference implementations I've found all store an array of physical monitors per hMonitor. This is why I felt it was best in DeviceContext - it's actually only very loosely related to the hMonitor, in the same way that your DeviceContext is created from an hMonitor. Plus we now have a single DeviceContext per "display" which is capable of adjusting gamma and brightness.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the remarks: