Skip to content

Commit

Permalink
Replace WinForms with raw WinAPI calls (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyrrrz authored Apr 3, 2024
1 parent 7de20b1 commit e1b1d0f
Show file tree
Hide file tree
Showing 53 changed files with 660 additions and 462 deletions.
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

0 comments on commit e1b1d0f

Please sign in to comment.