Skip to content

Commit 9f5930f

Browse files
authored
Fix hotkeys not registering properly on startup (#324)
1 parent d1d8de8 commit 9f5930f

12 files changed

+82
-42
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace LightBulb.PlatformInterop.Internal;
4+
5+
internal static class NativeModule
6+
{
7+
public static nint CurrentHandle { get; } = Marshal.GetHINSTANCE(typeof(NativeModule).Module);
8+
}

LightBulb.PlatformInterop/Internal/WndClassEx.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,14 @@ namespace LightBulb.PlatformInterop.Internal;
55
[StructLayout(LayoutKind.Sequential)]
66
internal readonly record struct WndClassEx
77
{
8-
public WndClassEx()
9-
{
10-
Size = (uint)Marshal.SizeOf(this);
11-
Instance = Marshal.GetHINSTANCE(typeof(WndClassEx).Module);
12-
}
8+
public WndClassEx() => Size = (uint)Marshal.SizeOf(this);
139

1410
public uint Size { get; }
1511
public uint Style { get; init; }
1612
public required WndProc WndProc { get; init; }
1713
public int ClassExtra { get; init; }
1814
public int WindowExtra { get; init; }
19-
public nint Instance { get; }
15+
public nint Instance { get; init; }
2016
public nint Icon { get; init; }
2117
public nint Cursor { get; init; }
2218
public nint Background { get; init; }

LightBulb.PlatformInterop/Internal/WndProcSponge.cs

+10-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void Dispose()
3737
if (!NativeMethods.DestroyWindow(windowHandle))
3838
Debug.WriteLine($"Failed to destroy window #{windowHandle}.");
3939

40-
if (!NativeMethods.UnregisterClass(ClassName, classHandle))
40+
if (!NativeMethods.UnregisterClass(ClassName, NativeModule.CurrentHandle))
4141
Debug.WriteLine($"Failed to unregister window class #{classHandle}.");
4242
}
4343
}
@@ -67,7 +67,13 @@ internal partial class WndProcSponge
6767
}
6868
);
6969

70-
var classInfo = new WndClassEx { ClassName = ClassName, WndProc = wndProc };
70+
var classInfo = new WndClassEx
71+
{
72+
ClassName = ClassName,
73+
WndProc = wndProc,
74+
Instance = NativeModule.CurrentHandle
75+
};
76+
7177
var classHandle = NativeMethods.RegisterClassEx(ref classInfo);
7278
if (classHandle == 0)
7379
{
@@ -84,7 +90,7 @@ internal partial class WndProcSponge
8490
0,
8591
0,
8692
0,
87-
0,
93+
-3, // HWND_MESSAGE
8894
0,
8995
0,
9096
0
@@ -93,7 +99,7 @@ internal partial class WndProcSponge
9399
if (windowHandle == 0)
94100
{
95101
Debug.WriteLine("Failed to create window.");
96-
NativeMethods.UnregisterClass(ClassName, classHandle);
102+
NativeMethods.UnregisterClass(ClassName, NativeModule.CurrentHandle);
97103
return null;
98104
}
99105

LightBulb/App.axaml.cs

+11-8
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ public App()
7474
};
7575

7676
InitializeTheme();
77-
},
78-
false
77+
}
7978
)
8079
);
8180

@@ -95,13 +94,17 @@ public App()
9594
+ Environment.NewLine
9695
+ (_mainViewModel.Dashboard.IsActive ? status : "Disabled");
9796

98-
Dispatcher.UIThread.Invoke(() =>
97+
try
9998
{
100-
if (TrayIcon.GetIcons(this)?.FirstOrDefault() is { } trayIcon)
101-
trayIcon.ToolTipText = tooltip;
102-
});
103-
},
104-
false
99+
Dispatcher.UIThread.Invoke(() =>
100+
{
101+
if (TrayIcon.GetIcons(this)?.FirstOrDefault() is { } trayIcon)
102+
trayIcon.ToolTipText = tooltip;
103+
});
104+
}
105+
// Ignore exceptions when the application is shutting down
106+
catch (OperationCanceledException) { }
107+
}
105108
)
106109
);
107110
}

LightBulb/Services/HotKeyService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class HotKeyService : IDisposable
1515

