1.更换软件协议为AGPL

2.切换项目名称为Hua.Todo
This commit is contained in:
ShaoHua
2026-04-06 22:06:30 +08:00
parent 40a91e39b6
commit 758f6772c6
147 changed files with 1203 additions and 644 deletions
@@ -0,0 +1,143 @@
#if MACCATALYST
using AppKit;
using Foundation;
namespace Hua.Todo.Maui.Services.Platforms
{
/// <summary>
/// macOS 平台全局热键服务实现类
/// 使用 AppKit 框架实现全局热键功能
/// </summary>
public class MacGlobalHotKeyService : IGlobalHotKeyService
{
private NSObject? _eventMonitor;
private Action? _callback;
private bool _isRegistered;
private NSEventModifierMask _currentModifiers;
private string _currentKey;
/// <summary>
/// macOS 平台支持全局热键
/// </summary>
public bool IsSupported => true;
/// <summary>
/// 注册全局热键
/// </summary>
public void RegisterHotKey(string modifiers, string key, Action callback)
{
_callback = callback;
_currentModifiers = ParseModifiers(modifiers);
_currentKey = key.ToUpper();
if (_isRegistered)
{
UnregisterHotKey();
}
_eventMonitor = NSEvent.AddGlobalMonitorForEventsMatchingMask(
NSEventMask.KeyDown,
HandleKeyDown
);
_isRegistered = true;
}
/// <summary>
/// 注销全局热键
/// </summary>
public void UnregisterHotKey()
{
if (_eventMonitor != null)
{
NSEvent.RemoveMonitor(_eventMonitor);
_eventMonitor = null;
_isRegistered = false;
}
}
/// <summary>
/// 更新热键配置
/// </summary>
public void UpdateHotKey(string modifiers, string key)
{
if (_callback != null)
{
RegisterHotKey(modifiers, key, _callback);
}
}
/// <summary>
/// 处理键盘按下事件
/// </summary>
private void HandleKeyDown(NSEvent evt)
{
bool modifiersMatch = false;
if (_currentModifiers.HasFlag(AppKit.NSEventModifierMask.CommandKey) &&
evt.ModifierFlags.HasFlag(AppKit.NSEventModifierMask.CommandKey))
{
modifiersMatch = true;
}
if (_currentModifiers.HasFlag(AppKit.NSEventModifierMask.AlternateKey) &&
evt.ModifierFlags.HasFlag(AppKit.NSEventModifierMask.AlternateKey))
{
modifiersMatch = true;
}
if (_currentModifiers.HasFlag(AppKit.NSEventModifierMask.ControlKey) &&
evt.ModifierFlags.HasFlag(AppKit.NSEventModifierMask.ControlKey))
{
modifiersMatch = true;
}
if (_currentModifiers.HasFlag(AppKit.NSEventModifierMask.ShiftKey) &&
evt.ModifierFlags.HasFlag(AppKit.NSEventModifierMask.ShiftKey))
{
modifiersMatch = true;
}
if (modifiersMatch && evt.CharactersIgnoringModifiers == _currentKey)
{
_callback?.Invoke();
}
}
/// <summary>
/// 解析修饰键字符串
/// </summary>
private AppKit.NSEventModifierMask ParseModifiers(string modifiers)
{
AppKit.NSEventModifierMask mask = 0;
if (string.IsNullOrEmpty(modifiers)) return mask;
var parts = modifiers.Split(',');
foreach (var part in parts)
{
var p = part.Trim();
if (p.Equals("Command", StringComparison.OrdinalIgnoreCase) ||
p.Equals("Cmd", StringComparison.OrdinalIgnoreCase))
{
mask |= AppKit.NSEventModifierMask.CommandKey;
}
if (p.Equals("Option", StringComparison.OrdinalIgnoreCase) ||
p.Equals("Alt", StringComparison.OrdinalIgnoreCase))
{
mask |= AppKit.NSEventModifierMask.AlternateKey;
}
if (p.Equals("Control", StringComparison.OrdinalIgnoreCase))
{
mask |= AppKit.NSEventModifierMask.ControlKey;
}
if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase))
{
mask |= AppKit.NSEventModifierMask.ShiftKey;
}
}
return mask;
}
}
}
#endif
@@ -0,0 +1,127 @@
#if ANDROID
using Android.Content;
using Android.App;
using AndroidX.Core.App;
using AndroidX.Core.Content.PM;
using AndroidX.Core.Graphics.Drawable;
namespace Hua.Todo.Maui.Services.Platforms
{
/// <summary>
/// Android 平台全局热键服务实现类
/// 由于 Android 限制全局热键,使用通知快捷方式作为替代方案
/// </summary>
public class MobileGlobalHotKeyService : IGlobalHotKeyService
{
private Action? _callback;
/// <summary>
/// Android 平台不支持全局热键
/// </summary>
public bool IsSupported => false;
/// <summary>
/// 注册通知快捷方式作为热键替代方案
/// </summary>
public void RegisterHotKey(string modifiers, string key, Action callback)
{
_callback = callback;
RegisterAndroidNotificationShortcut();
}
/// <summary>
/// 注销快捷方式(空实现)
/// </summary>
public void UnregisterHotKey()
{
}
/// <summary>
/// 更新快捷方式配置
/// </summary>
public void UpdateHotKey(string modifiers, string key)
{
if (_callback != null)
{
RegisterHotKey(modifiers, key, _callback);
}
}
/// <summary>
/// 注册 Android 通知快捷方式
/// </summary>
private void RegisterAndroidNotificationShortcut()
{
try
{
var context = Android.App.Application.Context;
var shortcutId = "quick_entry_shortcut";
var intent = new Intent(context, typeof(MainActivity));
intent.SetAction(Intent.ActionView);
intent.PutExtra("action", "quick_entry");
var shortcutInfo = new ShortcutInfoCompat.Builder(context, shortcutId)
.SetShortLabel("快速记录")
.SetLongLabel("快速记录任务")
.SetIntent(intent)
.Build();
ShortcutManagerCompat.PushDynamicShortcut(context, shortcutInfo);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to register Android shortcut: {ex.Message}");
}
}
}
}
#endif
#if IOS
using Foundation;
using UIKit;
namespace Hua.Todo.Maui.Services.Platforms
{
/// <summary>
/// iOS 平台全局热键服务实现类
/// 由于 iOS 限制全局热键,提供空实现
/// </summary>
public class MobileGlobalHotKeyService : IGlobalHotKeyService
{
private Action? _callback;
/// <summary>
/// iOS 平台不支持全局热键
/// </summary>
public bool IsSupported => false;
/// <summary>
/// 注册热键(空实现)
/// </summary>
public void RegisterHotKey(string modifiers, string key, Action callback)
{
_callback = callback;
}
/// <summary>
/// 注销热键(空实现)
/// </summary>
public void UnregisterHotKey()
{
}
/// <summary>
/// 更新热键(空实现)
/// </summary>
public void UpdateHotKey(string modifiers, string key)
{
if (_callback != null)
{
RegisterHotKey(modifiers, key, _callback);
}
}
}
}
#endif
@@ -0,0 +1,189 @@
#if WINDOWS
using System.Runtime.InteropServices;
using WinRT.Interop;
using MauiWindow = Microsoft.Maui.Controls.Window;
namespace Hua.Todo.Maui.Services.Platforms
{
/// <summary>
/// Windows 平台全局热键服务实现类
/// 使用 Windows API 实现全局热键功能
/// </summary>
public class WindowsGlobalHotKeyService : IGlobalHotKeyService
{
private const int HOTKEY_ID = 9000;
private const int WM_HOTKEY = 0x0312;
private const int GWL_WNDPROC = -4;
public const uint MOD_ALT = 0x0001;
public const uint MOD_CONTROL = 0x0002;
public const uint MOD_SHIFT = 0x0004;
public const uint MOD_WIN = 0x0008;
[DllImport("user32.dll")]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private IntPtr _windowHandle;
private MauiWindow? _window;
private Action? _callback;
private bool _isRegistered;
private uint _currentModifiers;
private uint _currentKey;
private IntPtr _originalWndProc;
private WndProcDelegate? _wndProc;
/// <summary>
/// Windows 平台支持全局热键
/// </summary>
public bool IsSupported => true;
/// <summary>
/// 注册全局热键
/// </summary>
public void RegisterHotKey(string modifiers, string key, Action callback)
{
if (_window == null)
{
_window = Microsoft.Maui.Controls.Application.Current?.Windows.FirstOrDefault();
if (_window == null) return;
}
if (_window.Handler?.PlatformView is not Microsoft.UI.Xaml.Window platformWindow) return;
_windowHandle = WindowNative.GetWindowHandle(platformWindow);
if (_windowHandle == IntPtr.Zero) return;
_callback = callback;
_currentModifiers = ParseModifiers(modifiers);
_currentKey = ParseKey(key);
if (_isRegistered)
{
UnregisterHotKey();
}
if (RegisterHotKey(_windowHandle, HOTKEY_ID, _currentModifiers, _currentKey))
{
_isRegistered = true;
EnsureWndProcHook();
}
else
{
System.Diagnostics.Debug.WriteLine("Failed to register hotkey");
}
}
/// <summary>
/// 注销全局热键
/// </summary>
public void UnregisterHotKey()
{
if (_isRegistered)
{
UnregisterHotKey(_windowHandle, HOTKEY_ID);
_isRegistered = false;
}
if (_originalWndProc != IntPtr.Zero && _windowHandle != IntPtr.Zero)
{
SetWindowProc(_windowHandle, GWL_WNDPROC, _originalWndProc);
_originalWndProc = IntPtr.Zero;
_wndProc = null;
}
}
/// <summary>
/// 更新热键配置
/// </summary>
public void UpdateHotKey(string modifiers, string key)
{
if (_callback != null)
{
RegisterHotKey(modifiers, key, _callback);
}
}
private void EnsureWndProcHook()
{
if (_originalWndProc != IntPtr.Zero) return;
if (_windowHandle == IntPtr.Zero) return;
_wndProc = WndProc;
var newWndProcPtr = Marshal.GetFunctionPointerForDelegate(_wndProc);
_originalWndProc = SetWindowProc(_windowHandle, GWL_WNDPROC, newWndProcPtr);
}
private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == WM_HOTKEY && wParam == (IntPtr)HOTKEY_ID)
{
_callback?.Invoke();
}
if (_originalWndProc != IntPtr.Zero)
{
return CallWindowProc(_originalWndProc, hWnd, msg, wParam, lParam);
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
/// <summary>
/// 解析修饰键字符串
/// </summary>
private uint ParseModifiers(string modifiers)
{
uint mod = 0;
if (string.IsNullOrEmpty(modifiers)) return mod;
var parts = modifiers.Split(',');
foreach (var part in parts)
{
var p = part.Trim();
if (p.Equals("Control", StringComparison.OrdinalIgnoreCase)) mod |= MOD_CONTROL;
if (p.Equals("Alt", StringComparison.OrdinalIgnoreCase)) mod |= MOD_ALT;
if (p.Equals("Shift", StringComparison.OrdinalIgnoreCase)) mod |= MOD_SHIFT;
if (p.Equals("Windows", StringComparison.OrdinalIgnoreCase)) mod |= MOD_WIN;
}
return mod;
}
/// <summary>
/// 解析主键
/// </summary>
private uint ParseKey(string key)
{
if (key.Length == 1)
{
char c = char.ToUpper(key[0]);
if (c >= 'A' && c <= 'Z') return (uint)c;
if (c >= '0' && c <= '9') return (uint)c;
}
return 0x58; // Default 'X'
}
private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongW")]
private static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
private static IntPtr SetWindowProc(IntPtr hWnd, int nIndex, IntPtr newProc)
{
return IntPtr.Size == 8
? SetWindowLongPtr64(hWnd, nIndex, newProc)
: SetWindowLong32(hWnd, nIndex, newProc);
}
[DllImport("user32.dll")]
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
}
}
#endif
@@ -0,0 +1,84 @@
#if WINDOWS
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using Microsoft.UI.Xaml;
using WinRT.Interop;
using Hua.Todo.Maui.Services;
namespace Hua.Todo.Maui.Services.Platforms
{
public class WindowsSystemTrayService : ISystemTrayService, IDisposable
{
private NotifyIcon? _notifyIcon;
private Microsoft.Maui.Controls.Window? _window;
private Action? _onShowWindow;
private Action? _onExit;
private bool _disposed;
public void Initialize(Microsoft.Maui.Controls.Window window, Action onShowWindow, Action onExit)
{
_window = window;
_onShowWindow = onShowWindow;
_onExit = onExit;
_notifyIcon = new NotifyIcon();
_notifyIcon.Icon = GetAppIcon();
_notifyIcon.Text = GetNotifyIconText();
_notifyIcon.Visible = true;
_notifyIcon.DoubleClick += (s, e) => _onShowWindow?.Invoke();
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("打开主界面", null, (s, e) => _onShowWindow?.Invoke());
contextMenu.Items.Add("退出", null, (s, e) => _onExit?.Invoke());
_notifyIcon.ContextMenuStrip = contextMenu;
}
public void ShowBalloonTip(string title, string message)
{
_notifyIcon?.ShowBalloonTip(3000, title, message, ToolTipIcon.Info);
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_notifyIcon?.Dispose();
_notifyIcon = null;
}
private Icon GetAppIcon()
{
try
{
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith("icon.ico"));
if (resourceName != null)
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
if (stream != null)
{
return new Icon(stream);
}
}
}
}
catch
{
}
return SystemIcons.Application;
}
private static string GetNotifyIconText()
{
return AppMetadata.GetTrayTooltipText();
}
}
}
#endif