From ac2ef16d85d239971d9e670f356c93e7740401aa Mon Sep 17 00:00:00 2001 From: Heavenly Avenger Date: Fri, 28 Mar 2025 07:09:23 -0300 Subject: [PATCH 1/2] Implements CLI saveAll command. This will do the same as the GUI does when using the hotkey or context action to save all open windows' positions. Usage examples: WindowResizer.CLI.exe saveAll WindowResizer.CLI.exe saveAll --config a:\apps\WinResizer\config.json WindowResizer.CLI.exe saveAll --profile CLIPositions Once saved, to restore them all WindowResizer.CLI.exe resize (if a different config file path is used to save, the same must be used to restore). --- src/WindowResizer.Base/ConfigUtils.cs | 30 +++++++++ src/WindowResizer.Base/WindowCmd.cs | 52 +++++++++++++++ .../Commands/SaveAllCommand.cs | 65 +++++++++++++++++++ src/WindowResizer.CLI/Program.cs | 1 + 4 files changed, 148 insertions(+) create mode 100755 src/WindowResizer.CLI/Commands/SaveAllCommand.cs diff --git a/src/WindowResizer.Base/ConfigUtils.cs b/src/WindowResizer.Base/ConfigUtils.cs index 6bbf4b8..b01774c 100644 --- a/src/WindowResizer.Base/ConfigUtils.cs +++ b/src/WindowResizer.Base/ConfigUtils.cs @@ -25,5 +25,35 @@ public static bool Load(string? configPath, Action? onError) return false; } } + + public static bool LoadOrCreate(string? configPath, string? profileName, Action? onError) + { + configPath ??= Path.Combine( + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(WindowResizer)), + DefaultConfigFile); + + profileName ??= string.Empty; + + try + { + ConfigFactory.Load(configPath); + } + catch (Exception ex) + { + if (ex is System.IO.FileNotFoundException) + { + var cnf = Config.NewConfig(profileName); + } + else + { + onError?.Invoke("Unexpected error while trying to load config file at \"" + configPath + "\": " + ex.Message); + return false; + } + } + + ConfigFactory.ConfigPath = configPath; + return true; + } + } } diff --git a/src/WindowResizer.Base/WindowCmd.cs b/src/WindowResizer.Base/WindowCmd.cs index e8067c7..56295cf 100644 --- a/src/WindowResizer.Base/WindowCmd.cs +++ b/src/WindowResizer.Base/WindowCmd.cs @@ -75,6 +75,48 @@ public static bool Resize(string? configPath, string? profileName, string? proce return true; } + public static bool SaveAll(string? configPath, string? profileName, + Action? onError = null, + Action>? onDebug = null) + { + var profile = LoadOrCreateConfig(configPath, profileName, onError); + + if (profile == null) + { + return false; + } + + profile.WindowSizes.Clear(); + + var windows = Resizer.GetOpenWindows(); + var targets = new List(); + + foreach (var handle in windows) + { + if (!IsProcessAvailable(handle, out string processName, null)) + { + continue; + } + + var t = Resizer.GetWindowTitle(handle); + + targets.Add(new TargetWindow(handle, processName, t)); + } + + foreach (var tp in targets) + { + UpdateOrSaveWindowSize(tp.Handle, profile, (p, e) => + { + tp.Result = "Elevated privileges may be required."; + onError?.Invoke($"Unable to save position for process <{p}>, elevated privileges may be required."); + }); + } + + onDebug?.Invoke(targets); + + return true; + } + public class TargetWindow { public TargetWindow(IntPtr handle, string processName, string? title) @@ -114,4 +156,14 @@ public TargetWindow(IntPtr handle, string processName, string? title) return p; } + + private static Config? LoadOrCreateConfig(string? configPath, string? profileName, Action? onError) + { + if (!ConfigUtils.LoadOrCreate(configPath, profileName, onError)) + { + return null; + } + + return ConfigFactory.Current; + } } diff --git a/src/WindowResizer.CLI/Commands/SaveAllCommand.cs b/src/WindowResizer.CLI/Commands/SaveAllCommand.cs new file mode 100755 index 0000000..2f06be4 --- /dev/null +++ b/src/WindowResizer.CLI/Commands/SaveAllCommand.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.CommandLine; +using System.Linq; +using System.Threading.Tasks; +using Spectre.Console; +using WindowResizer.Base; +using WindowResizer.CLI.Utils; + +namespace WindowResizer.CLI.Commands +{ + internal class SaveAllCommand : Command + { + public SaveAllCommand() : base("saveAll", "Saves the current position of all open windows.") + { + var configOption = new ConfigOption(); + AddOption(configOption); + var profileOption = new ProfileOption(); + AddOption(profileOption); + var verboseOption = new VerboseOption(); + AddOption(verboseOption); + + this.SetHandler((config, profile, verbose) => + { + void VerboseInfo(List lists) + { + if (verbose) + { + Verbose(lists); + } + } + + var success = WindowCmd.SaveAll(config?.FullName, profile, Output.Error, VerboseInfo); + + return Task.FromResult(success ? 0 : 1); + }, configOption, profileOption, verboseOption); + } + + private static void Verbose(List lists) + { + if (!lists.Any()) + { + Output.Echo("No windows to be saved."); + return; + } + + Output.Echo("The following windows were saved:"); + + var table = new Table(); + table.AddColumn(new TableColumn("Handle")); + table.AddColumn(new TableColumn("Process")); + table.AddColumn(new TableColumn("Title")); + table.AddColumn(new TableColumn("Success").Centered()); + table.AddColumn(new TableColumn("Error")); + foreach (var item in lists) + { + var result = string.IsNullOrEmpty(item.Result) ? "[green]Y[/]" : "[red]N[/]"; + table.AddRow(item.Handle.ToString(), $"[green]{item.ProcessName}[/]", item.Title ?? string.Empty, result, $"[red]{item.Result}[/]"); + } + + table.Border(TableBorder.Square); + table.Alignment(Justify.Left); + AnsiConsole.Write(table); + } + } +} diff --git a/src/WindowResizer.CLI/Program.cs b/src/WindowResizer.CLI/Program.cs index 970adc8..ca3bc0b 100644 --- a/src/WindowResizer.CLI/Program.cs +++ b/src/WindowResizer.CLI/Program.cs @@ -18,6 +18,7 @@ static Task Main(string[] args) var rootCommand = new RootCommand($"{nameof(WindowResizer)} CLI."); rootCommand.AddCommand(new ResizeCommand()); + rootCommand.AddCommand(new SaveAllCommand()); var parser = new CommandLineBuilder(rootCommand) .UseDefaults() From da3e2f4e42d2690157ee7169955552969bbcc15d Mon Sep 17 00:00:00 2001 From: Heavenly Avenger Date: Fri, 28 Mar 2025 08:42:14 -0300 Subject: [PATCH 2/2] Syncs up with the 'dev' branch. The 'Config' class was renamed to 'ProfileConfig'. Likewise, 'ConfigFactory' to 'ProfilesFactory'. --- src/WindowResizer.Base/ConfigUtils.cs | 6 +++--- src/WindowResizer.Base/WindowCmd.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/WindowResizer.Base/ConfigUtils.cs b/src/WindowResizer.Base/ConfigUtils.cs index b01774c..85f5b12 100644 --- a/src/WindowResizer.Base/ConfigUtils.cs +++ b/src/WindowResizer.Base/ConfigUtils.cs @@ -36,13 +36,13 @@ public static bool LoadOrCreate(string? configPath, string? profileName, Action< try { - ConfigFactory.Load(configPath); + ProfilesFactory.Load(configPath); } catch (Exception ex) { if (ex is System.IO.FileNotFoundException) { - var cnf = Config.NewConfig(profileName); + var cnf = ProfileConfig.NewConfig(profileName); } else { @@ -51,7 +51,7 @@ public static bool LoadOrCreate(string? configPath, string? profileName, Action< } } - ConfigFactory.ConfigPath = configPath; + ProfilesFactory.ConfigPath = configPath; return true; } diff --git a/src/WindowResizer.Base/WindowCmd.cs b/src/WindowResizer.Base/WindowCmd.cs index 56295cf..cad2283 100644 --- a/src/WindowResizer.Base/WindowCmd.cs +++ b/src/WindowResizer.Base/WindowCmd.cs @@ -157,13 +157,13 @@ public TargetWindow(IntPtr handle, string processName, string? title) return p; } - private static Config? LoadOrCreateConfig(string? configPath, string? profileName, Action? onError) + private static ProfileConfig? LoadOrCreateConfig(string? configPath, string? profileName, Action? onError) { if (!ConfigUtils.LoadOrCreate(configPath, profileName, onError)) { return null; } - return ConfigFactory.Current; + return ProfilesFactory.Current; } }