diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index d3f44530c..0fb6e2b3c 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -256,6 +256,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings 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 952ca70c4..8374fc9fe 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -136,7 +136,14 @@ namespace Flow.Launcher { 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 f8ace91f8..0baa1bef5 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -104,6 +104,7 @@ + diff --git a/Flow.Launcher/Helper/AutoStartup.cs b/Flow.Launcher/Helper/AutoStartup.cs index 4bff30caf..c5e20504b 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 class AutoStartup } } - 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 class AutoStartup } } - 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 @@ public class AutoStartup 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 c66772c83..93b45df91 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -47,6 +47,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 4e498ba23..dddaa99d4 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneGeneralViewModel.cs @@ -42,9 +42,20 @@ public partial class SettingsPaneGeneralViewModel : BaseModel 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 partial class SettingsPaneGeneralViewModel : BaseModel } } + 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 30e065b16..a80e618e8 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -36,6 +36,13 @@ OnContent="{DynamicResource enable}" /> + + + +