diff --git a/AGENTS.md b/AGENTS.md index 3be9caa..11ef3b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ For documentation-only changes, explain why build or test commands were skipped. - Radial Actions is a lightweight Windows utility opened by a global hotkey. Startup, hotkey registration, tray behavior, and menu display should stay fast and predictable. - User-configured actions must not execute during tests, settings load, preview rendering, or validation. - Invalid or stale settings should normalize to safe defaults instead of crashing on startup. -- Shell actions must preserve explicit target, arguments, and working directory behavior. +- Open actions (formerly Shell) must preserve explicit target, arguments, and working directory behavior. - Key actions must accept known media/volume actions and validated custom shortcuts; invalid shortcuts should fail clearly. - Update checks must remain controlled by settings and should not introduce surprise network work in deterministic tests. diff --git a/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs b/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs index 603d7a3..fb0157a 100644 --- a/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs +++ b/RadialActions.Tests/Actions/ActionDefaultsServiceTests.cs @@ -31,7 +31,7 @@ public void ApplyKeyDefaults_UpdatesIconOnlyForBlankDefaultOrPreviousAutoValue() } [Fact] - public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() + public void ApplyOpenDefaults_PreservesManualNameIconAndWorkingDirectory() { Directory.CreateDirectory(_tempRoot); var firstRoot = Path.Combine(_tempRoot, "First"); @@ -42,12 +42,12 @@ public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() var secondPath = Path.Combine(secondRoot, "Second.exe"); var service = new ActionDefaultsService(); - var action = PieAction.CreateShellAction("Manual Name", firstPath, "\U0001F6E0\uFE0F", workingDirectory: _tempRoot); + var action = PieAction.CreateOpenAction("Manual Name", firstPath, "\U0001F6E0\uFE0F", workingDirectory: _tempRoot); service.TrackExistingDefaults([action]); action.Parameter = secondPath; - service.ApplyShellDefaults(action, action.Parameter); + service.ApplyOpenDefaults(action, action.Parameter); Assert.Equal("Manual Name", action.Name); Assert.Equal("\U0001F6E0\uFE0F", action.Icon); @@ -55,16 +55,16 @@ public void ApplyShellDefaults_PreservesManualNameIconAndWorkingDirectory() } [Fact] - public void ApplyShellDefaults_LegacyStarIcon_UpgradesToSelectedDefaultIcon() + public void ApplyOpenDefaults_LegacyStarIcon_UpgradesToSelectedDefaultIcon() { Directory.CreateDirectory(_tempRoot); var targetPath = Path.Combine(_tempRoot, "First.exe"); var service = new ActionDefaultsService(); - var action = PieAction.CreateShellAction(PieAction.DefaultName, targetPath, ActionDefaultsService.LegacyDefaultIcon); + var action = PieAction.CreateOpenAction(PieAction.DefaultName, targetPath, ActionDefaultsService.LegacyDefaultIcon); - service.ApplyShellDefaults(action, action.Parameter); + service.ApplyOpenDefaults(action, action.Parameter); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); } public void Dispose() diff --git a/RadialActions.Tests/Actions/ActionDropFactoryTests.cs b/RadialActions.Tests/Actions/ActionDropFactoryTests.cs index d540c8f..83b66f0 100644 --- a/RadialActions.Tests/Actions/ActionDropFactoryTests.cs +++ b/RadialActions.Tests/Actions/ActionDropFactoryTests.cs @@ -157,9 +157,9 @@ public void CreateAction_BlankTarget_UsesDefaultNameAndFileIcon() { var action = ActionDropFactory.CreateAction(string.Empty); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(PieAction.DefaultName, action.Name); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); } [Fact] @@ -167,10 +167,10 @@ public void CreateAction_Url_FillsWebDefaults() { var action = ActionDropFactory.CreateAction("https://example.com/path"); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal("https://example.com/path", action.Parameter); Assert.Equal("example.com", action.Name); - Assert.Equal(ShellActionDefaults.WebIcon, action.Icon); + Assert.Equal(OpenActionDefaults.WebIcon, action.Icon); Assert.Equal(string.Empty, action.WorkingDirectory); } @@ -183,10 +183,10 @@ public void CreateAction_Directory_FillsFolderDefaults() var action = ActionDropFactory.CreateAction(folder); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(folder, action.Parameter); Assert.Equal("Games", action.Name); - Assert.Equal(ShellActionDefaults.FolderIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FolderIcon, action.Icon); Assert.Equal(folder, action.WorkingDirectory); } @@ -199,10 +199,10 @@ public void CreateAction_File_FillsFileDefaults() var action = ActionDropFactory.CreateAction(file); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.Equal(file, action.Parameter); Assert.Equal("Notes", action.Name); - Assert.Equal(ShellActionDefaults.FileIcon, action.Icon); + Assert.Equal(OpenActionDefaults.FileIcon, action.Icon); Assert.Equal(_tempRoot, action.WorkingDirectory); } diff --git a/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs b/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs index 0d3ac16..c0aeb74 100644 --- a/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs +++ b/RadialActions.Tests/Actions/ActionsSettingsViewModelTests.cs @@ -81,7 +81,7 @@ public void AddDroppedTargets_InsertsAfterSelectionAndSelectsLast() var addedA = viewModel.Actions[2]; var addedB = viewModel.Actions[3]; Assert.Equal([first, second, addedA, addedB, third], viewModel.Actions); - Assert.Equal(ActionType.Shell, addedA.Type); + Assert.Equal(ActionType.Open, addedA.Type); Assert.Equal("https://a.com", addedA.Parameter); Assert.Equal("https://b.com", addedB.Parameter); Assert.Same(addedB, viewModel.SelectedAction); @@ -121,7 +121,7 @@ public void ActionEditorViewModel_ActionTypes_HidesNoneType() { var viewModel = new ActionEditorViewModel(new ActionDefaultsService(), []); - Assert.Equal([ActionType.Key, ActionType.Shell], viewModel.ActionTypes.Select(option => option.Type)); + Assert.Equal([ActionType.Key, ActionType.Open], viewModel.ActionTypes.Select(option => option.Type)); } [Fact] diff --git a/RadialActions.Tests/Actions/ShellActionDefaultsTests.cs b/RadialActions.Tests/Actions/OpenActionDefaultsTests.cs similarity index 57% rename from RadialActions.Tests/Actions/ShellActionDefaultsTests.cs rename to RadialActions.Tests/Actions/OpenActionDefaultsTests.cs index 6d23e95..d6952ec 100644 --- a/RadialActions.Tests/Actions/ShellActionDefaultsTests.cs +++ b/RadialActions.Tests/Actions/OpenActionDefaultsTests.cs @@ -1,21 +1,21 @@ namespace RadialActions.Tests; -public sealed class ShellActionDefaultsTests : IDisposable +public sealed class OpenActionDefaultsTests : IDisposable { private readonly string _tempRoot = Path.Combine(Path.GetTempPath(), "RadialActions.Tests", Guid.NewGuid().ToString("N")); [Fact] public void GetDefaults_EmptyTarget_ReturnsNull() { - Assert.Null(ShellActionDefaults.FromTarget(string.Empty)); + Assert.Null(OpenActionDefaults.FromTarget(string.Empty)); } [Fact] public void GetDefaults_Url_UsesHostNameAndWebIcon() { - var defaults = ShellActionDefaults.FromTarget("https://docs.github.com/en"); + var defaults = OpenActionDefaults.FromTarget("https://docs.github.com/en"); - Assert.Equal(new ShellActionDefaults("docs.github.com", ShellActionDefaults.WebIcon, string.Empty), defaults); + Assert.Equal(new OpenActionDefaults("docs.github.com", OpenActionDefaults.WebIcon, string.Empty), defaults); } [Fact] @@ -25,9 +25,9 @@ public void GetDefaults_FilePath_UsesFileNameIconAndWorkingDirectory() var filePath = Path.Combine(_tempRoot, "Example App.exe"); File.WriteAllText(filePath, "test"); - var defaults = ShellActionDefaults.FromTarget(filePath); + var defaults = OpenActionDefaults.FromTarget(filePath); - Assert.Equal(new ShellActionDefaults("Example App", ShellActionDefaults.FileIcon, _tempRoot), defaults); + Assert.Equal(new OpenActionDefaults("Example App", OpenActionDefaults.FileIcon, _tempRoot), defaults); } [Fact] @@ -36,9 +36,9 @@ public void GetDefaults_FolderUri_UsesFolderNameIconAndWorkingDirectory() var folderPath = Path.Combine(_tempRoot, "Docs"); Directory.CreateDirectory(folderPath); - var defaults = ShellActionDefaults.FromTarget(new Uri(folderPath).AbsoluteUri); + var defaults = OpenActionDefaults.FromTarget(new Uri(folderPath).AbsoluteUri); - Assert.Equal(new ShellActionDefaults("Docs", ShellActionDefaults.FolderIcon, folderPath), defaults); + Assert.Equal(new OpenActionDefaults("Docs", OpenActionDefaults.FolderIcon, folderPath), defaults); } public void Dispose() diff --git a/RadialActions.Tests/Actions/PieActionTests.cs b/RadialActions.Tests/Actions/PieActionTests.cs index 5ab5781..a7fe0b0 100644 --- a/RadialActions.Tests/Actions/PieActionTests.cs +++ b/RadialActions.Tests/Actions/PieActionTests.cs @@ -15,11 +15,11 @@ public void CreateKeyAction_UnknownId_UsesFirstKnownAction() } [Fact] - public void CreateShellAction_SetsExpectedFields() + public void CreateOpenAction_SetsExpectedFields() { - var action = PieAction.CreateShellAction("Docs", "https://example.com", "*", "--foo", "C:\\"); + var action = PieAction.CreateOpenAction("Docs", "https://example.com", "*", "--foo", "C:\\"); - Assert.Equal(ActionType.Shell, action.Type); + Assert.Equal(ActionType.Open, action.Type); Assert.True(action.IsEnabled); Assert.Equal("Docs", action.Name); Assert.Equal("*", action.Icon); @@ -59,9 +59,9 @@ public void Execute_NoneAction_ThrowsInvalidOperationException() } [Fact] - public void Execute_ShellActionWithoutTarget_ThrowsInvalidOperationException() + public void Execute_OpenActionWithoutTarget_ThrowsInvalidOperationException() { - var action = PieAction.CreateShellAction("Docs", string.Empty); + var action = PieAction.CreateOpenAction("Docs", string.Empty); var ex = Assert.Throws(() => action.Execute()); diff --git a/RadialActions.Tests/Settings/SettingsSerializationTests.cs b/RadialActions.Tests/Settings/SettingsSerializationTests.cs index bb552eb..f0f3890 100644 --- a/RadialActions.Tests/Settings/SettingsSerializationTests.cs +++ b/RadialActions.Tests/Settings/SettingsSerializationTests.cs @@ -69,7 +69,7 @@ public void SerializeToJson_RoundTripsCoreValues() settings.Actions = new System.Collections.ObjectModel.ObservableCollection { PieAction.CreateKeyAction("Mute"), - PieAction.CreateShellAction("Explorer", "explorer.exe") + PieAction.CreateOpenAction("Explorer", "explorer.exe") }; settings.Actions[1].IsEnabled = false; @@ -83,7 +83,7 @@ public void SerializeToJson_RoundTripsCoreValues() Assert.Equal(ActionType.Key, loaded.Actions[0].Type); Assert.Equal("Mute", loaded.Actions[0].Parameter); Assert.True(loaded.Actions[0].IsEnabled); - Assert.Equal(ActionType.Shell, loaded.Actions[1].Type); + Assert.Equal(ActionType.Open, loaded.Actions[1].Type); Assert.Equal("explorer.exe", loaded.Actions[1].Parameter); Assert.False(loaded.Actions[1].IsEnabled); } diff --git a/RadialActions/Actions/Action.cs b/RadialActions/Actions/Action.cs index e1bf1ea..5fa3693 100644 --- a/RadialActions/Actions/Action.cs +++ b/RadialActions/Actions/Action.cs @@ -20,9 +20,9 @@ public enum ActionType Key = 1, /// - /// Launch an app, open a file, or open a URL using shell execution. + /// Open an app, file, folder, or URL using shell execution. /// - Shell = 2, + Open = 2, } /// @@ -114,13 +114,13 @@ public static bool TryGetKeyAction(string id, out KeyActionDefinition definition private string _parameter = string.Empty; /// - /// Additional arguments for shell actions. + /// Additional arguments for Open actions. /// [ObservableProperty] private string _arguments = string.Empty; /// - /// Optional working directory for shell actions. + /// Optional working directory for Open actions. /// [ObservableProperty] private string _workingDirectory = string.Empty; @@ -157,12 +157,12 @@ public static PieAction CreateKeyAction(string keyActionId) } /// - /// Creates a shell action. + /// Creates an Open action. /// - public static PieAction CreateShellAction(string name, string target, string icon = "📁", string arguments = "", string workingDirectory = "") + public static PieAction CreateOpenAction(string name, string target, string icon = "📁", string arguments = "", string workingDirectory = "") => new(name, icon) { - Type = ActionType.Shell, + Type = ActionType.Open, Parameter = target, Arguments = arguments, WorkingDirectory = workingDirectory @@ -182,8 +182,8 @@ public void Execute() case ActionType.Key: ExecuteKey(); return; - case ActionType.Shell: - ExecuteShell(); + case ActionType.Open: + ExecuteOpen(); return; default: throw new NotSupportedException("Action type is not supported"); @@ -207,7 +207,7 @@ private void ExecuteKey() ActionUtil.SimulateKeyboardShortcut(Parameter); } - private void ExecuteShell() + private void ExecuteOpen() { if (string.IsNullOrWhiteSpace(Parameter)) throw new InvalidOperationException("Launch target not configured"); diff --git a/RadialActions/Actions/ActionDefaultsService.cs b/RadialActions/Actions/ActionDefaultsService.cs index 6a1f264..f2a087b 100644 --- a/RadialActions/Actions/ActionDefaultsService.cs +++ b/RadialActions/Actions/ActionDefaultsService.cs @@ -5,20 +5,20 @@ public sealed class ActionDefaultsService public const string LegacyDefaultIcon = "\u2B50"; private readonly Dictionary _autoKeyDefaults = []; - private readonly Dictionary _autoShellDefaults = []; + private readonly Dictionary _autoOpenDefaults = []; - public ShellActionDefaults? GetShellDefaults(string target) => ShellActionDefaults.FromTarget(target); + public OpenActionDefaults? GetOpenDefaults(string target) => OpenActionDefaults.FromTarget(target); public void Forget(PieAction action) { _autoKeyDefaults.Remove(action); - _autoShellDefaults.Remove(action); + _autoOpenDefaults.Remove(action); } public void TrackExistingDefaults(IEnumerable actions) { _autoKeyDefaults.Clear(); - _autoShellDefaults.Clear(); + _autoOpenDefaults.Clear(); foreach (var action in actions) { @@ -29,12 +29,12 @@ public void TrackExistingDefaults(IEnumerable actions) _autoKeyDefaults[action] = definition; } } - else if (action.Type == ActionType.Shell) + else if (action.Type == ActionType.Open) { - var defaults = GetShellDefaults(action.Parameter); + var defaults = GetOpenDefaults(action.Parameter); if (defaults.HasValue) { - _autoShellDefaults[action] = defaults.Value; + _autoOpenDefaults[action] = defaults.Value; } } } @@ -69,16 +69,16 @@ public void ApplyKeyDefaults(PieAction action, KeyActionDefinition definition) _autoKeyDefaults[action] = definition; } - public void ApplyShellDefaults(PieAction action, string target) + public void ApplyOpenDefaults(PieAction action, string target) { - if (action.Type != ActionType.Shell) + if (action.Type != ActionType.Open) return; - var defaults = GetShellDefaults(target); + var defaults = GetOpenDefaults(target); if (!defaults.HasValue) return; - _autoShellDefaults.TryGetValue(action, out var previous); + _autoOpenDefaults.TryGetValue(action, out var previous); var next = defaults.Value; if (ShouldApplyDefault(action.Name, PieAction.DefaultName, previous.Name ?? string.Empty)) @@ -98,7 +98,7 @@ public void ApplyShellDefaults(PieAction action, string target) action.WorkingDirectory = next.WorkingDirectory; } - _autoShellDefaults[action] = next; + _autoOpenDefaults[action] = next; } private static bool ShouldApplyDefault(string currentValue, string defaultValue, string previousValue) diff --git a/RadialActions/Actions/ActionDropFactory.cs b/RadialActions/Actions/ActionDropFactory.cs index 733b91b..f83c17d 100644 --- a/RadialActions/Actions/ActionDropFactory.cs +++ b/RadialActions/Actions/ActionDropFactory.cs @@ -5,7 +5,7 @@ namespace RadialActions; /// -/// Turns data dropped onto the actions list (files, folders, apps, or links) into prefilled shell actions. +/// Turns data dropped onto the actions list (files, folders, apps, or links) into prefilled Open actions. /// public static class ActionDropFactory { @@ -57,14 +57,14 @@ void Add(string candidate) } /// - /// Builds a shell action for a single dropped target, prefilling its name, icon, and working directory from . + /// Builds an Open action for a single dropped target, prefilling its name, icon, and working directory from . /// public static PieAction CreateAction(string target) { - var defaults = ShellActionDefaults.FromTarget(target) - ?? new ShellActionDefaults(PieAction.DefaultName, ShellActionDefaults.FileIcon, string.Empty); + var defaults = OpenActionDefaults.FromTarget(target) + ?? new OpenActionDefaults(PieAction.DefaultName, OpenActionDefaults.FileIcon, string.Empty); - return PieAction.CreateShellAction(defaults.Name, target, defaults.Icon, workingDirectory: defaults.WorkingDirectory); + return PieAction.CreateOpenAction(defaults.Name, target, defaults.Icon, workingDirectory: defaults.WorkingDirectory); } // Reads the most specific link text the drop advertises, preferring the dedicated URL formats over plain text. diff --git a/RadialActions/Actions/ShellActionDefaults.cs b/RadialActions/Actions/OpenActionDefaults.cs similarity index 74% rename from RadialActions/Actions/ShellActionDefaults.cs rename to RadialActions/Actions/OpenActionDefaults.cs index cacc6e2..f5a32ab 100644 --- a/RadialActions/Actions/ShellActionDefaults.cs +++ b/RadialActions/Actions/OpenActionDefaults.cs @@ -2,13 +2,13 @@ namespace RadialActions; -public readonly record struct ShellActionDefaults(string Name, string Icon, string WorkingDirectory) +public readonly record struct OpenActionDefaults(string Name, string Icon, string WorkingDirectory) { public const string WebIcon = "\U0001F310"; public const string FolderIcon = "\U0001F4C2"; public const string FileIcon = "\U0001F4C1"; - public static ShellActionDefaults? FromTarget(string target) + public static OpenActionDefaults? FromTarget(string target) { if (string.IsNullOrWhiteSpace(target)) return null; @@ -21,13 +21,13 @@ public readonly record struct ShellActionDefaults(string Name, string Icon, stri } var name = string.IsNullOrWhiteSpace(uri.Host) ? uri.AbsoluteUri : uri.Host; - return new ShellActionDefaults(name, WebIcon, string.Empty); + return new OpenActionDefaults(name, WebIcon, string.Empty); } return FromPath(target); } - private static ShellActionDefaults FromPath(string path) + private static OpenActionDefaults FromPath(string path) { var isDirectory = Directory.Exists(path); var name = Path.GetFileNameWithoutExtension(path); @@ -44,6 +44,6 @@ private static ShellActionDefaults FromPath(string path) var icon = isDirectory ? FolderIcon : FileIcon; var workingDirectory = isDirectory ? path : Path.GetDirectoryName(path) ?? string.Empty; - return new ShellActionDefaults(name, icon, workingDirectory); + return new OpenActionDefaults(name, icon, workingDirectory); } } diff --git a/RadialActions/Properties/Settings.cs b/RadialActions/Properties/Settings.cs index 23b5df7..d739700 100644 --- a/RadialActions/Properties/Settings.cs +++ b/RadialActions/Properties/Settings.cs @@ -69,7 +69,7 @@ public static ObservableCollection CreateDefaultActions() PieAction.CreateKeyAction("PreviousTrack"), PieAction.CreateKeyAction("NextTrack"), PieAction.CreateKeyAction("Mute"), - PieAction.CreateShellAction("File Explorer", "explorer.exe", "📁"), + PieAction.CreateOpenAction("File Explorer", "explorer.exe", "📁"), ]; } diff --git a/RadialActions/Settings/ActionEditorView.xaml b/RadialActions/Settings/ActionEditorView.xaml index a066c41..e404d45 100644 --- a/RadialActions/Settings/ActionEditorView.xaml +++ b/RadialActions/Settings/ActionEditorView.xaml @@ -51,7 +51,7 @@ - + Visibility="{Binding SelectedActionType, Converter={local:MatchToVisibilityConverter}, ConverterParameter={x:Static local:ActionType.Open}}"> @@ -118,7 +118,7 @@ Text="{Binding SelectedAction.Parameter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />