309 lines
11 KiB
C#
309 lines
11 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using System.Windows.Interop;
|
|
using Microsoft.Win32;
|
|
using TodoList.Services;
|
|
using TodoList.ViewModels;
|
|
using TodoList.Views;
|
|
using System.Linq;
|
|
|
|
namespace TodoList
|
|
{
|
|
public partial class App : System.Windows.Application
|
|
{
|
|
private IDataService _dataService;
|
|
private GlobalShortcutService _shortcutService;
|
|
private MainWindow _mainWindow;
|
|
private SettingsService _settingsService;
|
|
private System.Windows.Forms.NotifyIcon _notifyIcon;
|
|
private Mutex _mutex;
|
|
private EventWaitHandle _eventWaitHandle;
|
|
private const string UniqueEventName = "Global\\TodoListApp_Event_v1";
|
|
|
|
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
|
|
|
|
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
|
|
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
|
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
|
|
public App()
|
|
{
|
|
// Disable hardware acceleration to prevent black screen issues
|
|
// Must be set before any UI is created
|
|
System.Windows.Media.RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;
|
|
}
|
|
|
|
protected override void OnStartup(StartupEventArgs e)
|
|
{
|
|
// Ensure app doesn't shutdown when main window closes (we hide it)
|
|
this.ShutdownMode = ShutdownMode.OnExplicitShutdown;
|
|
|
|
const string appName = "Global\\TodoListApp_Unique_Mutex_v1";
|
|
bool createdNew;
|
|
_mutex = new Mutex(true, appName, out createdNew);
|
|
|
|
if (!createdNew)
|
|
{
|
|
// Signal the existing instance
|
|
try
|
|
{
|
|
using (var evt = EventWaitHandle.OpenExisting(UniqueEventName))
|
|
{
|
|
evt.Set();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// Fallback to old method if event open fails
|
|
var hWnd = FindWindow(null, "待办事项");
|
|
if (hWnd != IntPtr.Zero)
|
|
{
|
|
ShowWindow(hWnd, 9); // SW_RESTORE
|
|
SetForegroundWindow(hWnd);
|
|
}
|
|
}
|
|
|
|
// Force exit to prevent second instance from running
|
|
Environment.Exit(0);
|
|
return;
|
|
}
|
|
|
|
// Create the event handle for this instance
|
|
_eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
|
|
|
|
// Start a thread to listen for signals
|
|
Thread thread = new Thread(() =>
|
|
{
|
|
while (true)
|
|
{
|
|
_eventWaitHandle.WaitOne();
|
|
this.Dispatcher.Invoke(() => ShowMainWindow());
|
|
}
|
|
});
|
|
thread.IsBackground = true;
|
|
thread.Start();
|
|
|
|
base.OnStartup(e);
|
|
|
|
// Configure Auto Start
|
|
ConfigureAutoStart();
|
|
|
|
// Create Desktop/StartMenu Shortcut if needed (optional feature)
|
|
// CreateShortcut();
|
|
|
|
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
|
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|
|
|
try
|
|
{
|
|
_settingsService = new SettingsService();
|
|
_dataService = new FileDataService();
|
|
_shortcutService = new GlobalShortcutService();
|
|
|
|
var mainViewModel = new MainViewModel(_dataService, _settingsService);
|
|
|
|
_mainWindow = new MainWindow(mainViewModel);
|
|
_mainWindow.Loaded += MainWindow_Loaded;
|
|
|
|
// Initialize Tray Icon
|
|
InitializeTrayIcon();
|
|
|
|
// Check for silent mode
|
|
bool silent = e.Args.Contains("--silent") || e.Args.Contains("-s");
|
|
|
|
if (!silent)
|
|
{
|
|
_mainWindow.Show();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log("Startup Error: " + ex.ToString());
|
|
System.Windows.MessageBox.Show("Startup Error: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private void ConfigureAutoStart()
|
|
{
|
|
try
|
|
{
|
|
var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
|
|
string cmd = $"\"{exePath}\" --silent";
|
|
|
|
// If running as dotnet tool, try to find the shim or stable entry point
|
|
// Usually %USERPROFILE%\.dotnet\tools\todo.exe
|
|
var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
|
var toolShim = System.IO.Path.Combine(userProfile, ".dotnet", "tools", "todo.exe");
|
|
|
|
if (System.IO.File.Exists(toolShim))
|
|
{
|
|
// If the shim exists and we are likely running it (or just installed it), prefer the shim
|
|
// This handles updates better as the shim path stays constant.
|
|
// But we should verify if the current process IS related to it?
|
|
// Actually, if installed as tool, we definitely want to use the shim.
|
|
cmd = $"\"{toolShim}\" --silent";
|
|
}
|
|
|
|
var key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true);
|
|
if (key != null)
|
|
{
|
|
var existing = key.GetValue("TodoListApp");
|
|
if (existing == null || existing.ToString() != cmd)
|
|
{
|
|
key.SetValue("TodoListApp", cmd);
|
|
}
|
|
key.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log("AutoStart Error: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private void InitializeTrayIcon()
|
|
{
|
|
_notifyIcon = new System.Windows.Forms.NotifyIcon();
|
|
|
|
// Try load icon from resource or file
|
|
try
|
|
{
|
|
// Load from embedded resource
|
|
var resourceUri = new Uri("pack://application:,,,/icon.ico");
|
|
var streamInfo = System.Windows.Application.GetResourceStream(resourceUri);
|
|
|
|
if (streamInfo != null)
|
|
{
|
|
using (var stream = streamInfo.Stream)
|
|
{
|
|
_notifyIcon.Icon = new System.Drawing.Icon(stream);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_notifyIcon.Icon = System.Drawing.SystemIcons.Application;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
_notifyIcon.Icon = System.Drawing.SystemIcons.Application;
|
|
}
|
|
|
|
_notifyIcon.Visible = true;
|
|
_notifyIcon.Text = "TodoList";
|
|
_notifyIcon.DoubleClick += (s, e) => ShowMainWindow();
|
|
|
|
var contextMenu = new System.Windows.Forms.ContextMenuStrip();
|
|
contextMenu.Items.Add("打开主界面", null, (s, e) => ShowMainWindow());
|
|
contextMenu.Items.Add("退出", null, (s, e) => ExitApplication());
|
|
_notifyIcon.ContextMenuStrip = contextMenu;
|
|
}
|
|
|
|
private void ShowMainWindow()
|
|
{
|
|
Log("ShowMainWindow called");
|
|
if (_mainWindow != null)
|
|
{
|
|
if (_mainWindow.WindowState == WindowState.Minimized)
|
|
{
|
|
_mainWindow.WindowState = WindowState.Normal;
|
|
}
|
|
_mainWindow.Show();
|
|
_mainWindow.Activate();
|
|
|
|
// Force foreground
|
|
var helper = new WindowInteropHelper(_mainWindow);
|
|
var handle = helper.Handle;
|
|
if (handle != IntPtr.Zero)
|
|
{
|
|
SetForegroundWindow(handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExitApplication()
|
|
{
|
|
_notifyIcon?.Dispose();
|
|
_notifyIcon = null;
|
|
Shutdown();
|
|
}
|
|
|
|
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
|
|
{
|
|
Log("Dispatcher Error: " + e.Exception.ToString());
|
|
System.Windows.MessageBox.Show("Dispatcher Error: " + e.Exception.Message);
|
|
e.Handled = true;
|
|
}
|
|
|
|
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
{
|
|
Log("Domain Error: " + e.ExceptionObject.ToString());
|
|
System.Windows.MessageBox.Show("Critical Error: " + e.ExceptionObject.ToString());
|
|
}
|
|
|
|
private void Log(string message)
|
|
{
|
|
try
|
|
{
|
|
System.IO.File.AppendAllText("error.log", DateTime.Now + ": " + message + Environment.NewLine);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private bool _isHotkeyRegistered;
|
|
|
|
private void RegisterHotkey()
|
|
{
|
|
if (_isHotkeyRegistered || _shortcutService == null || _settingsService == null) return;
|
|
|
|
var helper = new WindowInteropHelper(_mainWindow);
|
|
var handle = helper.Handle;
|
|
|
|
if (handle != IntPtr.Zero)
|
|
{
|
|
var settings = _settingsService.Settings;
|
|
var mods = GlobalShortcutService.GetModifier(settings.ShortcutModifiers);
|
|
var key = GlobalShortcutService.GetKey(settings.ShortcutKey);
|
|
|
|
_shortcutService.Register(handle, OnHotKeyPressed, mods, key);
|
|
_isHotkeyRegistered = true;
|
|
|
|
// Subscribe to settings changes to update hotkey
|
|
_settingsService.Settings.PropertyChanged += (s, args) =>
|
|
{
|
|
if (args.PropertyName == nameof(AppSettings.ShortcutModifiers) ||
|
|
args.PropertyName == nameof(AppSettings.ShortcutKey))
|
|
{
|
|
var newMods = GlobalShortcutService.GetModifier(_settingsService.Settings.ShortcutModifiers);
|
|
var newKey = GlobalShortcutService.GetKey(_settingsService.Settings.ShortcutKey);
|
|
_shortcutService.UpdateShortcut(newMods, newKey);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
private void OnHotKeyPressed()
|
|
{
|
|
Log("Hotkey pressed.");
|
|
ShowMainWindow();
|
|
}
|
|
|
|
protected override void OnExit(ExitEventArgs e)
|
|
{
|
|
_notifyIcon?.Dispose();
|
|
_shortcutService?.Dispose();
|
|
base.OnExit(e);
|
|
}
|
|
|
|
// We can hook into MainWindow's Loaded event to register hotkey
|
|
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
RegisterHotkey();
|
|
}
|
|
}
|
|
}
|