diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md
index c2699e7f9d..66a0daa1d8 100644
--- a/DATA_AND_PRIVACY.md
+++ b/DATA_AND_PRIVACY.md
@@ -444,6 +444,10 @@ _If you want to find diagnostic data events in the source code, these two links
Microsoft.PowerToys.FancyZones_ZoneWindowKeyUp |
Occurs when a key is released while interacting with zones. |
+
+ | Microsoft.PowerToys.FancyZones_CLICommand |
+ Triggered when a FancyZones CLI command is executed, logging the command name and success status. |
+
### FileExplorerAddOns
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/FancyZonesBaseCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/FancyZonesBaseCommand.cs
index d47fc42cdf..57fd4c3dad 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/FancyZonesBaseCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/FancyZonesBaseCommand.cs
@@ -8,6 +8,8 @@ using System.CommandLine.Invocation;
using FancyZonesCLI;
using FancyZonesCLI.CommandLine;
+using FancyZonesCLI.Telemetry;
+using Microsoft.PowerToys.Telemetry;
namespace FancyZonesCLI.CommandLine.Commands;
@@ -24,12 +26,14 @@ internal abstract class FancyZonesBaseCommand : Command
private void InvokeInternal(InvocationContext context)
{
Logger.LogInfo($"Executing command '{Name}'");
+ bool successful = false;
if (!FancyZonesCliGuards.IsFancyZonesRunning())
{
Logger.LogWarning($"Command '{Name}' blocked: FancyZones is not running");
- context.Console.Error.Write($"Error: FancyZones is not running. Start PowerToys (FancyZones) and retry.{Environment.NewLine}");
+ context.Console.Error.Write($"{Properties.Resources.error_fancyzones_not_running}{Environment.NewLine}");
context.ExitCode = 1;
+ LogTelemetry(successful: false);
return;
}
@@ -37,6 +41,7 @@ internal abstract class FancyZonesBaseCommand : Command
{
string output = Execute(context);
context.ExitCode = 0;
+ successful = true;
Logger.LogInfo($"Command '{Name}' completed successfully");
Logger.LogDebug($"Command '{Name}' output length: {output?.Length ?? 0}");
@@ -52,6 +57,28 @@ internal abstract class FancyZonesBaseCommand : Command
Logger.LogError($"Command '{Name}' failed", ex);
context.Console.Error.Write($"Error: {ex.Message}{Environment.NewLine}");
context.ExitCode = 1;
+ successful = false;
+ }
+ finally
+ {
+ LogTelemetry(successful);
+ }
+ }
+
+ private void LogTelemetry(bool successful)
+ {
+ try
+ {
+ PowerToysTelemetry.Log.WriteEvent(new FancyZonesCLICommandEvent
+ {
+ CommandName = Name,
+ Successful = successful,
+ });
+ }
+ catch (Exception ex)
+ {
+ // Don't fail the command if telemetry logging fails
+ Logger.LogError($"Failed to log telemetry for command '{Name}'", ex);
}
}
}
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetActiveLayoutCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetActiveLayoutCommand.cs
index d2fc0f4f9d..14e7fe71c5 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetActiveLayoutCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetActiveLayoutCommand.cs
@@ -15,7 +15,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class GetActiveLayoutCommand : FancyZonesBaseCommand
{
public GetActiveLayoutCommand()
- : base("get-active-layout", "Show currently active layout")
+ : base("get-active-layout", Properties.Resources.cmd_get_active_layout)
{
AddAlias("active");
}
@@ -28,7 +28,7 @@ internal sealed partial class GetActiveLayoutCommand : FancyZonesBaseCommand
if (editorParams.Monitors == null || editorParams.Monitors.Count == 0)
{
- throw new InvalidOperationException("Could not get current monitor information.");
+ throw new InvalidOperationException(Properties.Resources.get_active_layout_no_monitor_info);
}
// Read applied layouts.
@@ -36,11 +36,11 @@ internal sealed partial class GetActiveLayoutCommand : FancyZonesBaseCommand
if (appliedLayouts.AppliedLayouts == null)
{
- return "No layouts configured.";
+ return Properties.Resources.get_active_layout_no_layouts;
}
var sb = new System.Text.StringBuilder();
- sb.AppendLine("\n=== Active FancyZones Layout(s) ===\n");
+ sb.AppendLine($"\n{Properties.Resources.get_active_layout_header}\n");
// Show only layouts for currently connected monitors.
for (int i = 0; i < editorParams.Monitors.Count; i++)
@@ -71,7 +71,7 @@ internal sealed partial class GetActiveLayoutCommand : FancyZonesBaseCommand
}
else
{
- sb.AppendLine(" No layout applied");
+ sb.AppendLine(Properties.Resources.get_active_layout_no_layout);
}
if (i < editorParams.Monitors.Count - 1)
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetHotkeysCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetHotkeysCommand.cs
index 342598c822..7f91849922 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetHotkeysCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetHotkeysCommand.cs
@@ -15,7 +15,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class GetHotkeysCommand : FancyZonesBaseCommand
{
public GetHotkeysCommand()
- : base("get-hotkeys", "List all layout hotkeys")
+ : base("get-hotkeys", Properties.Resources.cmd_get_hotkeys)
{
AddAlias("hk");
}
@@ -26,12 +26,12 @@ internal sealed partial class GetHotkeysCommand : FancyZonesBaseCommand
if (hotkeys.LayoutHotkeys == null || hotkeys.LayoutHotkeys.Count == 0)
{
- return "No hotkeys configured.";
+ return Properties.Resources.get_hotkeys_no_hotkeys;
}
var sb = new System.Text.StringBuilder();
- sb.AppendLine("=== Layout Hotkeys ===\n");
- sb.AppendLine("Press Win + Ctrl + Alt + to switch layouts:\n");
+ sb.AppendLine($"{Properties.Resources.get_hotkeys_header}\n");
+ sb.AppendLine($"{Properties.Resources.get_hotkeys_instruction}\n");
foreach (var hotkey in hotkeys.LayoutHotkeys.OrderBy(h => h.Key))
{
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetLayoutsCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetLayoutsCommand.cs
index fc754fb95b..f7fbd43eb7 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetLayoutsCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetLayoutsCommand.cs
@@ -15,7 +15,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class GetLayoutsCommand : FancyZonesBaseCommand
{
public GetLayoutsCommand()
- : base("get-layouts", "List available layouts")
+ : base("get-layouts", Properties.Resources.cmd_get_layouts)
{
AddAlias("ls");
}
@@ -61,7 +61,7 @@ internal sealed partial class GetLayoutsCommand : FancyZonesBaseCommand
if (customLayouts.CustomLayouts != null)
{
- sb.AppendLine(CultureInfo.InvariantCulture, $"=== Custom Layouts ({customLayouts.CustomLayouts.Count} total) ===");
+ sb.AppendLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.get_layouts_custom_header, customLayouts.CustomLayouts.Count));
for (int i = 0; i < customLayouts.CustomLayouts.Count; i++)
{
@@ -92,8 +92,8 @@ internal sealed partial class GetLayoutsCommand : FancyZonesBaseCommand
// Add note for canvas layouts.
if (isCanvasLayout)
{
- sb.AppendLine("\n Note: Canvas layout preview is approximate.");
- sb.AppendLine(" Open FancyZones Editor for precise zone boundaries.");
+ sb.AppendLine($"\n {Properties.Resources.get_layouts_canvas_note}");
+ sb.AppendLine($" {Properties.Resources.get_layouts_canvas_detail}");
}
if (i < customLayouts.CustomLayouts.Count - 1)
@@ -102,7 +102,7 @@ internal sealed partial class GetLayoutsCommand : FancyZonesBaseCommand
}
}
- sb.AppendLine("\nUse 'FancyZonesCLI.exe set-layout ' to apply a layout.");
+ sb.AppendLine($"\n{Properties.Resources.get_layouts_usage}");
}
return sb.ToString().TrimEnd();
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetMonitorsCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetMonitorsCommand.cs
index f8e8ebb189..700ee656ce 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetMonitorsCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/GetMonitorsCommand.cs
@@ -15,7 +15,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class GetMonitorsCommand : FancyZonesBaseCommand
{
public GetMonitorsCommand()
- : base("get-monitors", "List monitors and FancyZones metadata")
+ : base("get-monitors", Properties.Resources.cmd_get_monitors)
{
AddAlias("m");
}
@@ -31,19 +31,19 @@ internal sealed partial class GetMonitorsCommand : FancyZonesBaseCommand
}
catch (Exception ex)
{
- throw new InvalidOperationException($"Failed to read monitor information. {ex.Message}{Environment.NewLine}Note: Ensure FancyZones is running to get current monitor information.", ex);
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.get_monitors_error, ex.Message), ex);
}
if (editorParams.Monitors == null || editorParams.Monitors.Count == 0)
{
- return "No monitors found.";
+ return Properties.Resources.get_monitors_no_monitors;
}
// Also read applied layouts to show which layout is active on each monitor.
var appliedLayouts = FancyZonesDataIO.ReadAppliedLayouts();
var sb = new System.Text.StringBuilder();
- sb.AppendLine(CultureInfo.InvariantCulture, $"=== Monitors ({editorParams.Monitors.Count} total) ===");
+ sb.AppendLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.get_monitors_header, editorParams.Monitors.Count));
sb.AppendLine();
for (int i = 0; i < editorParams.Monitors.Count; i++)
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenEditorCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenEditorCommand.cs
index 122d3a000f..5cdb6c1557 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenEditorCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenEditorCommand.cs
@@ -5,6 +5,7 @@
using System;
using System.CommandLine.Invocation;
using System.Diagnostics;
+using System.Globalization;
using System.Linq;
using System.Threading;
@@ -13,7 +14,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class OpenEditorCommand : FancyZonesBaseCommand
{
public OpenEditorCommand()
- : base("open-editor", "Launch FancyZones layout editor")
+ : base("open-editor", Properties.Resources.cmd_open_editor)
{
AddAlias("e");
}
@@ -38,7 +39,7 @@ internal sealed partial class OpenEditorCommand : FancyZonesBaseCommand
}
catch (Exception ex)
{
- throw new InvalidOperationException($"Failed to request FancyZones Editor launch. {ex.Message}", ex);
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.open_editor_error, ex.Message), ex);
}
}
}
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenSettingsCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenSettingsCommand.cs
index 84698bb9b6..ce55d82d79 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenSettingsCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/OpenSettingsCommand.cs
@@ -5,6 +5,7 @@
using System;
using System.CommandLine.Invocation;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
namespace FancyZonesCLI.CommandLine.Commands;
@@ -12,7 +13,7 @@ namespace FancyZonesCLI.CommandLine.Commands;
internal sealed partial class OpenSettingsCommand : FancyZonesBaseCommand
{
public OpenSettingsCommand()
- : base("open-settings", "Open FancyZones settings page")
+ : base("open-settings", Properties.Resources.cmd_open_settings)
{
AddAlias("settings");
}
@@ -37,14 +38,14 @@ internal sealed partial class OpenSettingsCommand : FancyZonesBaseCommand
if (process == null)
{
- throw new InvalidOperationException("PowerToys.exe failed to start.");
+ throw new InvalidOperationException(Properties.Resources.open_settings_error_not_started);
}
return string.Empty;
}
catch (Exception ex)
{
- throw new InvalidOperationException($"Failed to open FancyZones Settings. {ex.Message}", ex);
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.open_settings_error, ex.Message), ex);
}
}
}
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/RemoveHotkeyCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/RemoveHotkeyCommand.cs
index 4f88e268c5..64de0bcc8a 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/RemoveHotkeyCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/RemoveHotkeyCommand.cs
@@ -5,6 +5,7 @@
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
+using System.Globalization;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
@@ -16,11 +17,11 @@ internal sealed partial class RemoveHotkeyCommand : FancyZonesBaseCommand
private readonly Argument _key;
public RemoveHotkeyCommand()
- : base("remove-hotkey", "Remove hotkey assignment")
+ : base("remove-hotkey", Properties.Resources.cmd_remove_hotkey)
{
AddAlias("rhk");
- _key = new Argument("key", "Hotkey index (0-9)");
+ _key = new Argument("key", Properties.Resources.remove_hotkey_arg_key);
AddArgument(_key);
}
@@ -33,14 +34,14 @@ internal sealed partial class RemoveHotkeyCommand : FancyZonesBaseCommand
if (hotkeysWrapper.LayoutHotkeys == null)
{
- return "No hotkeys configured.";
+ return Properties.Resources.remove_hotkey_no_hotkeys;
}
var hotkeysList = hotkeysWrapper.LayoutHotkeys;
var removed = hotkeysList.RemoveAll(h => h.Key == key);
if (removed == 0)
{
- return $"No hotkey assigned to key {key}";
+ return string.Format(CultureInfo.InvariantCulture, Properties.Resources.remove_hotkey_not_found, key);
}
// Save.
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetHotkeyCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetHotkeyCommand.cs
index 4982be284c..993bb4b095 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetHotkeyCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetHotkeyCommand.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.Invocation;
+using System.Globalization;
using System.Linq;
using FancyZonesEditorCommon.Data;
@@ -19,12 +20,12 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
private readonly Argument _layout;
public SetHotkeyCommand()
- : base("set-hotkey", "Assign hotkey (0-9) to a custom layout")
+ : base("set-hotkey", Properties.Resources.cmd_set_hotkey)
{
AddAlias("shk");
- _key = new Argument("key", "Hotkey index (0-9)");
- _layout = new Argument("layout", "Custom layout UUID");
+ _key = new Argument("key", Properties.Resources.set_hotkey_arg_key);
+ _layout = new Argument("layout", Properties.Resources.set_hotkey_arg_layout);
AddArgument(_key);
AddArgument(_layout);
@@ -38,7 +39,7 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
if (key < 0 || key > 9)
{
- throw new InvalidOperationException("Key must be between 0 and 9.");
+ throw new InvalidOperationException(Properties.Resources.set_hotkey_error_invalid_key);
}
// Editor only allows assigning hotkeys to existing custom layouts.
@@ -59,7 +60,7 @@ internal sealed partial class SetHotkeyCommand : FancyZonesBaseCommand
if (!matchedLayout.HasValue)
{
- throw new InvalidOperationException($"Layout '{layout}' is not a custom layout UUID.");
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_hotkey_error_not_custom, layout));
}
string layoutName = matchedLayout.Value.Name;
diff --git a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetLayoutCommand.cs b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetLayoutCommand.cs
index 3d2a204af9..1b3ca079b5 100644
--- a/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetLayoutCommand.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/CommandLine/Commands/SetLayoutCommand.cs
@@ -26,14 +26,14 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
private readonly Option _all;
public SetLayoutCommand()
- : base("set-layout", "Set layout by UUID or template name")
+ : base("set-layout", Properties.Resources.cmd_set_layout)
{
AddAlias("s");
- _layoutId = new Argument("layout", "Layout UUID or template type (e.g. focus, columns)");
+ _layoutId = new Argument("layout", Properties.Resources.set_layout_arg_layout);
AddArgument(_layoutId);
- _monitor = new Option(AliasesMonitor, "Apply to monitor N (1-based)");
+ _monitor = new Option(AliasesMonitor, Properties.Resources.set_layout_opt_monitor);
_monitor.AddValidator(result =>
{
if (result.Tokens.Count == 0)
@@ -44,11 +44,11 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
int? monitor = result.GetValueOrDefault();
if (monitor.HasValue && monitor.Value < 1)
{
- result.ErrorMessage = "Monitor index must be >= 1.";
+ result.ErrorMessage = Properties.Resources.set_layout_error_monitor_index;
}
});
- _all = new Option(AliasesAll, "Apply to all monitors");
+ _all = new Option(AliasesAll, Properties.Resources.set_layout_opt_all);
AddOption(_monitor);
AddOption(_all);
@@ -60,7 +60,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
if (monitor.HasValue && all)
{
- commandResult.ErrorMessage = "Cannot specify both --monitor and --all.";
+ commandResult.ErrorMessage = Properties.Resources.set_layout_error_both_options;
}
});
}
@@ -97,15 +97,15 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
{
if (all)
{
- return string.Format(CultureInfo.InvariantCulture, "Layout '{0}' applied to all monitors.", layout);
+ return string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_success_all, layout);
}
if (monitor.HasValue)
{
- return string.Format(CultureInfo.InvariantCulture, "Layout '{0}' applied to monitor {1}.", layout, monitor.Value);
+ return string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_success_monitor, layout, monitor.Value);
}
- return string.Format(CultureInfo.InvariantCulture, "Layout '{0}' applied to monitor 1.", layout);
+ return string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_success_default, layout);
}
private static (CustomLayouts.CustomLayoutWrapper? TargetCustomLayout, LayoutTemplates.TemplateLayoutWrapper? TargetTemplate) ResolveTargetLayout(string layout)
@@ -127,10 +127,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
if (!targetCustomLayout.HasValue && !targetTemplate.HasValue)
{
- throw new InvalidOperationException(
- $"Layout '{layout}' not found{Environment.NewLine}" +
- "Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid', 'priority-grid')" +
- $"{Environment.NewLine} For custom layouts, use the UUID from 'get-layouts'");
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_error_not_found, layout));
}
return (targetCustomLayout, targetTemplate);
@@ -197,7 +194,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
int monitorIndex = monitor.Value - 1; // Convert to 0-based.
if (monitorIndex < 0 || monitorIndex >= editorParams.Monitors.Count)
{
- throw new InvalidOperationException($"Monitor {monitor.Value} not found. Available monitors: 1-{editorParams.Monitors.Count}");
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_error_monitor_not_found, monitor.Value, editorParams.Monitors.Count));
}
result.Add(monitorIndex);
@@ -250,7 +247,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
if (newLayouts.Count == 0)
{
- throw new InvalidOperationException("Internal error - no monitors to update.");
+ throw new InvalidOperationException(Properties.Resources.set_layout_error_no_monitors);
}
return newLayouts;
@@ -306,7 +303,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
}
else
{
- throw new InvalidOperationException($"Unsupported custom layout type '{targetCustomLayout.Value.Type}'.");
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.set_layout_error_unsupported_type, targetCustomLayout.Value.Type));
}
return (
@@ -329,7 +326,7 @@ internal sealed partial class SetLayoutCommand : FancyZonesBaseCommand
targetTemplate.Value.SensitivityRadius);
}
- throw new InvalidOperationException("Internal error - no layout selected.");
+ throw new InvalidOperationException(Properties.Resources.set_layout_error_no_layout);
}
private static AppliedLayouts.AppliedLayoutsListWrapper MergeWithHistoricalLayouts(
diff --git a/src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj b/src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj
index 36274c95ef..f1198d5b40 100644
--- a/src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj
+++ b/src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj
@@ -13,7 +13,7 @@
false
..\..\..\..\$(Platform)\$(Configuration)
FancyZonesCLI
- $(NoWarn);SA1500;SA1402;CA1852
+ $(NoWarn);SA1500;SA1402;CA1852;CA1863;CA1305
@@ -24,6 +24,22 @@
+
+
+
+
+
+ Resources.resx
+ True
+ True
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
diff --git a/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.Designer.cs b/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..3df0a9c606
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.Designer.cs
@@ -0,0 +1,353 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace FancyZonesCLI.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FancyZonesCLI.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ internal static string error_fancyzones_not_running {
+ get {
+ return ResourceManager.GetString("error_fancyzones_not_running", resourceCulture);
+ }
+ }
+
+ internal static string cmd_get_active_layout {
+ get {
+ return ResourceManager.GetString("cmd_get_active_layout", resourceCulture);
+ }
+ }
+
+ internal static string get_active_layout_no_monitor_info {
+ get {
+ return ResourceManager.GetString("get_active_layout_no_monitor_info", resourceCulture);
+ }
+ }
+
+ internal static string get_active_layout_no_layouts {
+ get {
+ return ResourceManager.GetString("get_active_layout_no_layouts", resourceCulture);
+ }
+ }
+
+ internal static string get_active_layout_header {
+ get {
+ return ResourceManager.GetString("get_active_layout_header", resourceCulture);
+ }
+ }
+
+ internal static string get_active_layout_no_layout {
+ get {
+ return ResourceManager.GetString("get_active_layout_no_layout", resourceCulture);
+ }
+ }
+
+ internal static string cmd_get_layouts {
+ get {
+ return ResourceManager.GetString("cmd_get_layouts", resourceCulture);
+ }
+ }
+
+ internal static string get_layouts_templates_header {
+ get {
+ return ResourceManager.GetString("get_layouts_templates_header", resourceCulture);
+ }
+ }
+
+ internal static string get_layouts_custom_header {
+ get {
+ return ResourceManager.GetString("get_layouts_custom_header", resourceCulture);
+ }
+ }
+
+ internal static string get_layouts_canvas_note {
+ get {
+ return ResourceManager.GetString("get_layouts_canvas_note", resourceCulture);
+ }
+ }
+
+ internal static string get_layouts_canvas_detail {
+ get {
+ return ResourceManager.GetString("get_layouts_canvas_detail", resourceCulture);
+ }
+ }
+
+ internal static string get_layouts_usage {
+ get {
+ return ResourceManager.GetString("get_layouts_usage", resourceCulture);
+ }
+ }
+
+ internal static string cmd_get_monitors {
+ get {
+ return ResourceManager.GetString("cmd_get_monitors", resourceCulture);
+ }
+ }
+
+ internal static string get_monitors_error {
+ get {
+ return ResourceManager.GetString("get_monitors_error", resourceCulture);
+ }
+ }
+
+ internal static string get_monitors_no_monitors {
+ get {
+ return ResourceManager.GetString("get_monitors_no_monitors", resourceCulture);
+ }
+ }
+
+ internal static string get_monitors_header {
+ get {
+ return ResourceManager.GetString("get_monitors_header", resourceCulture);
+ }
+ }
+
+ internal static string cmd_set_layout {
+ get {
+ return ResourceManager.GetString("cmd_set_layout", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_arg_layout {
+ get {
+ return ResourceManager.GetString("set_layout_arg_layout", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_opt_monitor {
+ get {
+ return ResourceManager.GetString("set_layout_opt_monitor", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_opt_all {
+ get {
+ return ResourceManager.GetString("set_layout_opt_all", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_monitor_index {
+ get {
+ return ResourceManager.GetString("set_layout_error_monitor_index", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_both_options {
+ get {
+ return ResourceManager.GetString("set_layout_error_both_options", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_not_found {
+ get {
+ return ResourceManager.GetString("set_layout_error_not_found", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_monitor_not_found {
+ get {
+ return ResourceManager.GetString("set_layout_error_monitor_not_found", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_no_monitors {
+ get {
+ return ResourceManager.GetString("set_layout_error_no_monitors", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_unsupported_type {
+ get {
+ return ResourceManager.GetString("set_layout_error_unsupported_type", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_error_no_layout {
+ get {
+ return ResourceManager.GetString("set_layout_error_no_layout", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_success_all {
+ get {
+ return ResourceManager.GetString("set_layout_success_all", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_success_monitor {
+ get {
+ return ResourceManager.GetString("set_layout_success_monitor", resourceCulture);
+ }
+ }
+
+ internal static string set_layout_success_default {
+ get {
+ return ResourceManager.GetString("set_layout_success_default", resourceCulture);
+ }
+ }
+
+ internal static string cmd_open_editor {
+ get {
+ return ResourceManager.GetString("cmd_open_editor", resourceCulture);
+ }
+ }
+
+ internal static string open_editor_error {
+ get {
+ return ResourceManager.GetString("open_editor_error", resourceCulture);
+ }
+ }
+
+ internal static string cmd_open_settings {
+ get {
+ return ResourceManager.GetString("cmd_open_settings", resourceCulture);
+ }
+ }
+
+ internal static string open_settings_error_not_started {
+ get {
+ return ResourceManager.GetString("open_settings_error_not_started", resourceCulture);
+ }
+ }
+
+ internal static string open_settings_error {
+ get {
+ return ResourceManager.GetString("open_settings_error", resourceCulture);
+ }
+ }
+
+ internal static string cmd_set_hotkey {
+ get {
+ return ResourceManager.GetString("cmd_set_hotkey", resourceCulture);
+ }
+ }
+
+ internal static string set_hotkey_arg_key {
+ get {
+ return ResourceManager.GetString("set_hotkey_arg_key", resourceCulture);
+ }
+ }
+
+ internal static string set_hotkey_arg_layout {
+ get {
+ return ResourceManager.GetString("set_hotkey_arg_layout", resourceCulture);
+ }
+ }
+
+ internal static string set_hotkey_error_invalid_key {
+ get {
+ return ResourceManager.GetString("set_hotkey_error_invalid_key", resourceCulture);
+ }
+ }
+
+ internal static string set_hotkey_error_not_custom {
+ get {
+ return ResourceManager.GetString("set_hotkey_error_not_custom", resourceCulture);
+ }
+ }
+
+ internal static string cmd_remove_hotkey {
+ get {
+ return ResourceManager.GetString("cmd_remove_hotkey", resourceCulture);
+ }
+ }
+
+ internal static string remove_hotkey_arg_key {
+ get {
+ return ResourceManager.GetString("remove_hotkey_arg_key", resourceCulture);
+ }
+ }
+
+ internal static string remove_hotkey_no_hotkeys {
+ get {
+ return ResourceManager.GetString("remove_hotkey_no_hotkeys", resourceCulture);
+ }
+ }
+
+ internal static string remove_hotkey_not_found {
+ get {
+ return ResourceManager.GetString("remove_hotkey_not_found", resourceCulture);
+ }
+ }
+
+ internal static string cmd_get_hotkeys {
+ get {
+ return ResourceManager.GetString("cmd_get_hotkeys", resourceCulture);
+ }
+ }
+
+ internal static string get_hotkeys_no_hotkeys {
+ get {
+ return ResourceManager.GetString("get_hotkeys_no_hotkeys", resourceCulture);
+ }
+ }
+
+ internal static string get_hotkeys_header {
+ get {
+ return ResourceManager.GetString("get_hotkeys_header", resourceCulture);
+ }
+ }
+
+ internal static string get_hotkeys_instruction {
+ get {
+ return ResourceManager.GetString("get_hotkeys_instruction", resourceCulture);
+ }
+ }
+
+ internal static string editor_params_timeout {
+ get {
+ return ResourceManager.GetString("editor_params_timeout", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.resx b/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.resx
new file mode 100644
index 0000000000..5d4d0a1c74
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesCLI/Properties/Resources.resx
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+
+ Error: FancyZones is not running. Start PowerToys (FancyZones) and retry.
+
+
+
+
+ Show currently active layout
+
+
+ Could not get current monitor information.
+
+
+ No layouts configured.
+
+
+ === Active FancyZones Layout(s) ===
+
+
+ No layout applied
+
+
+
+
+ List available layouts
+
+
+ === Built-in Template Layouts ({0} total) ===
+
+
+ === Custom Layouts ({0} total) ===
+
+
+ Note: Canvas layout preview is approximate.
+
+
+ Open FancyZones Editor for precise zone boundaries.
+
+
+ Use 'FancyZonesCLI.exe set-layout <UUID>' to apply a layout.
+
+
+
+
+ List monitors and FancyZones metadata
+
+
+ Failed to read monitor information. {0}
+Note: Ensure FancyZones is running to get current monitor information.
+
+
+ No monitors found.
+
+
+ === Monitors ({0} total) ===
+
+
+
+
+ Set layout by UUID or template name
+
+
+ Layout UUID or template type (e.g. focus, columns)
+
+
+ Apply to monitor N (1-based)
+
+
+ Apply to all monitors
+
+
+ Monitor index must be >= 1.
+
+
+ Cannot specify both --monitor and --all.
+
+
+ Layout '{0}' not found
+Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid', 'priority-grid')
+ For custom layouts, use the UUID from 'get-layouts'
+
+
+ Monitor {0} not found. Available monitors: 1-{1}
+
+
+ Internal error - no monitors to update.
+
+
+ Unsupported custom layout type '{0}'.
+
+
+ Internal error - no layout selected.
+
+
+ Layout '{0}' applied to all monitors.
+
+
+ Layout '{0}' applied to monitor {1}.
+
+
+ Layout '{0}' applied to monitor 1.
+
+
+
+
+ Launch FancyZones layout editor
+
+
+ Failed to request FancyZones Editor launch. {0}
+
+
+
+
+ Open FancyZones settings page
+
+
+ PowerToys.exe failed to start.
+
+
+ Failed to open FancyZones Settings. {0}
+
+
+
+
+ Assign hotkey (0-9) to a custom layout
+
+
+ Hotkey index (0-9)
+
+
+ Custom layout UUID
+
+
+ Key must be between 0 and 9.
+
+
+ Layout '{0}' is not a custom layout UUID.
+
+
+
+
+ Remove hotkey assignment
+
+
+ Hotkey index (0-9)
+
+
+ No hotkeys configured.
+
+
+ No hotkey assigned to key {0}
+
+
+
+
+ List all layout hotkeys
+
+
+ No hotkeys configured.
+
+
+ === Layout Hotkeys ===
+
+
+ Press Win + Ctrl + Alt + <number> to switch layouts:
+
+
+
+
+ Could not get current monitor information (timed out after {0}ms waiting for '{1}').
+
+
diff --git a/src/modules/fancyzones/FancyZonesCLI/Telemetry/FancyZonesCLICommandEvent.cs b/src/modules/fancyzones/FancyZonesCLI/Telemetry/FancyZonesCLICommandEvent.cs
new file mode 100644
index 0000000000..05751b5f43
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesCLI/Telemetry/FancyZonesCLICommandEvent.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Tracing;
+using Microsoft.PowerToys.Telemetry;
+using Microsoft.PowerToys.Telemetry.Events;
+
+namespace FancyZonesCLI.Telemetry
+{
+ ///
+ /// Telemetry event for FancyZones CLI command execution.
+ ///
+ [EventData]
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
+ public class FancyZonesCLICommandEvent : EventBase, IEvent
+ {
+ public FancyZonesCLICommandEvent()
+ {
+ EventName = "FancyZones_CLICommand";
+ }
+
+ ///
+ /// Gets or sets the name of the CLI command that was executed.
+ ///
+ public string CommandName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the command executed successfully.
+ ///
+ public bool Successful { get; set; }
+
+ public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
+ }
+}
diff --git a/src/modules/fancyzones/FancyZonesCLI/Utils/EditorParametersRefresh.cs b/src/modules/fancyzones/FancyZonesCLI/Utils/EditorParametersRefresh.cs
index 898d4b2f9e..44303fb50a 100644
--- a/src/modules/fancyzones/FancyZonesCLI/Utils/EditorParametersRefresh.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/Utils/EditorParametersRefresh.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Threading;
@@ -60,7 +61,7 @@ internal static class EditorParametersRefresh
var finalParams = FancyZonesDataIO.ReadEditorParameters();
if (finalParams.Monitors == null || finalParams.Monitors.Count == 0)
{
- throw new InvalidOperationException($"Could not get current monitor information (timed out after {maxWaitMilliseconds}ms waiting for '{Path.GetFileName(filePath)}').");
+ throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.editor_params_timeout, maxWaitMilliseconds, Path.GetFileName(filePath)));
}
return finalParams;