diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index c412fb32f5a..81895fdcc3c 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -238,6 +238,7 @@ public SearchPrecisionScore QuerySearchPrecision public bool EnableUpdateLog { get; set; } public bool StartFlowLauncherOnSystemStartup { get; set; } = false; + public bool UseLogonTaskForStartup { get; set; } = false; public bool HideOnStartup { get; set; } = true; bool _hideNotifyIcon { get; set; } public bool HideNotifyIcon diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 4d1adc6cd51..f74e9a38886 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -119,7 +119,14 @@ private void AutoStartup() { try { - Helper.AutoStartup.Enable(); + if (_settings.UseLogonTaskForStartup) + { + Helper.AutoStartup.EnableViaLogonTask(); + } + else + { + Helper.AutoStartup.EnableViaRegistry(); + } } catch (Exception e) { diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 16228258f5f..af09334cc5d 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -100,6 +100,7 @@ + diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs index 4bff30caf03..c5e20504b7d 100644 --- a/Flow.Launcher/Helper/AutoStartup.cs +++ b/Flow.Launcher/Helper/AutoStartup.cs @@ -1,18 +1,31 @@ using System; +using System.IO; +using System.Linq; +using System.Security.Principal; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Microsoft.Win32; +using Microsoft.Win32.TaskScheduler; namespace Flow.Launcher.Helper; public class AutoStartup { private const string StartupPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; + private const string LogonTaskName = $"{Constant.FlowLauncher} Startup"; + private const string LogonTaskDesc = $"{Constant.FlowLauncher} Auto Startup"; public static bool IsEnabled { get { + // Check if logon task is enabled + if (CheckLogonTask()) + { + return true; + } + + // Check if registry is enabled try { using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); @@ -28,12 +41,74 @@ public static bool IsEnabled } } - public static void Disable() + private static bool CheckLogonTask() + { + using var taskService = new TaskService(); + var task = taskService.RootFolder.AllTasks.FirstOrDefault(t => t.Name == LogonTaskName); + if (task != null) + { + try + { + // Check if the action is the same as the current executable path + var action = task.Definition.Actions.FirstOrDefault()!.ToString().Trim(); + if (!Constant.ExecutablePath.Equals(action, StringComparison.OrdinalIgnoreCase) && !File.Exists(action)) + { + UnscheduleLogonTask(); + ScheduleLogonTask(); + } + + return true; + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Failed to check logon task: {e}"); + } + } + + return false; + } + + public static void DisableViaLogonTaskAndRegistry() + { + Disable(true); + Disable(false); + } + + public static void EnableViaLogonTask() + { + Enable(true); + } + + public static void EnableViaRegistry() + { + Enable(false); + } + + public static void ChangeToViaLogonTask() + { + Disable(false); + Enable(true); + } + + public static void ChangeToViaRegistry() + { + Disable(true); + Enable(false); + } + + private static void Disable(bool logonTask) { try { - using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); - key?.DeleteValue(Constant.FlowLauncher, false); + if (logonTask) + { + UnscheduleLogonTask(); + } + else + { + using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); + key?.DeleteValue(Constant.FlowLauncher, false); + } } catch (Exception e) { @@ -42,12 +117,19 @@ public static void Disable() } } - internal static void Enable() + private static void Enable(bool logonTask) { try { - using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); - key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\""); + if (logonTask) + { + ScheduleLogonTask(); + } + else + { + using var key = Registry.CurrentUser.OpenSubKey(StartupPath, true); + key?.SetValue(Constant.FlowLauncher, $"\"{Constant.ExecutablePath}\""); + } } catch (Exception e) { @@ -55,4 +137,54 @@ internal static void Enable() throw; } } + + private static bool ScheduleLogonTask() + { + using var td = TaskService.Instance.NewTask(); + td.RegistrationInfo.Description = LogonTaskDesc; + td.Triggers.Add(new LogonTrigger { UserId = WindowsIdentity.GetCurrent().Name, Delay = TimeSpan.FromSeconds(2) }); + td.Actions.Add(Constant.ExecutablePath); + + if (IsCurrentUserIsAdmin()) + { + td.Principal.RunLevel = TaskRunLevel.Highest; + } + + td.Settings.StopIfGoingOnBatteries = false; + td.Settings.DisallowStartIfOnBatteries = false; + td.Settings.ExecutionTimeLimit = TimeSpan.Zero; + + try + { + TaskService.Instance.RootFolder.RegisterTaskDefinition(LogonTaskName, td); + return true; + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Failed to schedule logon task: {e}"); + return false; + } + } + + private static bool UnscheduleLogonTask() + { + using var taskService = new TaskService(); + try + { + taskService.RootFolder.DeleteTask(LogonTaskName); + return true; + } + catch (Exception e) + { + Log.Error("AutoStartup", $"Failed to unschedule logon task: {e}"); + return false; + } + } + + private static bool IsCurrentUserIsAdmin() + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } } diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 4c465d61f52..cd9d0b02adb 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -46,6 +46,8 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Start Flow Launcher on system startup + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup Hide Flow Launcher when focus is lost Do not show new version notifications diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs index 3d94355e687..ab38cd51408 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -42,9 +42,20 @@ public bool StartFlowLauncherOnSystemStartup try { if (value) - AutoStartup.Enable(); + { + if (UseLogonTaskForStartup) + { + AutoStartup.EnableViaLogonTask(); + } + else + { + AutoStartup.EnableViaRegistry(); + } + } else - AutoStartup.Disable(); + { + AutoStartup.DisableViaLogonTaskAndRegistry(); + } } catch (Exception e) { @@ -54,6 +65,34 @@ public bool StartFlowLauncherOnSystemStartup } } + public bool UseLogonTaskForStartup + { + get => Settings.UseLogonTaskForStartup; + set + { + Settings.UseLogonTaskForStartup = value; + + if (StartFlowLauncherOnSystemStartup) + { + try + { + if (UseLogonTaskForStartup) + { + AutoStartup.ChangeToViaLogonTask(); + } + else + { + AutoStartup.ChangeToViaRegistry(); + } + } + catch (Exception e) + { + Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), + e.Message); + } + } + } + } public List SearchWindowScreens { get; } = DropdownDataGeneric.GetValues("SearchWindowScreen"); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml index 30e065b1601..a80e618e8b4 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -36,6 +36,13 @@ OnContent="{DynamicResource enable}" /> + + + +