1616
public void RegisterHotKey(HotKey hotKey, Action callback)
1717
{
18-
// Convert WPF key/modifiers to Windows API virtual key/modifiers
18+
// Convert Avalonia key/modifiers to Windows API virtual key/modifiers
1919
var virtualKey = KeyInterop.VirtualKeyFromKey(hotKey.Key.ToQwertyKey());
2020
var modifiers = (int)hotKey.Modifiers;
2121

LightBulb/Utils/Extensions/NotifyPropertyChangedExtensions.cs

+37-11
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,11 @@ public static IDisposable WatchProperty<TOwner, TProperty>(
1313
this TOwner owner,
1414
Expression<Func<TOwner, TProperty>> propertyExpression,
1515
Action callback,
16-
bool watchInitialValue = true
16+
bool watchInitialValue = false
1717
)
1818
where TOwner : INotifyPropertyChanged
1919
{
20-
var memberExpression =
21-
propertyExpression.Body as MemberExpression
22-
// Property value might be boxed inside a conversion expression, if the types don't match
23-
?? (propertyExpression.Body as UnaryExpression)?.Operand as MemberExpression;
24-
20+
var memberExpression = propertyExpression.Body as MemberExpression;
2521
if (memberExpression?.Member is not PropertyInfo property)
2622
throw new ArgumentException("Provided expression must reference a property.");
2723

@@ -48,21 +44,51 @@ public static IDisposable WatchProperties<TOwner>(
4844
this TOwner owner,
4945
IReadOnlyList<Expression<Func<TOwner, object?>>> propertyExpressions,
5046
Action callback,
51-
bool watchInitialValue = true
47+
bool watchInitialValue = false
5248
)
5349
where TOwner : INotifyPropertyChanged
5450
{
55-
var watchers = propertyExpressions
56-
.Select(x => WatchProperty(owner, x, callback, watchInitialValue))
51+
var properties = propertyExpressions
52+
.Select(expression =>
53+
{
54+
var memberExpression =
55+
expression.Body as MemberExpression
56+
// Because the expression is typed to return an object, the compiler will
57+
// implicitly wrap it in a conversion unary expression if it's of any other type.
58+
?? (expression.Body as UnaryExpression)?.Operand as MemberExpression;
59+
60+
if (memberExpression?.Member is not PropertyInfo property)
61+
throw new ArgumentException("Provided expression must reference a property.");
62+
63+
return property;
64+
})
5765
.ToArray();
5866

59-
return Disposable.Create(() => watchers.DisposeAll());
67+
void OnPropertyChanged(object? sender, PropertyChangedEventArgs args)
68+
{
69+
if (
70+
string.IsNullOrWhiteSpace(args.PropertyName)
71+
|| properties.Any(p =>
72+
string.Equals(args.PropertyName, p.Name, StringComparison.Ordinal)
73+
)
74+
)
75+
{
76+
callback();
77+
}
78+
}
79+
80+
owner.PropertyChanged += OnPropertyChanged;
81+
82+
if (watchInitialValue)
83+
callback();
84+
85+
return Disposable.Create(() => owner.PropertyChanged -= OnPropertyChanged);
6086
}
6187

6288
public static IDisposable WatchAllProperties<TOwner>(
6389
this TOwner owner,
6490
Action callback,
65-
bool watchInitialValues = true
91+
bool watchInitialValues = false
6692
)
6793
where TOwner : INotifyPropertyChanged
6894
{

LightBulb/ViewModels/Components/DashboardViewModel.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Linq;
3+
using Avalonia;
34
using CommunityToolkit.Mvvm.ComponentModel;
45
using CommunityToolkit.Mvvm.Input;
56
using LightBulb.Core;
@@ -100,6 +101,7 @@ ExternalApplicationService externalApplicationService
100101
// Re-register hotkeys when they get updated
101102
settingsService.WatchProperties(
102103
[
104+
o => o.FocusWindowHotKey,
103105
o => o.ToggleHotKey,
104106
o => o.IncreaseTemperatureOffsetHotKey,
105107
o => o.DecreaseTemperatureOffsetHotKey,
@@ -196,6 +198,14 @@ private void RegisterHotKeys()
196198
{
197199
_hotKeyService.UnregisterAllHotKeys();
198200

201+
if (_settingsService.FocusWindowHotKey != HotKey.None)
202+
{
203+
_hotKeyService.RegisterHotKey(
204+
_settingsService.FocusWindowHotKey,
205+
() => Application.Current?.TryFocusMainWindow()
206+
);
207+
}
208+
199209
if (_settingsService.ToggleHotKey != HotKey.None)
200210
{
201211
_hotKeyService.RegisterHotKey(
@@ -371,8 +381,6 @@ private void Initialize()
371381
_updateConfigurationTimer.Start();
372382
_updateIsPausedTimer.Start();
373383

374-
RegisterHotKeys();
375-
376384
// Hack: feign property changes to refresh the tray icon
377385
OnAllPropertiesChanged();
378386
}

LightBulb/ViewModels/Components/Settings/ApplicationWhitelistSettingsTabViewModel.cs

-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ private void RefreshApplications()
6767
protected override void Dispose(bool disposing)
6868
{
6969
if (disposing)
70-
{
7170
_eventRoot.Dispose();
72-
}
7371

7472
base.Dispose(disposing);
7573
}

LightBulb/ViewModels/Components/Settings/LocationSettingsTabViewModel.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public LocationSettingsTabViewModel(SettingsService settingsService)
2929
: base(settingsService, 1, "Location")
3030
{
3131
_eventRoot.Add(
32-
this.WatchProperty(o => o.Location, () => LocationQuery = Location?.ToString())
32+
this.WatchProperty(o => o.Location, () => LocationQuery = Location?.ToString(), true)
3333
);
3434
}
3535

@@ -118,9 +118,7 @@ private async Task ResolveLocationAsync()
118118
protected override void Dispose(bool disposing)
119119
{
120120
if (disposing)
121-
{
122121
_eventRoot.Dispose();
123-
}
124122

125123
base.Dispose(disposing);
126124
}

LightBulb/ViewModels/Components/Settings/SettingsTabViewModelBase.cs

-2
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,7 @@ string displayName
4141
protected override void Dispose(bool disposing)
4242
{
4343
if (disposing)
44-
{
4544
_eventRoot.Dispose();
46-
}
4745

4846
base.Dispose(disposing);
4947
}

LightBulb/ViewModels/MainViewModel.cs

-2
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,7 @@ private async Task ShowSettingsAsync() =>
194194
protected override void Dispose(bool disposing)
195195
{
196196
if (disposing)
197-
{
198197
_checkForUpdatesTimer.Dispose();
199-
}
200198

201199
base.Dispose(disposing);
202200
}

LightBulb/Views/Components/Settings/ApplicationWhitelistSettingsTabView.axaml.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ private void UserControl_OnLoaded(object? sender, RoutedEventArgs args)
3030
() =>
3131
WhitelistedApplicationsListBox.SelectedItems = new AvaloniaList<object>(
3232
DataContext.WhitelistedApplications ?? []
33-
)
33+
),
34+
true
3435
)
3536
);
3637
}

0 commit comments

Comments
 (0)