diff --git a/src/modules/fancyzones/FancyZonesCLI/FancyZonesData.cs b/src/modules/fancyzones/FancyZonesCLI/FancyZonesData.cs
new file mode 100644
index 0000000000..e5515c7537
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesCLI/FancyZonesData.cs
@@ -0,0 +1,133 @@
+// 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;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
+
+namespace FancyZonesCLI;
+
+///
+/// Provides methods to read and write FancyZones configuration data.
+///
+internal static class FancyZonesData
+{
+ ///
+ /// Try to read applied layouts configuration.
+ ///
+ public static bool TryReadAppliedLayouts(out AppliedLayouts result, out string error)
+ {
+ return TryReadJsonFile(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts, out result, out error);
+ }
+
+ ///
+ /// Read applied layouts or return null if not found.
+ ///
+ public static AppliedLayouts ReadAppliedLayouts()
+ {
+ return ReadJsonFileOrDefault(FancyZonesPaths.AppliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts);
+ }
+
+ ///
+ /// Write applied layouts configuration.
+ ///
+ public static void WriteAppliedLayouts(AppliedLayouts layouts)
+ {
+ WriteJsonFile(FancyZonesPaths.AppliedLayouts, layouts, FancyZonesJsonContext.Default.AppliedLayouts);
+ }
+
+ ///
+ /// Read custom layouts or return null if not found.
+ ///
+ public static CustomLayouts ReadCustomLayouts()
+ {
+ return ReadJsonFileOrDefault(FancyZonesPaths.CustomLayouts, FancyZonesJsonContext.Default.CustomLayouts);
+ }
+
+ ///
+ /// Read layout templates or return null if not found.
+ ///
+ public static LayoutTemplates ReadLayoutTemplates()
+ {
+ return ReadJsonFileOrDefault(FancyZonesPaths.LayoutTemplates, FancyZonesJsonContext.Default.LayoutTemplates);
+ }
+
+ ///
+ /// Read layout hotkeys or return null if not found.
+ ///
+ public static LayoutHotkeys ReadLayoutHotkeys()
+ {
+ return ReadJsonFileOrDefault(FancyZonesPaths.LayoutHotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
+ }
+
+ ///
+ /// Write layout hotkeys configuration.
+ ///
+ public static void WriteLayoutHotkeys(LayoutHotkeys hotkeys)
+ {
+ WriteJsonFile(FancyZonesPaths.LayoutHotkeys, hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys);
+ }
+
+ ///
+ /// Check if editor parameters file exists.
+ ///
+ public static bool EditorParametersExist()
+ {
+ return File.Exists(FancyZonesPaths.EditorParameters);
+ }
+
+ private static bool TryReadJsonFile(string filePath, JsonTypeInfo jsonTypeInfo, out T result, out string error)
+ where T : class
+ {
+ result = null;
+ error = null;
+
+ if (!File.Exists(filePath))
+ {
+ error = $"File not found: {Path.GetFileName(filePath)}";
+ return false;
+ }
+
+ try
+ {
+ var json = File.ReadAllText(filePath);
+ result = JsonSerializer.Deserialize(json, jsonTypeInfo);
+ if (result == null)
+ {
+ error = $"Failed to parse {Path.GetFileName(filePath)}";
+ return false;
+ }
+
+ return true;
+ }
+ catch (JsonException ex)
+ {
+ error = $"JSON parse error in {Path.GetFileName(filePath)}: {ex.Message}";
+ return false;
+ }
+ catch (IOException ex)
+ {
+ error = $"Failed to read {Path.GetFileName(filePath)}: {ex.Message}";
+ return false;
+ }
+ }
+
+ private static T ReadJsonFileOrDefault(string filePath, JsonTypeInfo jsonTypeInfo, T defaultValue = null)
+ where T : class
+ {
+ if (TryReadJsonFile(filePath, jsonTypeInfo, out var result, out _))
+ {
+ return result;
+ }
+
+ return defaultValue;
+ }
+
+ private static void WriteJsonFile(string filePath, T data, JsonTypeInfo jsonTypeInfo)
+ {
+ var json = JsonSerializer.Serialize(data, jsonTypeInfo);
+ File.WriteAllText(filePath, json);
+ }
+}
diff --git a/src/modules/fancyzones/FancyZonesCLI/FancyZonesPaths.cs b/src/modules/fancyzones/FancyZonesCLI/FancyZonesPaths.cs
new file mode 100644
index 0000000000..f04d375392
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesCLI/FancyZonesPaths.cs
@@ -0,0 +1,30 @@
+// 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;
+using System.IO;
+
+namespace FancyZonesCLI;
+
+///
+/// Provides paths to FancyZones configuration files.
+///
+internal static class FancyZonesPaths
+{
+ private static readonly string DataPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+ "Microsoft",
+ "PowerToys",
+ "FancyZones");
+
+ public static string AppliedLayouts => Path.Combine(DataPath, "applied-layouts.json");
+
+ public static string CustomLayouts => Path.Combine(DataPath, "custom-layouts.json");
+
+ public static string LayoutTemplates => Path.Combine(DataPath, "layout-templates.json");
+
+ public static string LayoutHotkeys => Path.Combine(DataPath, "layout-hotkeys.json");
+
+ public static string EditorParameters => Path.Combine(DataPath, "editor-parameters.json");
+}
diff --git a/src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs b/src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs
index 80696b7528..fecdf33dbe 100644
--- a/src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
+using System.Globalization;
using System.Text;
using System.Text.Json;
@@ -12,20 +12,21 @@ namespace FancyZonesCLI;
public static class LayoutVisualizer
{
- public static void DrawTemplateLayout(TemplateLayout template)
+ public static string DrawTemplateLayout(TemplateLayout template)
{
- Console.WriteLine(" Visual Preview:");
+ var sb = new StringBuilder();
+ sb.AppendLine(" Visual Preview:");
switch (template.Type.ToLowerInvariant())
{
case "focus":
- DrawFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3);
+ sb.Append(RenderFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "columns":
- DrawGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3);
+ sb.Append(RenderGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "rows":
- DrawGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1);
+ sb.Append(RenderGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1));
break;
case "grid":
// Grid layout: calculate rows and columns from zone count
@@ -45,28 +46,31 @@ public static class LayoutVisualizer
cols++;
}
- DrawGridLayoutWithZoneCount(rows, cols, zoneCount);
+ sb.Append(RenderGridLayoutWithZoneCount(rows, cols, zoneCount));
break;
case "priority-grid":
- DrawPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3);
+ sb.Append(RenderPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3));
break;
case "blank":
- Console.WriteLine(" (No zones)");
+ sb.AppendLine(" (No zones)");
break;
default:
- Console.WriteLine($" ({template.Type} layout)");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" ({template.Type} layout)");
break;
}
+
+ return sb.ToString();
}
- public static void DrawCustomLayout(CustomLayout layout)
+ public static string DrawCustomLayout(CustomLayout layout)
{
if (layout.Info.ValueKind == JsonValueKind.Undefined || layout.Info.ValueKind == JsonValueKind.Null)
{
- return;
+ return string.Empty;
}
- Console.WriteLine(" Visual Preview:");
+ var sb = new StringBuilder();
+ sb.AppendLine(" Visual Preview:");
if (layout.Type == "grid" &&
layout.Info.TryGetProperty("rows", out var rows) &&
@@ -78,12 +82,12 @@ public static class LayoutVisualizer
// Check if there's a cell-child-map (merged cells)
if (layout.Info.TryGetProperty("cell-child-map", out var cellMap))
{
- DrawGridLayoutWithMergedCells(r, c, cellMap);
+ sb.Append(RenderGridLayoutWithMergedCells(r, c, cellMap));
}
else
{
int height = r >= 4 ? 12 : 8;
- DrawGridLayout(r, c, 30, height);
+ sb.Append(RenderGridLayout(r, c, 30, height));
}
}
else if (layout.Type == "canvas" &&
@@ -91,60 +95,61 @@ public static class LayoutVisualizer
layout.Info.TryGetProperty("ref-width", out var refWidth) &&
layout.Info.TryGetProperty("ref-height", out var refHeight))
{
- DrawCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32());
+ sb.Append(RenderCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32()));
}
+
+ return sb.ToString();
}
- private static void DrawFocusLayout(int zoneCount = 3)
+ private static string RenderFocusLayout(int zoneCount = 3)
{
+ var sb = new StringBuilder();
+
// Focus layout: overlapping zones with cascading offset
- // Show first 2, ellipsis, and last 1 if more than 4 zones
if (zoneCount == 1)
{
- Console.WriteLine(" +-------+");
- Console.WriteLine(" | |");
- Console.WriteLine(" | |");
- Console.WriteLine(" +-------+");
+ sb.AppendLine(" +-------+");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" +-------+");
}
else if (zoneCount == 2)
{
- Console.WriteLine(" +-------+");
- Console.WriteLine(" | |");
- Console.WriteLine(" | +-------+");
- Console.WriteLine(" +-| |");
- Console.WriteLine(" | |");
- Console.WriteLine(" +-------+");
+ sb.AppendLine(" +-------+");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" | +-------+");
+ sb.AppendLine(" +-| |");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" +-------+");
}
else
{
- Console.WriteLine(" +-------+");
- Console.WriteLine(" | |");
- Console.WriteLine(" | +-------+");
- Console.WriteLine(" +-| |");
- Console.WriteLine(" | +-------+");
- Console.WriteLine(" +-| |");
-
- // Middle ellipsis
- Console.WriteLine(" ...");
- Console.WriteLine($" (total: {zoneCount} zones)");
- Console.WriteLine(" ...");
-
- // Show indication of last zone (without full indent)
- Console.WriteLine(" | +-------+");
- Console.WriteLine(" +-| |");
- Console.WriteLine(" | |");
- Console.WriteLine(" +-------+");
+ sb.AppendLine(" +-------+");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" | +-------+");
+ sb.AppendLine(" +-| |");
+ sb.AppendLine(" | +-------+");
+ sb.AppendLine(" +-| |");
+ sb.AppendLine(" ...");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" (total: {zoneCount} zones)");
+ sb.AppendLine(" ...");
+ sb.AppendLine(" | +-------+");
+ sb.AppendLine(" +-| |");
+ sb.AppendLine(" | |");
+ sb.AppendLine(" +-------+");
}
+
+ return sb.ToString();
}
- private static void DrawPriorityGridLayout(int zoneCount = 3)
+ private static string RenderPriorityGridLayout(int zoneCount = 3)
{
// Priority Grid has predefined layouts for zone counts 1-11
// Data format from GridLayoutModel._priorityData
if (zoneCount >= 1 && zoneCount <= 11)
{
int[,] cellMap = GetPriorityGridCellMap(zoneCount);
- DrawGridLayoutWithCellMap(cellMap);
+ return RenderGridLayoutWithCellMap(cellMap);
}
else
{
@@ -162,7 +167,7 @@ public static class LayoutVisualizer
cols++;
}
- DrawGridLayoutWithZoneCount(rows, cols, zoneCount);
+ return RenderGridLayoutWithZoneCount(rows, cols, zoneCount);
}
}
@@ -186,8 +191,9 @@ public static class LayoutVisualizer
};
}
- private static void DrawGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8)
+ private static string RenderGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8)
{
+ var sb = new StringBuilder();
int rows = cellMap.GetLength(0);
int cols = cellMap.GetLength(1);
@@ -197,79 +203,54 @@ public static class LayoutVisualizer
for (int r = 0; r < rows; r++)
{
// Top border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- // Check if this cell should merge with the cell above
bool mergeTop = r > 0 && cellMap[r, c] == cellMap[r - 1, c];
-
- // Check if this cell should merge with the cell to the left
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
if (mergeTop)
{
- if (mergeLeft)
- {
- Console.Write(new string(' ', cellWidth));
- }
- else
- {
- Console.Write(new string(' ', cellWidth - 1));
- Console.Write("+");
- }
+ sb.Append(mergeLeft ? new string(' ', cellWidth) : new string(' ', cellWidth - 1) + "+");
}
else
{
- if (mergeLeft)
- {
- Console.Write(new string('-', cellWidth));
- }
- else
- {
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
- }
+ sb.Append(mergeLeft ? new string('-', cellWidth) : new string('-', cellWidth - 1) + "+");
}
}
- Console.WriteLine();
+ sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
- Console.Write(" ");
+ sb.Append(" ");
for (int c = 0; c < cols; c++)
{
bool mergeLeft = c > 0 && cellMap[r, c] == cellMap[r, c - 1];
- if (mergeLeft)
- {
- Console.Write(" ");
- }
- else
- {
- Console.Write("|");
- }
-
- Console.Write(new string(' ', cellWidth - 1));
+ sb.Append(mergeLeft ? ' ' : '|');
+ sb.Append(' ', cellWidth - 1);
}
- Console.WriteLine("|");
+ sb.AppendLine("|");
}
}
// Bottom border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
+ sb.Append('-', cellWidth - 1);
+ sb.Append('+');
}
- Console.WriteLine();
+ sb.AppendLine();
+ return sb.ToString();
}
- private static void DrawGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap)
+ private static string RenderGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap)
{
+ var sb = new StringBuilder();
const int displayWidth = 39;
const int displayHeight = 12;
@@ -284,45 +265,20 @@ public static class LayoutVisualizer
}
}
- // Find unique zones and their count
- var zones = new HashSet();
- for (int r = 0; r < rows; r++)
- {
- for (int c = 0; c < cols; c++)
- {
- zones.Add(zoneMap[r, c]);
- }
- }
-
int cellHeight = displayHeight / rows;
int cellWidth = displayWidth / cols;
// Draw top border
- Console.Write(" +");
- Console.Write(new string('-', displayWidth));
- Console.WriteLine("+");
+ sb.Append(" +");
+ sb.Append('-', displayWidth);
+ sb.AppendLine("+");
// Draw rows
for (int r = 0; r < rows; r++)
{
- // For each row, find the column range of each zone
- var zoneRanges = new Dictionary();
- for (int c = 0; c < cols; c++)
- {
- int zone = zoneMap[r, c];
- if (zoneRanges.TryGetValue(zone, out var range))
- {
- zoneRanges[zone] = (range.Start, c);
- }
- else
- {
- zoneRanges[zone] = (c, c);
- }
- }
-
for (int h = 0; h < cellHeight; h++)
{
- Console.Write(" |");
+ sb.Append(" |");
for (int c = 0; c < cols; c++)
{
@@ -330,47 +286,35 @@ public static class LayoutVisualizer
int leftZone = c > 0 ? zoneMap[r, c - 1] : -1;
bool needLeftBorder = c > 0 && currentZone != leftZone;
- // Check if this zone has a top border
- bool zoneHasTopBorder = false;
- if (r > 0 && h == 0)
- {
- int topZone = zoneMap[r - 1, c];
- zoneHasTopBorder = currentZone != topZone;
- }
+ bool zoneHasTopBorder = r > 0 && h == 0 && currentZone != zoneMap[r - 1, c];
- // Draw left border if needed
if (needLeftBorder)
{
- Console.Write("|");
-
- // Fill rest of cell
- for (int w = 1; w < cellWidth; w++)
- {
- Console.Write(zoneHasTopBorder ? "-" : " ");
- }
+ sb.Append('|');
+ sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth - 1);
}
else
{
- // No left border, fill entire cell
- for (int w = 0; w < cellWidth; w++)
- {
- Console.Write(zoneHasTopBorder ? "-" : " ");
- }
+ sb.Append(zoneHasTopBorder ? '-' : ' ', cellWidth);
}
}
- Console.WriteLine("|");
+ sb.AppendLine("|");
}
}
// Draw bottom border
- Console.Write(" +");
- Console.Write(new string('-', displayWidth));
- Console.WriteLine("+");
+ sb.Append(" +");
+ sb.Append('-', displayWidth);
+ sb.AppendLine("+");
+
+ return sb.ToString();
}
- public static void DrawGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8)
+ public static string RenderGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8)
{
+ var sb = new StringBuilder();
+
// Build zone map like Editor's InitGrid
int[,] zoneMap = new int[rows, cols];
int index = 0;
@@ -392,103 +336,93 @@ public static class LayoutVisualizer
for (int r = 0; r < rows; r++)
{
// Top border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- // Check if this cell should merge with the previous one (same zone)
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
- if (mergeLeft)
+ sb.Append('-', mergeLeft ? cellWidth : cellWidth - 1);
+ if (!mergeLeft)
{
- Console.Write(new string('-', cellWidth));
- }
- else
- {
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
+ sb.Append('+');
}
}
- Console.WriteLine();
+ sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
- Console.Write(" ");
+ sb.Append(" ");
for (int c = 0; c < cols; c++)
{
- // Check if this cell should merge with the previous one
bool mergeLeft = c > 0 && zoneMap[r, c] == zoneMap[r, c - 1];
- if (mergeLeft)
- {
- Console.Write(" ");
- }
- else
- {
- Console.Write("|");
- }
-
- Console.Write(new string(' ', cellWidth - 1));
+ sb.Append(mergeLeft ? ' ' : '|');
+ sb.Append(' ', cellWidth - 1);
}
- Console.WriteLine("|");
+ sb.AppendLine("|");
}
}
// Bottom border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
+ sb.Append('-', cellWidth - 1);
+ sb.Append('+');
}
- Console.WriteLine();
+ sb.AppendLine();
+ return sb.ToString();
}
- public static void DrawGridLayout(int rows, int cols, int width = 30, int height = 8)
+ public static string RenderGridLayout(int rows, int cols, int width = 30, int height = 8)
{
+ var sb = new StringBuilder();
int cellWidth = width / cols;
int cellHeight = height / rows;
for (int r = 0; r < rows; r++)
{
// Top border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
+ sb.Append('-', cellWidth - 1);
+ sb.Append('+');
}
- Console.WriteLine();
+ sb.AppendLine();
// Cell content
for (int h = 0; h < cellHeight - 1; h++)
{
- Console.Write(" ");
+ sb.Append(" ");
for (int c = 0; c < cols; c++)
{
- Console.Write("|");
- Console.Write(new string(' ', cellWidth - 1));
+ sb.Append('|');
+ sb.Append(' ', cellWidth - 1);
}
- Console.WriteLine("|");
+ sb.AppendLine("|");
}
}
// Bottom border
- Console.Write(" +");
+ sb.Append(" +");
for (int c = 0; c < cols; c++)
{
- Console.Write(new string('-', cellWidth - 1));
- Console.Write("+");
+ sb.Append('-', cellWidth - 1);
+ sb.Append('+');
}
- Console.WriteLine();
+ sb.AppendLine();
+ return sb.ToString();
}
- private static void DrawCanvasLayout(JsonElement zones, int refWidth, int refHeight)
+ private static string RenderCanvasLayout(JsonElement zones, int refWidth, int refHeight)
{
+ var sb = new StringBuilder();
const int displayWidth = 49;
const int displayHeight = 15;
@@ -518,7 +452,6 @@ public static class LayoutVisualizer
int dw = Math.Max(3, w * displayWidth / refWidth);
int dh = Math.Max(2, h * displayHeight / refHeight);
- // Clamp to display bounds
if (dx + dw > displayWidth)
{
dw = displayWidth - dx;
@@ -531,7 +464,6 @@ public static class LayoutVisualizer
zoneList.Add((dx, dy, dw, dh, zoneId));
- // Fill the grid for this zone
for (int r = dy; r < dy + dh && r < displayHeight; r++)
{
for (int c = dx; c < dx + dw && c < displayWidth; c++)
@@ -544,82 +476,75 @@ public static class LayoutVisualizer
}
// Draw top border
- Console.Write(" +");
- Console.Write(new string('-', displayWidth));
- Console.WriteLine("+");
+ sb.Append(" +");
+ sb.Append('-', displayWidth);
+ sb.AppendLine("+");
// Draw each row
+ char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
+
for (int r = 0; r < displayHeight; r++)
{
- Console.Write(" |");
+ sb.Append(" |");
for (int c = 0; c < displayWidth; c++)
{
var zonesHere = zoneGrid[r, c];
if (zonesHere.Count == 0)
{
- Console.Write(" ");
+ sb.Append(' ');
}
else
{
- // Get the topmost zone at this position
int topZone = zonesHere[zonesHere.Count - 1];
var rect = zoneList[topZone];
- int x = rect.X;
- int y = rect.Y;
- int w = rect.Width;
- int h = rect.Height;
+ bool isTopEdge = r == rect.Y;
+ bool isBottomEdge = r == rect.Y + rect.Height - 1;
+ bool isLeftEdge = c == rect.X;
+ bool isRightEdge = c == rect.X + rect.Width - 1;
- bool isTopEdge = r == y;
- bool isBottomEdge = r == y + h - 1;
- bool isLeftEdge = c == x;
- bool isRightEdge = c == x + w - 1;
-
- // Draw borders
if ((isTopEdge || isBottomEdge) && (isLeftEdge || isRightEdge))
{
- Console.Write("+");
+ sb.Append('+');
}
else if (isTopEdge || isBottomEdge)
{
- Console.Write("-");
+ sb.Append('-');
}
else if (isLeftEdge || isRightEdge)
{
- Console.Write("|");
+ sb.Append('|');
}
else
{
- // Use shading to show different zones
- char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
- Console.Write(shades[topZone % shades.Length]);
+ sb.Append(shades[topZone % shades.Length]);
}
}
}
- Console.WriteLine("|");
+ sb.AppendLine("|");
}
// Draw bottom border
- Console.Write(" +");
- Console.Write(new string('-', displayWidth));
- Console.WriteLine("+");
+ sb.Append(" +");
+ sb.Append('-', displayWidth);
+ sb.AppendLine("+");
// Draw legend
- Console.WriteLine();
- Console.Write(" Legend: ");
- char[] legendShades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
- for (int i = 0; i < Math.Min(zoneId, legendShades.Length); i++)
+ sb.AppendLine();
+ sb.Append(" Legend: ");
+ for (int i = 0; i < Math.Min(zoneId, shades.Length); i++)
{
if (i > 0)
{
- Console.Write(", ");
+ sb.Append(", ");
}
- Console.Write($"Zone {i} = {legendShades[i]}");
+ sb.Append(CultureInfo.InvariantCulture, $"Zone {i} = {shades[i]}");
}
- Console.WriteLine();
+ sb.AppendLine();
+ return sb.ToString();
}
}
diff --git a/src/modules/fancyzones/FancyZonesCLI/Program.cs b/src/modules/fancyzones/FancyZonesCLI/Program.cs
index 499abdc1c7..d8781392f3 100644
--- a/src/modules/fancyzones/FancyZonesCLI/Program.cs
+++ b/src/modules/fancyzones/FancyZonesCLI/Program.cs
@@ -15,108 +15,96 @@ namespace FancyZonesCLI;
internal sealed class Program
{
- private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
-
private static int Main(string[] args)
{
// Initialize Windows messages
InitializeWindowMessages();
+ (int ExitCode, string Output) result;
+
if (args.Length == 0)
{
- PrintUsage();
- return 1;
+ result = (1, GetUsageText());
+ }
+ else
+ {
+ var command = args[0].ToLowerInvariant();
+
+ result = command switch
+ {
+ "open-editor" or "editor" or "e" => OpenEditor(),
+ "get-monitors" or "monitors" or "m" => GetMonitors(),
+ "get-layouts" or "layouts" or "ls" => GetLayouts(),
+ "get-active-layout" or "active" or "get-active" or "a" => GetActiveLayout(),
+ "set-layout" or "set" or "s" => args.Length >= 2 ? SetLayout(args.Skip(1).ToArray()) : (1, "Error: set-layout requires a UUID parameter"),
+ "open-settings" or "settings" => OpenSettings(),
+ "get-hotkeys" or "hotkeys" or "hk" => GetHotkeys(),
+ "set-hotkey" or "shk" => args.Length >= 3 ? SetHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), args[2]) : (1, "Error: set-hotkey requires "),
+ "remove-hotkey" or "rhk" => args.Length >= 2 ? RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture)) : (1, "Error: remove-hotkey requires "),
+ "help" or "--help" or "-h" => (0, GetUsageText()),
+ _ => (1, $"Error: Unknown command: {command}\n\n{GetUsageText()}"),
+ };
}
- var command = args[0].ToLowerInvariant();
-
- return command switch
+ // Output result
+ if (!string.IsNullOrEmpty(result.Output))
{
- "open-editor" or "editor" or "e" => OpenEditor(),
- "get-monitors" or "monitors" or "m" => GetMonitors(),
- "get-layouts" or "layouts" or "ls" => GetLayouts(),
- "get-active-layout" or "active" or "get-active" or "a" => GetActiveLayout(),
- "set-layout" or "set" or "s" => args.Length >= 2 ? SetLayout(args.Skip(1).ToArray()) : PrintErrorAndReturn("Error: set-layout requires a UUID parameter"),
- "open-settings" or "settings" => OpenSettings(),
- "get-hotkeys" or "hotkeys" or "hk" => GetHotkeys(),
- "set-hotkey" or "shk" => args.Length >= 3 ? SetHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), args[2]) : PrintErrorAndReturn("Error: set-hotkey requires "),
- "remove-hotkey" or "rhk" => args.Length >= 2 ? RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture)) : PrintErrorAndReturn("Error: remove-hotkey requires "),
- "help" or "--help" or "-h" => PrintUsageAndReturn(),
- _ => PrintUnknownCommandAndReturn(command),
- };
+ Console.WriteLine(result.Output);
+ }
+
+ return result.ExitCode;
}
- private static int PrintErrorAndReturn(string message)
+ private static string GetUsageText()
{
- Console.WriteLine(message);
- return 1;
+ return """
+ FancyZones CLI - Command line interface for FancyZones
+ ======================================================
+
+ Usage: FancyZonesCLI.exe [options]
+
+ Commands:
+ open-editor (editor, e) Launch FancyZones layout editor
+ get-monitors (monitors, m) List all monitors and their properties
+ get-layouts (layouts, ls) List all available layouts
+ get-active-layout (get-active, active, a)
+ Show currently active layout
+ set-layout (set, s) [options]
+ Set layout by UUID
+ --monitor Apply to monitor N (1-based)
+ --all Apply to all monitors
+ open-settings (settings) Open FancyZones settings page
+ get-hotkeys (hotkeys, hk) List all layout hotkeys
+ set-hotkey (shk) Assign hotkey (0-9) to CUSTOM layout
+ Note: Only custom layouts work with hotkeys
+ remove-hotkey (rhk) Remove hotkey assignment
+ help Show this help message
+
+
+ Examples:
+ FancyZonesCLI.exe e # Open editor (short)
+ FancyZonesCLI.exe m # List monitors (short)
+ FancyZonesCLI.exe ls # List layouts (short)
+ FancyZonesCLI.exe a # Get active layout (short)
+ FancyZonesCLI.exe s focus --all # Set layout (short)
+ FancyZonesCLI.exe open-editor # Open editor (long)
+ FancyZonesCLI.exe get-monitors
+ FancyZonesCLI.exe get-layouts
+ FancyZonesCLI.exe set-layout {12345678-1234-1234-1234-123456789012}
+ FancyZonesCLI.exe set-layout focus --monitor 2
+ FancyZonesCLI.exe set-layout columns --all
+ FancyZonesCLI.exe set-hotkey 3 {12345678-1234-1234-1234-123456789012}
+ """;
}
- private static int PrintUsageAndReturn()
- {
- PrintUsage();
- return 0;
- }
-
- private static int PrintUnknownCommandAndReturn(string command)
- {
- Console.WriteLine($"Error: Unknown command: {command}\n");
- PrintUsage();
- return 1;
- }
-
- private static void PrintUsage()
- {
- Console.WriteLine("FancyZones CLI - Command line interface for FancyZones");
- Console.WriteLine("======================================================");
- Console.WriteLine();
- Console.WriteLine("Usage: FancyZonesCLI.exe [options]");
- Console.WriteLine();
- Console.WriteLine("Commands:");
- Console.WriteLine(" open-editor (editor, e) Launch FancyZones layout editor");
- Console.WriteLine(" get-monitors (monitors, m) List all monitors and their properties");
- Console.WriteLine(" get-layouts (layouts, ls) List all available layouts");
- Console.WriteLine(" get-active-layout (active, a) Show currently active layout");
- Console.WriteLine(" set-layout (set, s) [options]");
- Console.WriteLine(" Set layout by UUID");
- Console.WriteLine(" --monitor Apply to monitor N (1-based)");
- Console.WriteLine(" --all Apply to all monitors");
- Console.WriteLine(" open-settings (settings) Open FancyZones settings page");
- Console.WriteLine(" get-hotkeys (hotkeys, hk) List all layout hotkeys");
- Console.WriteLine(" set-hotkey (shk) Assign hotkey (0-9) to CUSTOM layout");
- Console.WriteLine(" Note: Only custom layouts work with hotkeys");
- Console.WriteLine(" remove-hotkey (rhk) Remove hotkey assignment");
- Console.WriteLine(" help Show this help message");
- Console.WriteLine();
- Console.WriteLine();
- Console.WriteLine("Examples:");
- Console.WriteLine(" FancyZonesCLI.exe e # Open editor (short)");
- Console.WriteLine(" FancyZonesCLI.exe m # List monitors (short)");
- Console.WriteLine(" FancyZonesCLI.exe ls # List layouts (short)");
- Console.WriteLine(" FancyZonesCLI.exe a # Get active layout (short)");
- Console.WriteLine(" FancyZonesCLI.exe s focus --all # Set layout (short)");
- Console.WriteLine(" FancyZonesCLI.exe open-editor # Open editor (long)");
- Console.WriteLine(" FancyZonesCLI.exe get-monitors");
- Console.WriteLine(" FancyZonesCLI.exe get-layouts");
- Console.WriteLine(" FancyZonesCLI.exe set-layout {12345678-1234-1234-1234-123456789012}");
- Console.WriteLine(" FancyZonesCLI.exe set-layout focus --monitor 2");
- Console.WriteLine(" FancyZonesCLI.exe set-layout columns --all");
- Console.WriteLine(" FancyZonesCLI.exe set-hotkey 3 {12345678-1234-1234-1234-123456789012}");
- }
-
- private static int OpenEditor()
+ private static (int ExitCode, string Output) OpenEditor()
{
var editorExe = "PowerToys.FancyZonesEditor.exe";
// Check if editor-parameters.json exists
- var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- var editorParamsPath = Path.Combine(localAppData, "Microsoft", "PowerToys", "FancyZones", "editor-parameters.json");
-
- if (!File.Exists(editorParamsPath))
+ if (!FancyZonesData.EditorParametersExist())
{
- Console.WriteLine("Error: editor-parameters.json not found.");
- Console.WriteLine("Please launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
- return 1;
+ return (1, "Error: editor-parameters.json not found.\nPlease launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
}
// Check if editor is already running
@@ -124,8 +112,7 @@ internal sealed class Program
if (existingProcess != null)
{
NativeMethods.SetForegroundWindow(existingProcess.MainWindowHandle);
- Console.WriteLine("FancyZones Editor is already running. Brought window to foreground.");
- return 0;
+ return (0, "FancyZones Editor is already running. Brought window to foreground.");
}
// Only check same directory as CLI
@@ -140,21 +127,18 @@ internal sealed class Program
FileName = editorPath,
UseShellExecute = true,
});
- Console.WriteLine("FancyZones Editor launched successfully.");
- return 0;
+ return (0, "FancyZones Editor launched successfully.");
}
catch (Exception ex)
{
- Console.WriteLine($"Failed to launch: {ex.Message}");
- return 1;
+ return (1, $"Failed to launch: {ex.Message}");
}
}
- Console.WriteLine($"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
- return 1;
+ return (1, $"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
}
- private static int OpenSettings()
+ private static (int ExitCode, string Output) OpenSettings()
{
try
{
@@ -170,8 +154,7 @@ internal sealed class Program
if (powertoysExe == null)
{
- Console.WriteLine("Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
- return 1;
+ return (1, "Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
}
Process.Start(new ProcessStartInfo
@@ -180,232 +163,180 @@ internal sealed class Program
Arguments = "--open-settings=FancyZones",
UseShellExecute = false,
});
- Console.WriteLine("FancyZones Settings opened successfully.");
- return 0;
+ return (0, "FancyZones Settings opened successfully.");
}
catch (Exception ex)
{
- Console.WriteLine($"Error: Failed to open FancyZones Settings. {ex.Message}");
- return 1;
+ return (1, $"Error: Failed to open FancyZones Settings. {ex.Message}");
}
}
- private static int GetMonitors()
+ private static (int ExitCode, string Output) GetMonitors()
{
- var dataPath = GetFancyZonesDataPath();
- var appliedLayoutsPath = Path.Combine(dataPath, "applied-layouts.json");
-
- if (!File.Exists(appliedLayoutsPath))
+ if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
- Console.WriteLine("Error: applied-layouts.json not found.");
- return 1;
+ return (1, $"Error: {error}");
}
- try
+ if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
{
- var json = File.ReadAllText(appliedLayoutsPath);
- var appliedLayouts = JsonSerializer.Deserialize(json, FancyZonesJsonContext.Default.AppliedLayouts);
-
- if (appliedLayouts?.Layouts == null || appliedLayouts.Layouts.Count == 0)
- {
- Console.WriteLine("No monitors found.");
- return 0;
- }
-
- Console.WriteLine($"=== Monitors ({appliedLayouts.Layouts.Count} total) ===");
- Console.WriteLine();
-
- for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
- {
- var layout = appliedLayouts.Layouts[i];
- var monitorNum = i + 1;
-
- Console.WriteLine($"Monitor {monitorNum}:");
- Console.WriteLine($" Monitor: {layout.Device.Monitor}");
- Console.WriteLine($" Monitor Instance: {layout.Device.MonitorInstance}");
- Console.WriteLine($" Monitor Number: {layout.Device.MonitorNumber}");
- Console.WriteLine($" Serial Number: {layout.Device.SerialNumber}");
- Console.WriteLine($" Virtual Desktop: {layout.Device.VirtualDesktop}");
- Console.WriteLine($" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
- Console.WriteLine($" Active Layout: {layout.AppliedLayout.Type}");
- Console.WriteLine($" Zone Count: {layout.AppliedLayout.ZoneCount}");
- Console.WriteLine();
- }
-
- return 0;
+ return (0, "No monitors found.");
}
- catch (Exception ex)
+
+ var sb = new System.Text.StringBuilder();
+ sb.AppendLine(CultureInfo.InvariantCulture, $"=== Monitors ({appliedLayouts.Layouts.Count} total) ===");
+ sb.AppendLine();
+
+ for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
{
- Console.WriteLine($"Error: Failed to read monitor information. {ex.Message}");
- return 1;
+ var layout = appliedLayouts.Layouts[i];
+ var monitorNum = i + 1;
+
+ sb.AppendLine(CultureInfo.InvariantCulture, $"Monitor {monitorNum}:");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor: {layout.Device.Monitor}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor Instance: {layout.Device.MonitorInstance}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Monitor Number: {layout.Device.MonitorNumber}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Serial Number: {layout.Device.SerialNumber}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Virtual Desktop: {layout.Device.VirtualDesktop}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Active Layout: {layout.AppliedLayout.Type}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Zone Count: {layout.AppliedLayout.ZoneCount}");
+ sb.AppendLine();
}
+
+ return (0, sb.ToString().TrimEnd());
}
- private static int GetLayouts()
+ private static (int ExitCode, string Output) GetLayouts()
{
- var dataPath = GetFancyZonesDataPath();
- var templatesPath = Path.Combine(dataPath, "layout-templates.json");
- var customLayoutsPath = Path.Combine(dataPath, "custom-layouts.json");
+ var sb = new System.Text.StringBuilder();
// Print template layouts
- if (File.Exists(templatesPath))
+ var templatesJson = FancyZonesData.ReadLayoutTemplates();
+ if (templatesJson?.Templates != null)
{
- try
+ sb.AppendLine(CultureInfo.InvariantCulture, $"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
+
+ for (int i = 0; i < templatesJson.Templates.Count; i++)
{
- var templatesJson = JsonSerializer.Deserialize(File.ReadAllText(templatesPath), FancyZonesJsonContext.Default.LayoutTemplates);
- if (templatesJson?.Templates != null)
+ var template = templatesJson.Templates[i];
+ sb.AppendLine(CultureInfo.InvariantCulture, $"[T{i + 1}] {template.Type}");
+ sb.Append(CultureInfo.InvariantCulture, $" Zones: {template.ZoneCount}");
+ if (template.ShowSpacing && template.Spacing > 0)
{
- Console.WriteLine($"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
+ sb.Append(CultureInfo.InvariantCulture, $", Spacing: {template.Spacing}px");
+ }
- for (int i = 0; i < templatesJson.Templates.Count; i++)
- {
- var template = templatesJson.Templates[i];
- Console.WriteLine($"[T{i + 1}] {template.Type}");
- Console.WriteLine($" Zones: {template.ZoneCount}");
- if (template.ShowSpacing && template.Spacing > 0)
- {
- Console.WriteLine($", Spacing: {template.Spacing}px");
- }
+ sb.AppendLine();
+ sb.AppendLine();
- Console.WriteLine();
+ // Draw visual preview
+ sb.Append(LayoutVisualizer.DrawTemplateLayout(template));
- // Draw visual preview
- LayoutVisualizer.DrawTemplateLayout(template);
-
- if (i < templatesJson.Templates.Count - 1)
- {
- Console.WriteLine();
- }
- }
-
- Console.WriteLine("\n");
+ if (i < templatesJson.Templates.Count - 1)
+ {
+ sb.AppendLine();
}
}
- catch (Exception ex)
- {
- Console.WriteLine($"Error parsing templates: {ex.Message}");
- }
+
+ sb.AppendLine("\n");
}
// Print custom layouts
- if (File.Exists(customLayoutsPath))
+ var customLayouts = FancyZonesData.ReadCustomLayouts();
+ if (customLayouts?.Layouts != null)
{
- try
- {
- var customLayouts = JsonSerializer.Deserialize(File.ReadAllText(customLayoutsPath), FancyZonesJsonContext.Default.CustomLayouts);
- if (customLayouts?.Layouts != null)
- {
- Console.WriteLine($"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
+ sb.AppendLine(CultureInfo.InvariantCulture, $"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
- for (int i = 0; i < customLayouts.Layouts.Count; i++)
+ for (int i = 0; i < customLayouts.Layouts.Count; i++)
+ {
+ var layout = customLayouts.Layouts[i];
+ sb.AppendLine(CultureInfo.InvariantCulture, $"[{i + 1}] {layout.Name}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" UUID: {layout.Uuid}");
+ sb.Append(CultureInfo.InvariantCulture, $" Type: {layout.Type}");
+
+ bool isCanvasLayout = false;
+ if (layout.Info.ValueKind != JsonValueKind.Undefined && layout.Info.ValueKind != JsonValueKind.Null)
+ {
+ if (layout.Type == "grid" && layout.Info.TryGetProperty("rows", out var rows) && layout.Info.TryGetProperty("columns", out var cols))
{
- var layout = customLayouts.Layouts[i];
- Console.WriteLine($"[{i + 1}] {layout.Name}");
- Console.WriteLine($" UUID: {layout.Uuid}");
- Console.Write($" Type: {layout.Type}");
-
- bool isCanvasLayout = false;
- if (layout.Info.ValueKind != JsonValueKind.Undefined && layout.Info.ValueKind != JsonValueKind.Null)
- {
- if (layout.Type == "grid" && layout.Info.TryGetProperty("rows", out var rows) && layout.Info.TryGetProperty("columns", out var cols))
- {
- Console.Write($" ({rows.GetInt32()}x{cols.GetInt32()} grid)");
- }
- else if (layout.Type == "canvas" && layout.Info.TryGetProperty("zones", out var zones))
- {
- Console.Write($" ({zones.GetArrayLength()} zones)");
- isCanvasLayout = true;
- }
- }
-
- Console.WriteLine("\n");
-
- // Draw visual preview
- LayoutVisualizer.DrawCustomLayout(layout);
-
- // Add note for canvas layouts
- if (isCanvasLayout)
- {
- Console.WriteLine("\n Note: Canvas layout preview is approximate.");
- Console.WriteLine(" Open FancyZones Editor for precise zone boundaries.");
- }
-
- if (i < customLayouts.Layouts.Count - 1)
- {
- Console.WriteLine();
- }
+ sb.Append(CultureInfo.InvariantCulture, $" ({rows.GetInt32()}x{cols.GetInt32()} grid)");
}
+ else if (layout.Type == "canvas" && layout.Info.TryGetProperty("zones", out var zones))
+ {
+ sb.Append(CultureInfo.InvariantCulture, $" ({zones.GetArrayLength()} zones)");
+ isCanvasLayout = true;
+ }
+ }
- Console.WriteLine("\nUse 'FancyZonesCLI.exe set-layout ' to apply a layout.");
+ sb.AppendLine("\n");
+
+ // Draw visual preview
+ sb.Append(LayoutVisualizer.DrawCustomLayout(layout));
+
+ // 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.");
+ }
+
+ if (i < customLayouts.Layouts.Count - 1)
+ {
+ sb.AppendLine();
}
}
- catch (Exception ex)
- {
- Console.WriteLine($"Error parsing custom layouts: {ex.Message}");
- }
+
+ sb.AppendLine("\nUse 'FancyZonesCLI.exe set-layout ' to apply a layout.");
}
- return 0;
+ return (0, sb.ToString().TrimEnd());
}
- private static int GetActiveLayout()
+ private static (int ExitCode, string Output) GetActiveLayout()
{
- var dataPath = GetFancyZonesDataPath();
- var appliedLayoutsPath = Path.Combine(dataPath, "applied-layouts.json");
-
- if (!File.Exists(appliedLayoutsPath))
+ if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
- Console.WriteLine($"Error: Could not find applied-layouts.json");
- return 1;
+ return (1, $"Error: {error}");
}
- try
+ if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
{
- var appliedLayouts = JsonSerializer.Deserialize(File.ReadAllText(appliedLayoutsPath), FancyZonesJsonContext.Default.AppliedLayouts);
- if (appliedLayouts?.Layouts == null || appliedLayouts.Layouts.Count == 0)
+ return (0, "No active layouts found.");
+ }
+
+ var sb = new System.Text.StringBuilder();
+ sb.AppendLine("\n=== Active FancyZones Layout(s) ===\n");
+
+ for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
+ {
+ var layout = appliedLayouts.Layouts[i];
+ sb.AppendLine(CultureInfo.InvariantCulture, $"Monitor {i + 1}:");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Name: {layout.AppliedLayout.Type}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" UUID: {layout.AppliedLayout.Uuid}");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Type: {layout.AppliedLayout.Type} ({layout.AppliedLayout.ZoneCount} zones)");
+
+ if (layout.AppliedLayout.ShowSpacing)
{
- Console.WriteLine("No active layouts found.");
- return 0;
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Spacing: {layout.AppliedLayout.Spacing}px");
}
- Console.WriteLine("\n=== Active FancyZones Layout(s) ===\n");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
- for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
+ if (i < appliedLayouts.Layouts.Count - 1)
{
- var layout = appliedLayouts.Layouts[i];
- Console.WriteLine($"Monitor {i + 1}:");
- Console.WriteLine($" Name: {layout.AppliedLayout.Type}");
- Console.WriteLine($" UUID: {layout.AppliedLayout.Uuid}");
- Console.WriteLine($" Type: {layout.AppliedLayout.Type} ({layout.AppliedLayout.ZoneCount} zones)");
-
- if (layout.AppliedLayout.ShowSpacing)
- {
- Console.WriteLine($" Spacing: {layout.AppliedLayout.Spacing}px");
- }
-
- Console.WriteLine($" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
-
- if (i < appliedLayouts.Layouts.Count - 1)
- {
- Console.WriteLine();
- }
+ sb.AppendLine();
}
+ }
- return 0;
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error: {ex.Message}");
- return 1;
- }
+ return (0, sb.ToString().TrimEnd());
}
- private static int SetLayout(string[] args)
+ private static (int ExitCode, string Output) SetLayout(string[] args)
{
if (args.Length == 0)
{
- Console.WriteLine("Error: set-layout requires a UUID parameter");
- return 1;
+ return (1, "Error: set-layout requires a UUID parameter");
}
string uuid = args[0];
@@ -424,8 +355,7 @@ internal sealed class Program
}
else
{
- Console.WriteLine($"Error: Invalid monitor number: {args[i + 1]}");
- return 1;
+ return (1, $"Error: Invalid monitor number: {args[i + 1]}");
}
}
else if (args[i] == "--all")
@@ -436,299 +366,182 @@ internal sealed class Program
if (targetMonitor.HasValue && applyToAll)
{
- Console.WriteLine("Error: Cannot specify both --monitor and --all");
- return 1;
+ return (1, "Error: Cannot specify both --monitor and --all");
}
- var dataPath = GetFancyZonesDataPath();
- var appliedLayoutsPath = Path.Combine(dataPath, "applied-layouts.json");
- var customLayoutsPath = Path.Combine(dataPath, "custom-layouts.json");
- var templatesPath = Path.Combine(dataPath, "layout-templates.json");
+ // Try to find layout in custom layouts first (by UUID)
+ var customLayouts = FancyZonesData.ReadCustomLayouts();
+ var targetCustomLayout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(uuid, StringComparison.OrdinalIgnoreCase));
- if (!File.Exists(appliedLayoutsPath))
+ // If not found in custom layouts, try template layouts (by type name)
+ TemplateLayout targetTemplate = null;
+ if (targetCustomLayout == null)
{
- Console.WriteLine("Error: applied-layouts.json not found");
- return 1;
+ var templates = FancyZonesData.ReadLayoutTemplates();
+ targetTemplate = templates?.Templates?.FirstOrDefault(t => t.Type.Equals(uuid, StringComparison.OrdinalIgnoreCase));
}
- try
+ if (targetCustomLayout == null && targetTemplate == null)
{
- // Try to find layout in custom layouts first (by UUID)
- CustomLayout targetCustomLayout = null;
- TemplateLayout targetTemplate = null;
-
- if (File.Exists(customLayoutsPath))
- {
- var customLayouts = JsonSerializer.Deserialize(File.ReadAllText(customLayoutsPath), FancyZonesJsonContext.Default.CustomLayouts);
- targetCustomLayout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(uuid, StringComparison.OrdinalIgnoreCase));
- }
-
- // If not found in custom layouts, try template layouts (by type name or UUID)
- if (targetCustomLayout == null && File.Exists(templatesPath))
- {
- var templates = JsonSerializer.Deserialize(File.ReadAllText(templatesPath), FancyZonesJsonContext.Default.LayoutTemplates);
-
- // Try matching by type name (case-insensitive)
- targetTemplate = templates?.Templates?.FirstOrDefault(t => t.Type.Equals(uuid, StringComparison.OrdinalIgnoreCase));
- }
-
- if (targetCustomLayout == null && targetTemplate == null)
- {
- Console.WriteLine($"Error: Layout '{uuid}' not found");
- Console.WriteLine("Tip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid', 'priority-grid')");
- Console.WriteLine(" For custom layouts, use the UUID from 'get-layouts'");
- return 1;
- }
-
- // Read current applied layouts
- var appliedLayouts = JsonSerializer.Deserialize(File.ReadAllText(appliedLayoutsPath), FancyZonesJsonContext.Default.AppliedLayouts);
- if (appliedLayouts?.Layouts == null || appliedLayouts.Layouts.Count == 0)
- {
- Console.WriteLine("Error: No monitors configured");
- return 1;
- }
-
- // Determine which monitors to update
- List monitorsToUpdate = new List();
- if (applyToAll)
- {
- for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
- {
- monitorsToUpdate.Add(i);
- }
- }
- else if (targetMonitor.HasValue)
- {
- int monitorIndex = targetMonitor.Value - 1; // Convert to 0-based
- if (monitorIndex < 0 || monitorIndex >= appliedLayouts.Layouts.Count)
- {
- Console.WriteLine($"Error: Monitor {targetMonitor.Value} not found. Available monitors: 1-{appliedLayouts.Layouts.Count}");
- return 1;
- }
-
- monitorsToUpdate.Add(monitorIndex);
- }
- else
- {
- // Default: first monitor
- monitorsToUpdate.Add(0);
- }
-
- // Update selected monitors
- foreach (int monitorIndex in monitorsToUpdate)
- {
- if (targetCustomLayout != null)
- {
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = targetCustomLayout.Uuid;
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetCustomLayout.Type;
- }
- else if (targetTemplate != null)
- {
- // For templates, use all-zeros UUID and the template type
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = "{00000000-0000-0000-0000-000000000000}";
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetTemplate.Type;
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.ZoneCount = targetTemplate.ZoneCount;
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.ShowSpacing = targetTemplate.ShowSpacing;
- appliedLayouts.Layouts[monitorIndex].AppliedLayout.Spacing = targetTemplate.Spacing;
- }
- }
-
- // Write back to file
- File.WriteAllText(appliedLayoutsPath, JsonSerializer.Serialize(appliedLayouts, FancyZonesJsonContext.Default.AppliedLayouts));
-
- // Notify FancyZones to reload
- NotifyFancyZones(wmPrivAppliedLayoutsFileUpdate);
-
- string layoutName = targetCustomLayout?.Name ?? targetTemplate?.Type ?? uuid;
- if (applyToAll)
- {
- Console.WriteLine($"Layout '{layoutName}' applied to all {monitorsToUpdate.Count} monitors");
- }
- else if (targetMonitor.HasValue)
- {
- Console.WriteLine($"Layout '{layoutName}' applied to monitor {targetMonitor.Value}");
- }
- else
- {
- Console.WriteLine($"Layout '{layoutName}' applied to monitor 1");
- }
-
- return 0;
+ return (1, $"Error: Layout '{uuid}' not found\nTip: For templates, use the type name (e.g., 'focus', 'columns', 'rows', 'grid', 'priority-grid')\n For custom layouts, use the UUID from 'get-layouts'");
}
- catch (Exception ex)
+
+ // Read current applied layouts
+ if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
{
- Console.WriteLine($"Error: {ex.Message}");
- return 1;
+ return (1, $"Error: {error}");
+ }
+
+ if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
+ {
+ return (1, "Error: No monitors configured");
+ }
+
+ // Determine which monitors to update
+ List monitorsToUpdate = new List();
+ if (applyToAll)
+ {
+ for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
+ {
+ monitorsToUpdate.Add(i);
+ }
+ }
+ else if (targetMonitor.HasValue)
+ {
+ int monitorIndex = targetMonitor.Value - 1; // Convert to 0-based
+ if (monitorIndex < 0 || monitorIndex >= appliedLayouts.Layouts.Count)
+ {
+ return (1, $"Error: Monitor {targetMonitor.Value} not found. Available monitors: 1-{appliedLayouts.Layouts.Count}");
+ }
+
+ monitorsToUpdate.Add(monitorIndex);
+ }
+ else
+ {
+ // Default: first monitor
+ monitorsToUpdate.Add(0);
+ }
+
+ // Update selected monitors
+ foreach (int monitorIndex in monitorsToUpdate)
+ {
+ if (targetCustomLayout != null)
+ {
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = targetCustomLayout.Uuid;
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetCustomLayout.Type;
+ }
+ else if (targetTemplate != null)
+ {
+ // For templates, use all-zeros UUID and the template type
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.Uuid = "{00000000-0000-0000-0000-000000000000}";
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.Type = targetTemplate.Type;
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.ZoneCount = targetTemplate.ZoneCount;
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.ShowSpacing = targetTemplate.ShowSpacing;
+ appliedLayouts.Layouts[monitorIndex].AppliedLayout.Spacing = targetTemplate.Spacing;
+ }
+ }
+
+ // Write back to file
+ FancyZonesData.WriteAppliedLayouts(appliedLayouts);
+
+ // Notify FancyZones to reload
+ NotifyFancyZones(wmPrivAppliedLayoutsFileUpdate);
+
+ string layoutName = targetCustomLayout?.Name ?? targetTemplate?.Type ?? uuid;
+ if (applyToAll)
+ {
+ return (0, $"Layout '{layoutName}' applied to all {monitorsToUpdate.Count} monitors");
+ }
+ else if (targetMonitor.HasValue)
+ {
+ return (0, $"Layout '{layoutName}' applied to monitor {targetMonitor.Value}");
+ }
+ else
+ {
+ return (0, $"Layout '{layoutName}' applied to monitor 1");
}
}
- private static int GetHotkeys()
+ private static (int ExitCode, string Output) GetHotkeys()
{
- var dataPath = GetFancyZonesDataPath();
- var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
-
- if (!File.Exists(hotkeysPath))
+ var hotkeys = FancyZonesData.ReadLayoutHotkeys();
+ if (hotkeys?.Hotkeys == null || hotkeys.Hotkeys.Count == 0)
{
- Console.WriteLine("No hotkeys configured.");
- return 0;
+ return (0, "No hotkeys configured.");
}
- try
+ var sb = new System.Text.StringBuilder();
+ sb.AppendLine("=== Layout Hotkeys ===\n");
+ sb.AppendLine("Press Win + Ctrl + Alt + to switch layouts:\n");
+
+ foreach (var hotkey in hotkeys.Hotkeys.OrderBy(h => h.Key))
{
- var hotkeys = JsonSerializer.Deserialize(File.ReadAllText(hotkeysPath), FancyZonesJsonContext.Default.LayoutHotkeys);
- if (hotkeys?.Hotkeys == null || hotkeys.Hotkeys.Count == 0)
- {
- Console.WriteLine("No hotkeys configured.");
- return 0;
- }
-
- Console.WriteLine("=== Layout Hotkeys ===\n");
- Console.WriteLine("Press Win + Ctrl + Alt + to switch layouts:\n");
-
- foreach (var hotkey in hotkeys.Hotkeys.OrderBy(h => h.Key))
- {
- Console.WriteLine($" [{hotkey.Key}] → {hotkey.LayoutId}");
- }
-
- return 0;
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error: {ex.Message}");
- return 1;
+ sb.AppendLine(CultureInfo.InvariantCulture, $" [{hotkey.Key}] → {hotkey.LayoutId}");
}
+
+ return (0, sb.ToString().TrimEnd());
}
- private static int SetHotkey(int key, string layoutUuid)
+ private static (int ExitCode, string Output) SetHotkey(int key, string layoutUuid)
{
if (key < 0 || key > 9)
{
- Console.WriteLine("Error: Key must be between 0 and 9");
- return 1;
+ return (1, "Error: Key must be between 0 and 9");
}
// Check if this is a custom layout UUID
- var dataPath = GetFancyZonesDataPath();
- var customLayoutsPath = Path.Combine(dataPath, "custom-layouts.json");
- bool isCustomLayout = false;
- string layoutName = layoutUuid;
+ var customLayouts = FancyZonesData.ReadCustomLayouts();
+ var matchedLayout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(layoutUuid, StringComparison.OrdinalIgnoreCase));
+ bool isCustomLayout = matchedLayout != null;
+ string layoutName = matchedLayout?.Name ?? layoutUuid;
- if (File.Exists(customLayoutsPath))
+ var hotkeys = FancyZonesData.ReadLayoutHotkeys() ?? new LayoutHotkeys();
+
+ hotkeys.Hotkeys ??= new List();
+
+ // Remove existing hotkey for this key
+ hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
+
+ // Add new hotkey
+ hotkeys.Hotkeys.Add(new LayoutHotkey { Key = key, LayoutId = layoutUuid });
+
+ // Save
+ File.WriteAllText(FancyZonesPaths.LayoutHotkeys, JsonSerializer.Serialize(hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys));
+
+ // Notify FancyZones
+ NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
+
+ if (isCustomLayout)
{
- try
- {
- var customLayoutsJson = File.ReadAllText(customLayoutsPath);
- var customLayouts = JsonSerializer.Deserialize(customLayoutsJson, FancyZonesJsonContext.Default.CustomLayouts);
- var layout = customLayouts?.Layouts?.FirstOrDefault(l => l.Uuid.Equals(layoutUuid, StringComparison.OrdinalIgnoreCase));
- if (layout != null)
- {
- isCustomLayout = true;
- layoutName = layout.Name;
- }
- }
- catch
- {
- // Ignore parse errors
- }
+ return (0, $"✓ Hotkey {key} assigned to custom layout '{layoutName}'\n Press Win + Ctrl + Alt + {key} to switch to this layout");
}
-
- var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
-
- try
+ else
{
- LayoutHotkeys hotkeys;
- if (File.Exists(hotkeysPath))
- {
- hotkeys = JsonSerializer.Deserialize(File.ReadAllText(hotkeysPath), FancyZonesJsonContext.Default.LayoutHotkeys) ?? new LayoutHotkeys();
- }
- else
- {
- hotkeys = new LayoutHotkeys();
- }
-
- hotkeys.Hotkeys ??= new List();
-
- // Remove existing hotkey for this key
- hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
-
- // Add new hotkey
- hotkeys.Hotkeys.Add(new LayoutHotkey { Key = key, LayoutId = layoutUuid });
-
- // Save
- File.WriteAllText(hotkeysPath, JsonSerializer.Serialize(hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys));
-
- // Notify FancyZones
- NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
-
- if (isCustomLayout)
- {
- Console.WriteLine($"✓ Hotkey {key} assigned to custom layout '{layoutName}'");
- Console.WriteLine($" Press Win + Ctrl + Alt + {key} to switch to this layout");
- }
- else
- {
- Console.WriteLine($"⚠ Warning: Hotkey {key} assigned to '{layoutUuid}'");
- Console.WriteLine($" Note: FancyZones hotkeys only work with CUSTOM layouts.");
- Console.WriteLine($" Template layouts (focus, columns, rows, etc.) cannot be used with hotkeys.");
- Console.WriteLine($" Create a custom layout in the FancyZones Editor to use this hotkey.");
- }
-
- return 0;
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Error: {ex.Message}");
- return 1;
+ return (0, $"⚠ Warning: Hotkey {key} assigned to '{layoutUuid}'\n Note: FancyZones hotkeys only work with CUSTOM layouts.\n Template layouts (focus, columns, rows, etc.) cannot be used with hotkeys.\n Create a custom layout in the FancyZones Editor to use this hotkey.");
}
}
- private static int RemoveHotkey(int key)
+ private static (int ExitCode, string Output) RemoveHotkey(int key)
{
- var dataPath = GetFancyZonesDataPath();
- var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
-
- if (!File.Exists(hotkeysPath))
+ var hotkeys = FancyZonesData.ReadLayoutHotkeys();
+ if (hotkeys?.Hotkeys == null)
{
- Console.WriteLine($"No hotkey assigned to key {key}");
- return 0;
+ return (0, $"No hotkey assigned to key {key}");
}
- try
+ var removed = hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
+ if (removed == 0)
{
- var hotkeys = JsonSerializer.Deserialize(File.ReadAllText(hotkeysPath), FancyZonesJsonContext.Default.LayoutHotkeys);
- if (hotkeys?.Hotkeys == null)
- {
- Console.WriteLine($"No hotkey assigned to key {key}");
- return 0;
- }
-
- var removed = hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
- if (removed == 0)
- {
- Console.WriteLine($"No hotkey assigned to key {key}");
- return 0;
- }
-
- // Save
- File.WriteAllText(hotkeysPath, JsonSerializer.Serialize(hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys));
-
- // Notify FancyZones
- NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
-
- Console.WriteLine($"Hotkey {key} removed");
- return 0;
+ return (0, $"No hotkey assigned to key {key}");
}
- catch (Exception ex)
- {
- Console.WriteLine($"Error: {ex.Message}");
- return 1;
- }
- }
- private static string GetFancyZonesDataPath()
- {
- var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
- return Path.Combine(localAppData, "Microsoft", "PowerToys", "FancyZones");
+ // Save
+ FancyZonesData.WriteLayoutHotkeys(hotkeys);
+
+ // Notify FancyZones
+ NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
+
+ return (0, $"Hotkey {key} removed");
}
// Windows Messages for notifying FancyZones