Flow.Launcher/Flow.Launcher.Avalonia/Helper/HotKeyMapper.cs
Hongtao Zhang 0f9b9329dd Add hotkey recorder with manual modifier tracking
- Add HotkeyRecorderDialog with global keyboard hook for capturing hotkeys
- Implement manual modifier state tracking to handle swallowed key events
- Add HotkeyControl button that opens the recorder dialog
- Add CheckAvailability and RemoveToggleHotkey to HotKeyMapper
- Expose GetKeyFromVk helper in GlobalHotkey infrastructure
- Add Settings pages (General, Plugin, Theme, Proxy, About)
- Add PreviewPanel for result previews in main window
- Fix hook reuse issue by clearing callback on close instead of disposing
2026-01-18 02:10:53 -08:00

125 lines
3.4 KiB
C#

using System;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Avalonia.ViewModel;
using Flow.Launcher.Infrastructure.Hotkey;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
namespace Flow.Launcher.Avalonia.Helper;
/// <summary>
/// Hotkey mapper for Avalonia - registers and manages global hotkeys.
/// </summary>
internal static class HotKeyMapper
{
private static readonly string ClassName = nameof(HotKeyMapper);
private static Settings? _settings;
private static MainViewModel? _mainViewModel;
private static int _toggleHotkeyId = -1;
/// <summary>
/// Initialize the hotkey system and register configured hotkeys.
/// </summary>
internal static void Initialize()
{
_mainViewModel = Ioc.Default.GetRequiredService<MainViewModel>();
_settings = Ioc.Default.GetService<Settings>();
if (_settings == null)
{
Log.Warn(ClassName, "Settings not available, using default hotkey");
return;
}
// Initialize the global hotkey system
GlobalHotkey.Initialize();
// Register the main toggle hotkey
SetToggleHotkey(_settings.Hotkey);
Log.Info(ClassName, $"HotKeyMapper initialized with hotkey: {_settings.Hotkey}");
}
/// <summary>
/// Set or update the toggle hotkey.
/// </summary>
internal static void SetToggleHotkey(string hotkeyString)
{
RemoveToggleHotkey();
if (string.IsNullOrWhiteSpace(hotkeyString))
{
Log.Warn(ClassName, "Empty hotkey string");
return;
}
var (mods, key) = GlobalHotkey.ParseHotkeyString(hotkeyString);
if (key == 0)
{
Log.Error(ClassName, $"Failed to parse hotkey: {hotkeyString}");
return;
}
_toggleHotkeyId = GlobalHotkey.Register(mods, key, OnToggleHotkey);
if (_toggleHotkeyId < 0)
{
Log.Error(ClassName, $"Failed to register hotkey: {hotkeyString}");
}
else
{
Log.Info(ClassName, $"Registered toggle hotkey: {hotkeyString}");
}
}
/// <summary>
/// Remove the current toggle hotkey.
/// </summary>
internal static void RemoveToggleHotkey()
{
if (_toggleHotkeyId >= 0)
{
GlobalHotkey.Unregister(_toggleHotkeyId);
_toggleHotkeyId = -1;
}
}
private static void OnToggleHotkey()
{
Log.Info(ClassName, "Toggle hotkey triggered");
_mainViewModel?.ToggleFlowLauncher();
}
/// <summary>
/// Checks if a hotkey is available for registration.
/// </summary>
internal static bool CheckAvailability(HotkeyModel hotkey)
{
var hotkeyString = hotkey.ToString();
var (mods, key) = GlobalHotkey.ParseHotkeyString(hotkeyString);
if (key == 0)
return false;
// Try to register and immediately unregister
int id = GlobalHotkey.Register(mods, key, () => { });
if (id >= 0)
{
GlobalHotkey.Unregister(id);
return true;
}
return false;
}
/// <summary>
/// Cleanup and unregister all hotkeys.
/// </summary>
internal static void Shutdown()
{
GlobalHotkey.Shutdown();
Log.Info(ClassName, "HotKeyMapper shutdown");
}
}