Skip to content
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

Replace WinForms with raw WinAPI calls #274

Merged
merged 15 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using LightBulb.WindowsApi.Types;
using System.Diagnostics;
using LightBulb.PlatformInterop.Internal;

namespace LightBulb.WindowsApi;
namespace LightBulb.PlatformInterop;

public partial class DeviceContext(nint handle) : NativeResource(handle)
{
Expand Down Expand Up @@ -52,17 +50,16 @@ protected override void Dispose(bool disposing)
// the device context gets invalidated.
// Resetting gamma in such cases will cause unwanted flickering.
// https://github.com/Tyrrrz/LightBulb/issues/206

if (!NativeMethods.DeleteDC(Handle))
Debug.WriteLine($"Failed to dispose device context #{Handle}.");
}
}

public partial class DeviceContext
{
public static DeviceContext? TryGetByName(string deviceName)
public static DeviceContext? TryCreate(string deviceName)
{
var handle = NativeMethods.CreateDC(deviceName, null, null, 0);
var handle = NativeMethods.CreateDC(deviceName, deviceName, null, 0);
if (handle == 0)
{
Debug.WriteLine($"Failed to retrieve device context for '{deviceName}'.");
Expand All @@ -71,18 +68,4 @@ public partial class DeviceContext

return new DeviceContext(handle);
}

public static IReadOnlyList<DeviceContext> GetAllScreens()
{
var result = new List<DeviceContext>();

foreach (var screen in Screen.AllScreens)
{
var deviceContext = TryGetByName(screen.DeviceName);
if (deviceContext is not null)
result.Add(deviceContext);
}

return result;
}
}
81 changes: 81 additions & 0 deletions LightBulb.PlatformInterop/GlobalHotKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Diagnostics;
using System.Threading;
using LightBulb.PlatformInterop.Internal;

namespace LightBulb.PlatformInterop;

public partial class GlobalHotKey : NativeResource<int>
{
private readonly object _lock = new();
private readonly IDisposable _wndProcRegistration;

private DateTimeOffset _lastTriggerTimestamp = DateTimeOffset.MinValue;

public GlobalHotKey(int id, Action callback)
: base(id)
{
_wndProcRegistration = WndProcSponge
.Default
.Listen(
0x312,
m =>
{
// Filter out other hotkey events
if (m.WParam != Handle)
return;

// Throttle triggers
lock (_lock)
{
if (
(DateTimeOffset.Now - _lastTriggerTimestamp).Duration().TotalSeconds
< 0.05
)
return;

_lastTriggerTimestamp = DateTimeOffset.Now;
}

callback();
}
);
}

protected override void Dispose(bool disposing)
{
if (disposing)
_wndProcRegistration.Dispose();

if (!NativeMethods.UnregisterHotKey(WndProcSponge.Default.Handle, Handle))
Debug.WriteLine($"Failed to dispose global hotkey #{Handle}.");
}
}

public partial class GlobalHotKey
{
private static int _lastHotKeyHandle;

public static GlobalHotKey? TryRegister(int virtualKey, int modifiers, Action callback)
{
var handle = Interlocked.Increment(ref _lastHotKeyHandle);

if (
!NativeMethods.RegisterHotKey(
WndProcSponge.Default.Handle,
handle,
modifiers,
virtualKey
)
)
{
Debug.WriteLine(
$"Failed to register global hotkey (key: {virtualKey}, mods: {modifiers})."
);

return null;
}

return new GlobalHotKey(handle, callback);
}
}
8 changes: 8 additions & 0 deletions LightBulb.PlatformInterop/Internal/EnumMonitorsProc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace LightBulb.PlatformInterop.Internal;

internal delegate bool EnumMonitorsProc(
nint hMonitor,
nint hdcMonitor,
Rect lprcMonitor,
nint dwData
);
3 changes: 3 additions & 0 deletions LightBulb.PlatformInterop/Internal/EnumWindowsProc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace LightBulb.PlatformInterop.Internal;

internal delegate bool EnumWindowsProc(nint hWnd, nint lParam);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Runtime.InteropServices;

namespace LightBulb.WindowsApi.Types;
namespace LightBulb.PlatformInterop.Internal;

[StructLayout(LayoutKind.Sequential)]
internal struct GammaRamp
Expand Down
17 changes: 17 additions & 0 deletions LightBulb.PlatformInterop/Internal/MonitorInfoEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;

namespace LightBulb.PlatformInterop.Internal;

[StructLayout(LayoutKind.Sequential)]
internal readonly record struct MonitorInfoEx
{
public MonitorInfoEx() => Size = Marshal.SizeOf(this);

public int Size { get; }
public Rect Monitor { get; init; }
public Rect WorkArea { get; init; }
public uint Flags { get; init; }

[field: MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string? DeviceName { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System.Runtime.InteropServices;
using LightBulb.WindowsApi.Types;

namespace LightBulb.WindowsApi;
namespace LightBulb.PlatformInterop.Internal;

internal static partial class NativeMethods
{
private const string Gdi32 = "gdi32.dll";

[DllImport(Gdi32, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport(Gdi32, SetLastError = true)]
public static extern nint CreateDC(
string? lpszDriver,
string? lpszDevice,
Expand All @@ -16,11 +15,11 @@ nint lpInitData
);

[DllImport(Gdi32, SetLastError = true)]
public static extern bool DeleteDC(nint hDc);
public static extern bool DeleteDC(nint hdc);

[DllImport(Gdi32, SetLastError = true)]
public static extern bool GetDeviceGammaRamp(nint hDc, out GammaRamp lpRamp);
public static extern bool GetDeviceGammaRamp(nint hdc, out GammaRamp lpRamp);

[DllImport(Gdi32, SetLastError = true)]
public static extern bool SetDeviceGammaRamp(nint hDc, ref GammaRamp lpRamp);
public static extern bool SetDeviceGammaRamp(nint hdc, ref GammaRamp lpRamp);
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
using System.Runtime.InteropServices;
using System.Text;
using LightBulb.WindowsApi.Types;

namespace LightBulb.WindowsApi;
namespace LightBulb.PlatformInterop.Internal;

internal static partial class NativeMethods
{
private const string Kernel32 = "kernel32.dll";

[DllImport(Kernel32, SetLastError = true)]
public static extern nint OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
uint processId
);
public static extern nint OpenProcess(uint processAccess, bool bInheritHandle, uint processId);

[DllImport(Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
[DllImport(Kernel32, SetLastError = true)]
public static extern bool QueryFullProcessImageName(
nint hPrc,
uint dwFlags,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,78 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using LightBulb.WindowsApi.Types;

namespace LightBulb.WindowsApi;
namespace LightBulb.PlatformInterop.Internal;

internal static partial class NativeMethods
{
private const string User32 = "user32.dll";

[DllImport(User32, SetLastError = true)]
public static extern nint GetForegroundWindow();
public static extern bool EnumDisplayMonitors(
nint hdc,
nint lprcClip,
EnumMonitorsProc lpfnEnum,
nint dwData
);

[DllImport(User32, SetLastError = true)]
public static extern bool GetWindowRect(nint hWnd, out Rect lpRect);
public static extern nint MonitorFromWindow(nint hWnd, uint dwFlags);

[DllImport(User32, SetLastError = true)]
public static extern bool GetClientRect(nint hWnd, out Rect lpRect);
public static extern bool GetMonitorInfo(nint hMonitor, ref MonitorInfoEx lpmi);

[DllImport(User32, SetLastError = true)]
public static extern bool IsWindowVisible(nint hWnd);
public static extern ushort RegisterClassEx(ref WndClassEx lpwcx);

[DllImport(User32, CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetClassName(nint hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport(User32, SetLastError = true)]
public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance);

[DllImport(User32, SetLastError = true)]
public static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
public static extern nint CreateWindowEx(
uint dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
nint hWndParent,
nint hMenu,
nint hInstance,
nint lpParam
);

public delegate bool EnumWindowsProc(nint hWnd, nint lParam);
[DllImport(User32, SetLastError = true)]
public static extern bool DestroyWindow(nint hWnd);

[DllImport(User32, SetLastError = true)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam);
public static extern nint DefWindowProc(nint hWnd, uint msg, nint wParam, nint lParam);

[DllImport(User32, SetLastError = true)]
public static extern bool RegisterHotKey(nint hWnd, int id, int fsModifiers, int vk);
public static extern bool PostMessage(nint hWnd, uint msg, nint wParam, nint lParam);

[DllImport(User32, SetLastError = true)]
public static extern bool UnregisterHotKey(nint hWnd, int id);
public static extern nint GetForegroundWindow();

[DllImport(User32, SetLastError = true)]
public static extern nint RegisterPowerSettingNotification(
nint hRecipient,
Guid powerSettingGuid,
int flags
);
public static extern bool GetWindowRect(nint hWnd, out Rect lpRect);

[DllImport(User32, SetLastError = true)]
public static extern bool UnregisterPowerSettingNotification(nint handle);
public static extern bool GetClientRect(nint hWnd, out Rect lpRect);

public delegate void WinEventProc(
nint hWinEventHook,
uint idEvent,
nint hWnd,
int idObject,
int idChild,
uint idEventThread,
uint dwmsEventTime
);
[DllImport(User32, SetLastError = true)]
public static extern bool IsWindowVisible(nint hWnd);

[DllImport(User32, SetLastError = true)]
public static extern int GetClassName(nint hWnd, StringBuilder lpClassName, int nMaxCount);

[DllImport(User32, SetLastError = true)]
public static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);

[DllImport(User32, SetLastError = true)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam);

[DllImport(User32, SetLastError = true)]
public static extern nint SetWinEventHook(
Expand All @@ -72,6 +88,22 @@ uint dwFlags
[DllImport(User32, SetLastError = true)]
public static extern bool UnhookWinEvent(nint hWinEventHook);

[DllImport(User32, CharSet = CharSet.Auto)]
[DllImport(User32, SetLastError = true)]
public static extern int MessageBox(nint hWnd, string text, string caption, uint type);

[DllImport(User32, SetLastError = true)]
public static extern bool RegisterHotKey(nint hWnd, int id, int fsModifiers, int vk);

[DllImport(User32, SetLastError = true)]
public static extern bool UnregisterHotKey(nint hWnd, int id);

[DllImport(User32, SetLastError = true)]
public static extern nint RegisterPowerSettingNotification(
nint hRecipient,
Guid powerSettingGuid,
int flags
);

[DllImport(User32, SetLastError = true)]
public static extern bool UnregisterPowerSettingNotification(nint handle);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace LightBulb.WindowsApi;
namespace LightBulb.PlatformInterop.Internal;

public abstract class NativeResource(nint handle) : IDisposable
public abstract class NativeResource<T>(T handle) : IDisposable
{
public nint Handle { get; } = handle;
public T Handle { get; } = handle;

[ExcludeFromCodeCoverage]
~NativeResource() => Dispose(false);
Expand All @@ -18,3 +18,5 @@ public void Dispose()
GC.SuppressFinalize(this);
}
}

public abstract class NativeResource(nint handle) : NativeResource<nint>(handle);
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;

namespace LightBulb.WindowsApi.Types;
namespace LightBulb.PlatformInterop.Internal;

[StructLayout(LayoutKind.Sequential)]
internal readonly record struct PowerBroadcastSetting(Guid PowerSettingId);
Loading