mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
add fancyzones cli
This commit is contained in:
@@ -60,6 +60,8 @@
|
||||
"PowerToys.FancyZonesEditorCommon.dll",
|
||||
"PowerToys.FancyZonesModuleInterface.dll",
|
||||
"PowerToys.FancyZones.exe",
|
||||
"FancyZonesCLI.exe",
|
||||
"FancyZonesCLI.dll",
|
||||
|
||||
"PowerToys.GcodePreviewHandler.dll",
|
||||
"PowerToys.GcodePreviewHandler.exe",
|
||||
|
||||
@@ -55,6 +55,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PackageIdentity", "src\Pack
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesEditor", "src\modules\fancyzones\editor\FancyZonesEditor\FancyZonesEditor.csproj", "{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FancyZonesCLI", "src\modules\fancyzones\FancyZonesCLI\FancyZonesCLI.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "powerrename", "powerrename", "{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRenameExt", "src\modules\powerrename\dll\PowerRenameExt.vcxproj", "{B25AC7A5-FB9F-4789-B392-D5C85E948670}"
|
||||
@@ -894,6 +896,14 @@ Global
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.ActiveCfg = Release|x64
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481}.Release|x64.Build.0 = Release|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
|
||||
{B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{B25AC7A5-FB9F-4789-B392-D5C85E948670}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -3067,6 +3077,7 @@ Global
|
||||
{9C6A7905-72D4-4BF5-B256-ABFDAEF68AE9} = {264B412F-DB8B-4CF8-A74B-96998B183045}
|
||||
{1A066C63-64B3-45F8-92FE-664E1CCE8077} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{5CCC8468-DEC8-4D36-99D4-5C891BEBD481} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
|
||||
{89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{B25AC7A5-FB9F-4789-B392-D5C85E948670} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
{51920F1F-C28C-4ADF-8660-4238766796C2} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
|
||||
26
src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj
Normal file
26
src/modules/fancyzones/FancyZonesCLI/FancyZonesCLI.csproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.FancyZonesCLI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys FancyZones Command Line Interface</AssemblyDescription>
|
||||
<Description>PowerToys FancyZones CLI</Description>
|
||||
|
||||
<OutputType>Exe</OutputType>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<PublishAot>true</PublishAot>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)</OutputPath>
|
||||
<AssemblyName>FancyZonesCLI</AssemblyName>
|
||||
<NoWarn>$(NoWarn);SA1500;SA1402;CA1852</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
625
src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs
Normal file
625
src/modules/fancyzones/FancyZonesCLI/LayoutVisualizer.cs
Normal file
@@ -0,0 +1,625 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FancyZonesCLI;
|
||||
|
||||
public static class LayoutVisualizer
|
||||
{
|
||||
public static void DrawTemplateLayout(TemplateLayout template)
|
||||
{
|
||||
Console.WriteLine(" Visual Preview:");
|
||||
|
||||
switch (template.Type.ToLowerInvariant())
|
||||
{
|
||||
case "focus":
|
||||
DrawFocusLayout(template.ZoneCount > 0 ? template.ZoneCount : 3);
|
||||
break;
|
||||
case "columns":
|
||||
DrawGridLayout(1, template.ZoneCount > 0 ? template.ZoneCount : 3);
|
||||
break;
|
||||
case "rows":
|
||||
DrawGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3, 1);
|
||||
break;
|
||||
case "grid":
|
||||
// Grid layout: calculate rows and columns from zone count
|
||||
// Algorithm from GridLayoutModel.InitGrid() - tries to make it close to square
|
||||
// with cols >= rows preference
|
||||
int zoneCount = template.ZoneCount > 0 ? template.ZoneCount : 3;
|
||||
int rows = 1;
|
||||
while (zoneCount / rows >= rows)
|
||||
{
|
||||
rows++;
|
||||
}
|
||||
|
||||
rows--;
|
||||
int cols = zoneCount / rows;
|
||||
if (zoneCount % rows != 0)
|
||||
{
|
||||
cols++;
|
||||
}
|
||||
|
||||
DrawGridLayoutWithZoneCount(rows, cols, zoneCount);
|
||||
break;
|
||||
case "priority-grid":
|
||||
DrawPriorityGridLayout(template.ZoneCount > 0 ? template.ZoneCount : 3);
|
||||
break;
|
||||
case "blank":
|
||||
Console.WriteLine(" (No zones)");
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine($" ({template.Type} layout)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawCustomLayout(CustomLayout layout)
|
||||
{
|
||||
if (layout.Info.ValueKind == JsonValueKind.Undefined || layout.Info.ValueKind == JsonValueKind.Null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine(" Visual Preview:");
|
||||
|
||||
if (layout.Type == "grid" &&
|
||||
layout.Info.TryGetProperty("rows", out var rows) &&
|
||||
layout.Info.TryGetProperty("columns", out var cols))
|
||||
{
|
||||
int r = rows.GetInt32();
|
||||
int c = cols.GetInt32();
|
||||
|
||||
// Check if there's a cell-child-map (merged cells)
|
||||
if (layout.Info.TryGetProperty("cell-child-map", out var cellMap))
|
||||
{
|
||||
DrawGridLayoutWithMergedCells(r, c, cellMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
int height = r >= 4 ? 12 : 8;
|
||||
DrawGridLayout(r, c, 30, height);
|
||||
}
|
||||
}
|
||||
else if (layout.Type == "canvas" &&
|
||||
layout.Info.TryGetProperty("zones", out var zones) &&
|
||||
layout.Info.TryGetProperty("ref-width", out var refWidth) &&
|
||||
layout.Info.TryGetProperty("ref-height", out var refHeight))
|
||||
{
|
||||
DrawCanvasLayout(zones, refWidth.GetInt32(), refHeight.GetInt32());
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawFocusLayout(int zoneCount = 3)
|
||||
{
|
||||
// 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(" +-------+");
|
||||
}
|
||||
else if (zoneCount == 2)
|
||||
{
|
||||
Console.WriteLine(" +-------+");
|
||||
Console.WriteLine(" | |");
|
||||
Console.WriteLine(" | +-------+");
|
||||
Console.WriteLine(" +-| |");
|
||||
Console.WriteLine(" | |");
|
||||
Console.WriteLine(" +-------+");
|
||||
}
|
||||
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(" +-------+");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawPriorityGridLayout(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);
|
||||
}
|
||||
else
|
||||
{
|
||||
// > 11 zones: fallback to grid layout
|
||||
int rows = 1;
|
||||
while (zoneCount / rows >= rows)
|
||||
{
|
||||
rows++;
|
||||
}
|
||||
|
||||
rows--;
|
||||
int cols = zoneCount / rows;
|
||||
if (zoneCount % rows != 0)
|
||||
{
|
||||
cols++;
|
||||
}
|
||||
|
||||
DrawGridLayoutWithZoneCount(rows, cols, zoneCount);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[,] GetPriorityGridCellMap(int zoneCount)
|
||||
{
|
||||
// Parsed from Editor's _priorityData byte arrays
|
||||
return zoneCount switch
|
||||
{
|
||||
1 => new int[,] { { 0 } },
|
||||
2 => new int[,] { { 0, 1 } },
|
||||
3 => new int[,] { { 0, 1, 2 } },
|
||||
4 => new int[,] { { 0, 1, 2 }, { 0, 1, 3 } },
|
||||
5 => new int[,] { { 0, 1, 2 }, { 3, 1, 4 } },
|
||||
6 => new int[,] { { 0, 1, 2 }, { 0, 1, 3 }, { 4, 1, 5 } },
|
||||
7 => new int[,] { { 0, 1, 2 }, { 3, 1, 4 }, { 5, 1, 6 } },
|
||||
8 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 2, 7 } },
|
||||
9 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 2, 5 }, { 6, 1, 7, 8 } },
|
||||
10 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 1, 8, 9 } },
|
||||
11 => new int[,] { { 0, 1, 2, 3 }, { 4, 1, 5, 6 }, { 7, 8, 9, 10 } },
|
||||
_ => new int[,] { { 0 } },
|
||||
};
|
||||
}
|
||||
|
||||
private static void DrawGridLayoutWithCellMap(int[,] cellMap, int width = 30, int height = 8)
|
||||
{
|
||||
int rows = cellMap.GetLength(0);
|
||||
int cols = cellMap.GetLength(1);
|
||||
|
||||
int cellWidth = width / cols;
|
||||
int cellHeight = height / rows;
|
||||
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
// Top border
|
||||
Console.Write(" +");
|
||||
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("+");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mergeLeft)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Cell content
|
||||
for (int h = 0; h < cellHeight - 1; h++)
|
||||
{
|
||||
Console.Write(" ");
|
||||
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));
|
||||
}
|
||||
|
||||
Console.WriteLine("|");
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
Console.Write(" +");
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void DrawGridLayoutWithMergedCells(int rows, int cols, JsonElement cellMap)
|
||||
{
|
||||
const int displayWidth = 39;
|
||||
const int displayHeight = 12;
|
||||
|
||||
// Build zone map from cell-child-map
|
||||
int[,] zoneMap = new int[rows, cols];
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
var rowArray = cellMap[r];
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
zoneMap[r, c] = rowArray[c].GetInt32();
|
||||
}
|
||||
}
|
||||
|
||||
// Find unique zones and their count
|
||||
var zones = new HashSet<int>();
|
||||
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("+");
|
||||
|
||||
// Draw rows
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
// For each row, find the column range of each zone
|
||||
var zoneRanges = new Dictionary<int, (int Start, int End)>();
|
||||
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(" |");
|
||||
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
int currentZone = zoneMap[r, c];
|
||||
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;
|
||||
}
|
||||
|
||||
// Draw left border if needed
|
||||
if (needLeftBorder)
|
||||
{
|
||||
Console.Write("|");
|
||||
|
||||
// Fill rest of cell
|
||||
for (int w = 1; w < cellWidth; w++)
|
||||
{
|
||||
Console.Write(zoneHasTopBorder ? "-" : " ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No left border, fill entire cell
|
||||
for (int w = 0; w < cellWidth; w++)
|
||||
{
|
||||
Console.Write(zoneHasTopBorder ? "-" : " ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("|");
|
||||
}
|
||||
}
|
||||
|
||||
// Draw bottom border
|
||||
Console.Write(" +");
|
||||
Console.Write(new string('-', displayWidth));
|
||||
Console.WriteLine("+");
|
||||
}
|
||||
|
||||
public static void DrawGridLayoutWithZoneCount(int rows, int cols, int zoneCount, int width = 30, int height = 8)
|
||||
{
|
||||
// Build zone map like Editor's InitGrid
|
||||
int[,] zoneMap = new int[rows, cols];
|
||||
int index = 0;
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
zoneMap[r, c] = index++;
|
||||
if (index == zoneCount)
|
||||
{
|
||||
index--; // Remaining cells use the last zone index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cellWidth = width / cols;
|
||||
int cellHeight = height / rows;
|
||||
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
// Top border
|
||||
Console.Write(" +");
|
||||
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)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth));
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Cell content
|
||||
for (int h = 0; h < cellHeight - 1; h++)
|
||||
{
|
||||
Console.Write(" ");
|
||||
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));
|
||||
}
|
||||
|
||||
Console.WriteLine("|");
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
Console.Write(" +");
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
public static void DrawGridLayout(int rows, int cols, int width = 30, int height = 8)
|
||||
{
|
||||
int cellWidth = width / cols;
|
||||
int cellHeight = height / rows;
|
||||
|
||||
for (int r = 0; r < rows; r++)
|
||||
{
|
||||
// Top border
|
||||
Console.Write(" +");
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Cell content
|
||||
for (int h = 0; h < cellHeight - 1; h++)
|
||||
{
|
||||
Console.Write(" ");
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
Console.Write("|");
|
||||
Console.Write(new string(' ', cellWidth - 1));
|
||||
}
|
||||
|
||||
Console.WriteLine("|");
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom border
|
||||
Console.Write(" +");
|
||||
for (int c = 0; c < cols; c++)
|
||||
{
|
||||
Console.Write(new string('-', cellWidth - 1));
|
||||
Console.Write("+");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static void DrawCanvasLayout(JsonElement zones, int refWidth, int refHeight)
|
||||
{
|
||||
const int displayWidth = 49;
|
||||
const int displayHeight = 15;
|
||||
|
||||
// Create a 2D array to track which zones occupy each position
|
||||
var zoneGrid = new List<int>[displayHeight, displayWidth];
|
||||
for (int i = 0; i < displayHeight; i++)
|
||||
{
|
||||
for (int j = 0; j < displayWidth; j++)
|
||||
{
|
||||
zoneGrid[i, j] = new List<int>();
|
||||
}
|
||||
}
|
||||
|
||||
// Map each zone to the grid
|
||||
int zoneId = 0;
|
||||
var zoneList = new List<(int X, int Y, int Width, int Height, int Id)>();
|
||||
|
||||
foreach (var zone in zones.EnumerateArray())
|
||||
{
|
||||
int x = zone.GetProperty("X").GetInt32();
|
||||
int y = zone.GetProperty("Y").GetInt32();
|
||||
int w = zone.GetProperty("width").GetInt32();
|
||||
int h = zone.GetProperty("height").GetInt32();
|
||||
|
||||
int dx = Math.Max(0, Math.Min(displayWidth - 1, x * displayWidth / refWidth));
|
||||
int dy = Math.Max(0, Math.Min(displayHeight - 1, y * displayHeight / refHeight));
|
||||
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;
|
||||
}
|
||||
|
||||
if (dy + dh > displayHeight)
|
||||
{
|
||||
dh = displayHeight - dy;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
zoneGrid[r, c].Add(zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
zoneId++;
|
||||
}
|
||||
|
||||
// Draw top border
|
||||
Console.Write(" +");
|
||||
Console.Write(new string('-', displayWidth));
|
||||
Console.WriteLine("+");
|
||||
|
||||
// Draw each row
|
||||
for (int r = 0; r < displayHeight; r++)
|
||||
{
|
||||
Console.Write(" |");
|
||||
for (int c = 0; c < displayWidth; c++)
|
||||
{
|
||||
var zonesHere = zoneGrid[r, c];
|
||||
|
||||
if (zonesHere.Count == 0)
|
||||
{
|
||||
Console.Write(" ");
|
||||
}
|
||||
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 == 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("+");
|
||||
}
|
||||
else if (isTopEdge || isBottomEdge)
|
||||
{
|
||||
Console.Write("-");
|
||||
}
|
||||
else if (isLeftEdge || isRightEdge)
|
||||
{
|
||||
Console.Write("|");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use shading to show different zones
|
||||
char[] shades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
|
||||
Console.Write(shades[topZone % shades.Length]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("|");
|
||||
}
|
||||
|
||||
// Draw bottom border
|
||||
Console.Write(" +");
|
||||
Console.Write(new string('-', displayWidth));
|
||||
Console.WriteLine("+");
|
||||
|
||||
// Draw legend
|
||||
Console.WriteLine();
|
||||
Console.Write(" Legend: ");
|
||||
char[] legendShades = { '.', ':', '░', '▒', '▓', '█', '◆', '●', '■', '▪' };
|
||||
for (int i = 0; i < Math.Min(zoneId, legendShades.Length); i++)
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
Console.Write(", ");
|
||||
}
|
||||
|
||||
Console.Write($"Zone {i} = {legendShades[i]}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
137
src/modules/fancyzones/FancyZonesCLI/Models.cs
Normal file
137
src/modules/fancyzones/FancyZonesCLI/Models.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace FancyZonesCLI;
|
||||
|
||||
// JSON Source Generator for AOT compatibility
|
||||
[JsonSerializable(typeof(LayoutTemplates))]
|
||||
[JsonSerializable(typeof(CustomLayouts))]
|
||||
[JsonSerializable(typeof(AppliedLayouts))]
|
||||
[JsonSerializable(typeof(LayoutHotkeys))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
internal partial class FancyZonesJsonContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
// Layout Templates
|
||||
public sealed class LayoutTemplates
|
||||
{
|
||||
[JsonPropertyName("layout-templates")]
|
||||
public List<TemplateLayout> Templates { get; set; }
|
||||
}
|
||||
|
||||
public sealed class TemplateLayout
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("zone-count")]
|
||||
public int ZoneCount { get; set; }
|
||||
|
||||
[JsonPropertyName("show-spacing")]
|
||||
public bool ShowSpacing { get; set; }
|
||||
|
||||
[JsonPropertyName("spacing")]
|
||||
public int Spacing { get; set; }
|
||||
|
||||
[JsonPropertyName("sensitivity-radius")]
|
||||
public int SensitivityRadius { get; set; }
|
||||
}
|
||||
|
||||
// Custom Layouts
|
||||
public sealed class CustomLayouts
|
||||
{
|
||||
[JsonPropertyName("custom-layouts")]
|
||||
public List<CustomLayout> Layouts { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CustomLayout
|
||||
{
|
||||
[JsonPropertyName("uuid")]
|
||||
public string Uuid { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("info")]
|
||||
public JsonElement Info { get; set; }
|
||||
}
|
||||
|
||||
// Applied Layouts
|
||||
public sealed class AppliedLayouts
|
||||
{
|
||||
[JsonPropertyName("applied-layouts")]
|
||||
public List<AppliedLayoutWrapper> Layouts { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AppliedLayoutWrapper
|
||||
{
|
||||
[JsonPropertyName("device")]
|
||||
public DeviceInfo Device { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("applied-layout")]
|
||||
public AppliedLayoutInfo AppliedLayout { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class DeviceInfo
|
||||
{
|
||||
[JsonPropertyName("monitor")]
|
||||
public string Monitor { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("monitor-instance")]
|
||||
public string MonitorInstance { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("monitor-number")]
|
||||
public int MonitorNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("serial-number")]
|
||||
public string SerialNumber { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("virtual-desktop")]
|
||||
public string VirtualDesktop { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class AppliedLayoutInfo
|
||||
{
|
||||
[JsonPropertyName("uuid")]
|
||||
public string Uuid { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("show-spacing")]
|
||||
public bool ShowSpacing { get; set; }
|
||||
|
||||
[JsonPropertyName("spacing")]
|
||||
public int Spacing { get; set; }
|
||||
|
||||
[JsonPropertyName("zone-count")]
|
||||
public int ZoneCount { get; set; }
|
||||
|
||||
[JsonPropertyName("sensitivity-radius")]
|
||||
public int SensitivityRadius { get; set; }
|
||||
}
|
||||
|
||||
// Layout Hotkeys
|
||||
public sealed class LayoutHotkeys
|
||||
{
|
||||
[JsonPropertyName("layout-hotkeys")]
|
||||
public List<LayoutHotkey> Hotkeys { get; set; }
|
||||
}
|
||||
|
||||
public sealed class LayoutHotkey
|
||||
{
|
||||
[JsonPropertyName("key")]
|
||||
public int Key { get; set; }
|
||||
|
||||
[JsonPropertyName("layout-id")]
|
||||
public string LayoutId { get; set; } = string.Empty;
|
||||
}
|
||||
764
src/modules/fancyzones/FancyZonesCLI/Program.cs
Normal file
764
src/modules/fancyzones/FancyZonesCLI/Program.cs
Normal file
@@ -0,0 +1,764 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FancyZonesCLI;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
// Initialize Windows messages
|
||||
InitializeWindowMessages();
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
var command = args[0].ToLowerInvariant();
|
||||
|
||||
return 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()) : 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 <key> <uuid>"),
|
||||
"remove-hotkey" or "rhk" => args.Length >= 2 ? RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture)) : PrintErrorAndReturn("Error: remove-hotkey requires <key>"),
|
||||
"help" or "--help" or "-h" => PrintUsageAndReturn(),
|
||||
_ => PrintUnknownCommandAndReturn(command),
|
||||
};
|
||||
}
|
||||
|
||||
private static int PrintErrorAndReturn(string message)
|
||||
{
|
||||
Console.WriteLine(message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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 <command> [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) <uuid> [options]");
|
||||
Console.WriteLine(" Set layout by UUID");
|
||||
Console.WriteLine(" --monitor <n> 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) <key> <uuid> Assign hotkey (0-9) to CUSTOM layout");
|
||||
Console.WriteLine(" Note: Only custom layouts work with hotkeys");
|
||||
Console.WriteLine(" remove-hotkey (rhk) <key> 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()
|
||||
{
|
||||
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))
|
||||
{
|
||||
Console.WriteLine("Error: editor-parameters.json not found.");
|
||||
Console.WriteLine("Please launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Check if editor is already running
|
||||
var existingProcess = Process.GetProcessesByName("PowerToys.FancyZonesEditor").FirstOrDefault();
|
||||
if (existingProcess != null)
|
||||
{
|
||||
NativeMethods.SetForegroundWindow(existingProcess.MainWindowHandle);
|
||||
Console.WriteLine("FancyZones Editor is already running. Brought window to foreground.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Only check same directory as CLI
|
||||
var editorPath = Path.Combine(AppContext.BaseDirectory, editorExe);
|
||||
|
||||
if (File.Exists(editorPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = editorPath,
|
||||
UseShellExecute = true,
|
||||
});
|
||||
Console.WriteLine("FancyZones Editor launched successfully.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to launch: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int OpenSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Find PowerToys.exe in common locations
|
||||
string powertoysExe = null;
|
||||
|
||||
// Check in the same directory as the CLI (typical for dev builds)
|
||||
var sameDirPath = Path.Combine(AppContext.BaseDirectory, "PowerToys.exe");
|
||||
if (File.Exists(sameDirPath))
|
||||
{
|
||||
powertoysExe = sameDirPath;
|
||||
}
|
||||
|
||||
if (powertoysExe == null)
|
||||
{
|
||||
Console.WriteLine("Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = powertoysExe,
|
||||
Arguments = "--open-settings=FancyZones",
|
||||
UseShellExecute = false,
|
||||
});
|
||||
Console.WriteLine("FancyZones Settings opened successfully.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: Failed to open FancyZones Settings. {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetMonitors()
|
||||
{
|
||||
var dataPath = GetFancyZonesDataPath();
|
||||
var appliedLayoutsPath = Path.Combine(dataPath, "applied-layouts.json");
|
||||
|
||||
if (!File.Exists(appliedLayoutsPath))
|
||||
{
|
||||
Console.WriteLine("Error: applied-layouts.json not found.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(appliedLayoutsPath);
|
||||
var appliedLayouts = JsonSerializer.Deserialize<AppliedLayouts>(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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: Failed to read monitor information. {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetLayouts()
|
||||
{
|
||||
var dataPath = GetFancyZonesDataPath();
|
||||
var templatesPath = Path.Combine(dataPath, "layout-templates.json");
|
||||
var customLayoutsPath = Path.Combine(dataPath, "custom-layouts.json");
|
||||
|
||||
// Print template layouts
|
||||
if (File.Exists(templatesPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var templatesJson = JsonSerializer.Deserialize(File.ReadAllText(templatesPath), FancyZonesJsonContext.Default.LayoutTemplates);
|
||||
if (templatesJson?.Templates != null)
|
||||
{
|
||||
Console.WriteLine($"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// Draw visual preview
|
||||
LayoutVisualizer.DrawTemplateLayout(template);
|
||||
|
||||
if (i < templatesJson.Templates.Count - 1)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\n");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error parsing templates: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Print custom layouts
|
||||
if (File.Exists(customLayoutsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var customLayouts = JsonSerializer.Deserialize(File.ReadAllText(customLayoutsPath), FancyZonesJsonContext.Default.CustomLayouts);
|
||||
if (customLayouts?.Layouts != null)
|
||||
{
|
||||
Console.WriteLine($"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
|
||||
|
||||
for (int i = 0; i < customLayouts.Layouts.Count; i++)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("\nUse 'FancyZonesCLI.exe set-layout <UUID>' to apply a layout.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error parsing custom layouts: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int GetActiveLayout()
|
||||
{
|
||||
var dataPath = GetFancyZonesDataPath();
|
||||
var appliedLayoutsPath = Path.Combine(dataPath, "applied-layouts.json");
|
||||
|
||||
if (!File.Exists(appliedLayoutsPath))
|
||||
{
|
||||
Console.WriteLine($"Error: Could not find applied-layouts.json");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var appliedLayouts = JsonSerializer.Deserialize(File.ReadAllText(appliedLayoutsPath), FancyZonesJsonContext.Default.AppliedLayouts);
|
||||
if (appliedLayouts?.Layouts == null || appliedLayouts.Layouts.Count == 0)
|
||||
{
|
||||
Console.WriteLine("No active layouts found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Console.WriteLine("\n=== Active FancyZones Layout(s) ===\n");
|
||||
|
||||
for (int i = 0; i < appliedLayouts.Layouts.Count; i++)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int SetLayout(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
Console.WriteLine("Error: set-layout requires a UUID parameter");
|
||||
return 1;
|
||||
}
|
||||
|
||||
string uuid = args[0];
|
||||
int? targetMonitor = null;
|
||||
bool applyToAll = false;
|
||||
|
||||
// Parse options
|
||||
for (int i = 1; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] == "--monitor" && i + 1 < args.Length)
|
||||
{
|
||||
if (int.TryParse(args[i + 1], out int monitorNum))
|
||||
{
|
||||
targetMonitor = monitorNum;
|
||||
i++; // Skip next arg
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Error: Invalid monitor number: {args[i + 1]}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (args[i] == "--all")
|
||||
{
|
||||
applyToAll = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetMonitor.HasValue && applyToAll)
|
||||
{
|
||||
Console.WriteLine("Error: Cannot specify both --monitor and --all");
|
||||
return 1;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
if (!File.Exists(appliedLayoutsPath))
|
||||
{
|
||||
Console.WriteLine("Error: applied-layouts.json not found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 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<int> monitorsToUpdate = new List<int>();
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetHotkeys()
|
||||
{
|
||||
var dataPath = GetFancyZonesDataPath();
|
||||
var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
|
||||
|
||||
if (!File.Exists(hotkeysPath))
|
||||
{
|
||||
Console.WriteLine("No hotkeys configured.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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 + <number> 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;
|
||||
}
|
||||
}
|
||||
|
||||
private static int SetHotkey(int key, string layoutUuid)
|
||||
{
|
||||
if (key < 0 || key > 9)
|
||||
{
|
||||
Console.WriteLine("Error: Key must be between 0 and 9");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
if (File.Exists(customLayoutsPath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var customLayoutsJson = File.ReadAllText(customLayoutsPath);
|
||||
var customLayouts = JsonSerializer.Deserialize<CustomLayouts>(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
|
||||
}
|
||||
}
|
||||
|
||||
var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
|
||||
|
||||
try
|
||||
{
|
||||
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<LayoutHotkey>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
private static int RemoveHotkey(int key)
|
||||
{
|
||||
var dataPath = GetFancyZonesDataPath();
|
||||
var hotkeysPath = Path.Combine(dataPath, "layout-hotkeys.json");
|
||||
|
||||
if (!File.Exists(hotkeysPath))
|
||||
{
|
||||
Console.WriteLine($"No hotkey assigned to key {key}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
// Windows Messages for notifying FancyZones
|
||||
private static uint wmPrivAppliedLayoutsFileUpdate;
|
||||
private static uint wmPrivLayoutHotkeysFileUpdate;
|
||||
|
||||
private static void NotifyFancyZones(uint message)
|
||||
{
|
||||
// Broadcast message to all windows
|
||||
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, message, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private static void InitializeWindowMessages()
|
||||
{
|
||||
wmPrivAppliedLayoutsFileUpdate = NativeMethods.RegisterWindowMessage("{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}");
|
||||
wmPrivLayoutHotkeysFileUpdate = NativeMethods.RegisterWindowMessage("{07229b7e-4f22-4357-b136-33c289be2295}");
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
public static extern uint RegisterWindowMessage(string lpString);
|
||||
}
|
||||
Reference in New Issue
Block a user