mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 19:57:57 +01:00
update file structure
This commit is contained in:
@@ -0,0 +1,90 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FancyZonesCLI.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Editor and Settings commands.
|
||||||
|
/// </summary>
|
||||||
|
internal static class EditorCommands
|
||||||
|
{
|
||||||
|
public static (int ExitCode, string Output) OpenEditor()
|
||||||
|
{
|
||||||
|
var editorExe = "PowerToys.FancyZonesEditor.exe";
|
||||||
|
|
||||||
|
// Check if editor-parameters.json exists
|
||||||
|
if (!FancyZonesData.EditorParametersExist())
|
||||||
|
{
|
||||||
|
return (1, "Error: editor-parameters.json not found.\nPlease launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if editor is already running
|
||||||
|
var existingProcess = Process.GetProcessesByName("PowerToys.FancyZonesEditor").FirstOrDefault();
|
||||||
|
if (existingProcess != null)
|
||||||
|
{
|
||||||
|
NativeMethods.SetForegroundWindow(existingProcess.MainWindowHandle);
|
||||||
|
return (0, "FancyZones Editor is already running. Brought window to foreground.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
});
|
||||||
|
return (0, "FancyZones Editor launched successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return (1, $"Failed to launch: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (1, $"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int ExitCode, string Output) 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)
|
||||||
|
{
|
||||||
|
return (1, "Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = powertoysExe,
|
||||||
|
Arguments = "--open-settings=FancyZones",
|
||||||
|
UseShellExecute = false,
|
||||||
|
});
|
||||||
|
return (0, "FancyZones Settings opened successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return (1, $"Error: Failed to open FancyZones Settings. {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FancyZonesCLI.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hotkey-related commands.
|
||||||
|
/// </summary>
|
||||||
|
internal static class HotkeyCommands
|
||||||
|
{
|
||||||
|
public static (int ExitCode, string Output) GetHotkeys()
|
||||||
|
{
|
||||||
|
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
|
||||||
|
if (hotkeys?.Hotkeys == null || hotkeys.Hotkeys.Count == 0)
|
||||||
|
{
|
||||||
|
return (0, "No hotkeys configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
sb.AppendLine("=== Layout Hotkeys ===\n");
|
||||||
|
sb.AppendLine("Press Win + Ctrl + Alt + <number> to switch layouts:\n");
|
||||||
|
|
||||||
|
foreach (var hotkey in hotkeys.Hotkeys.OrderBy(h => h.Key))
|
||||||
|
{
|
||||||
|
sb.AppendLine(CultureInfo.InvariantCulture, $" [{hotkey.Key}] => {hotkey.LayoutId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, sb.ToString().TrimEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int ExitCode, string Output) SetHotkey(int key, string layoutUuid, Action<uint> notifyFancyZones, uint wmPrivLayoutHotkeysFileUpdate)
|
||||||
|
{
|
||||||
|
if (key < 0 || key > 9)
|
||||||
|
{
|
||||||
|
return (1, "Error: Key must be between 0 and 9");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a custom layout UUID
|
||||||
|
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;
|
||||||
|
|
||||||
|
var hotkeys = FancyZonesData.ReadLayoutHotkeys() ?? 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
|
||||||
|
FancyZonesData.WriteLayoutHotkeys(hotkeys);
|
||||||
|
|
||||||
|
// Notify FancyZones
|
||||||
|
notifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
|
||||||
|
|
||||||
|
if (isCustomLayout)
|
||||||
|
{
|
||||||
|
return (0, $"✓ Hotkey {key} assigned to custom layout '{layoutName}'\n Press Win + Ctrl + Alt + {key} to switch to this layout");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int ExitCode, string Output) RemoveHotkey(int key, Action<uint> notifyFancyZones, uint wmPrivLayoutHotkeysFileUpdate)
|
||||||
|
{
|
||||||
|
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
|
||||||
|
if (hotkeys?.Hotkeys == null)
|
||||||
|
{
|
||||||
|
return (0, $"No hotkey assigned to key {key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var removed = hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
|
||||||
|
if (removed == 0)
|
||||||
|
{
|
||||||
|
return (0, $"No hotkey assigned to key {key}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
FancyZonesData.WriteLayoutHotkeys(hotkeys);
|
||||||
|
|
||||||
|
// Notify FancyZones
|
||||||
|
notifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
|
||||||
|
|
||||||
|
return (0, $"Hotkey {key} removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
272
src/modules/fancyzones/FancyZonesCLI/Commands/LayoutCommands.cs
Normal file
272
src/modules/fancyzones/FancyZonesCLI/Commands/LayoutCommands.cs
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace FancyZonesCLI.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Layout-related commands.
|
||||||
|
/// </summary>
|
||||||
|
internal static class LayoutCommands
|
||||||
|
{
|
||||||
|
public static (int ExitCode, string Output) GetLayouts()
|
||||||
|
{
|
||||||
|
var sb = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
// Print template layouts
|
||||||
|
var templatesJson = FancyZonesData.ReadLayoutTemplates();
|
||||||
|
if (templatesJson?.Templates != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < templatesJson.Templates.Count; i++)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
sb.Append(CultureInfo.InvariantCulture, $", Spacing: {template.Spacing}px");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Draw visual preview
|
||||||
|
sb.Append(LayoutVisualizer.DrawTemplateLayout(template));
|
||||||
|
|
||||||
|
if (i < templatesJson.Templates.Count - 1)
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print custom layouts
|
||||||
|
var customLayouts = FancyZonesData.ReadCustomLayouts();
|
||||||
|
if (customLayouts?.Layouts != null)
|
||||||
|
{
|
||||||
|
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("\nUse 'FancyZonesCLI.exe set-layout <UUID>' to apply a layout.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, sb.ToString().TrimEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int ExitCode, string Output) GetActiveLayout()
|
||||||
|
{
|
||||||
|
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
||||||
|
{
|
||||||
|
return (1, $"Error: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
sb.AppendLine(CultureInfo.InvariantCulture, $" Spacing: {layout.AppliedLayout.Spacing}px");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
|
||||||
|
|
||||||
|
if (i < appliedLayouts.Layouts.Count - 1)
|
||||||
|
{
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0, sb.ToString().TrimEnd());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static (int ExitCode, string Output) SetLayout(string[] args, Action<uint> notifyFancyZones, uint wmPrivAppliedLayoutsFileUpdate)
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
return (1, "Error: set-layout requires a UUID parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
return (1, $"Error: Invalid monitor number: {args[i + 1]}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (args[i] == "--all")
|
||||||
|
{
|
||||||
|
applyToAll = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetMonitor.HasValue && applyToAll)
|
||||||
|
{
|
||||||
|
return (1, "Error: Cannot specify both --monitor and --all");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 not found in custom layouts, try template layouts (by type name)
|
||||||
|
TemplateLayout targetTemplate = null;
|
||||||
|
if (targetCustomLayout == null)
|
||||||
|
{
|
||||||
|
var templates = FancyZonesData.ReadLayoutTemplates();
|
||||||
|
targetTemplate = templates?.Templates?.FirstOrDefault(t => t.Type.Equals(uuid, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetCustomLayout == null && targetTemplate == null)
|
||||||
|
{
|
||||||
|
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'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read current applied layouts
|
||||||
|
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
||||||
|
{
|
||||||
|
return (1, $"Error: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
|
||||||
|
{
|
||||||
|
return (1, "Error: No monitors configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// 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.Globalization;
|
||||||
|
|
||||||
|
namespace FancyZonesCLI.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Monitor-related commands.
|
||||||
|
/// </summary>
|
||||||
|
internal static class MonitorCommands
|
||||||
|
{
|
||||||
|
public static (int ExitCode, string Output) GetMonitors()
|
||||||
|
{
|
||||||
|
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
||||||
|
{
|
||||||
|
return (1, $"Error: {error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
|
||||||
|
{
|
||||||
|
return (0, "No monitors found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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++)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
58
src/modules/fancyzones/FancyZonesCLI/NativeMethods.cs
Normal file
58
src/modules/fancyzones/FancyZonesCLI/NativeMethods.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace FancyZonesCLI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Native Windows API methods for FancyZones CLI.
|
||||||
|
/// </summary>
|
||||||
|
internal static class NativeMethods
|
||||||
|
{
|
||||||
|
public static readonly IntPtr HWND_BROADCAST = new IntPtr(0xffff);
|
||||||
|
|
||||||
|
// Registered Windows messages for notifying FancyZones
|
||||||
|
private static uint wmPrivAppliedLayoutsFileUpdate;
|
||||||
|
private static uint wmPrivLayoutHotkeysFileUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Windows message ID for applied layouts file update notification.
|
||||||
|
/// </summary>
|
||||||
|
public static uint WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE => wmPrivAppliedLayoutsFileUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Windows message ID for layout hotkeys file update notification.
|
||||||
|
/// </summary>
|
||||||
|
public static uint WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE => wmPrivLayoutHotkeysFileUpdate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the Windows messages used for FancyZones notifications.
|
||||||
|
/// </summary>
|
||||||
|
public static void InitializeWindowMessages()
|
||||||
|
{
|
||||||
|
wmPrivAppliedLayoutsFileUpdate = RegisterWindowMessage("{2ef2c8a7-e0d5-4f31-9ede-52aade2d284d}");
|
||||||
|
wmPrivLayoutHotkeysFileUpdate = RegisterWindowMessage("{07229b7e-4f22-4357-b136-33c289be2295}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts a notification message to FancyZones.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The Windows message ID to broadcast.</param>
|
||||||
|
public static void NotifyFancyZones(uint message)
|
||||||
|
{
|
||||||
|
PostMessage(HWND_BROADCAST, message, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
@@ -3,13 +3,9 @@
|
|||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using FancyZonesCLI.Commands;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace FancyZonesCLI;
|
namespace FancyZonesCLI;
|
||||||
|
|
||||||
@@ -18,7 +14,7 @@ internal sealed class Program
|
|||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
// Initialize Windows messages
|
// Initialize Windows messages
|
||||||
InitializeWindowMessages();
|
NativeMethods.InitializeWindowMessages();
|
||||||
|
|
||||||
(int ExitCode, string Output) result;
|
(int ExitCode, string Output) result;
|
||||||
|
|
||||||
@@ -32,15 +28,21 @@ internal sealed class Program
|
|||||||
|
|
||||||
result = command switch
|
result = command switch
|
||||||
{
|
{
|
||||||
"open-editor" or "editor" or "e" => OpenEditor(),
|
"open-editor" or "editor" or "e" => EditorCommands.OpenEditor(),
|
||||||
"get-monitors" or "monitors" or "m" => GetMonitors(),
|
"get-monitors" or "monitors" or "m" => MonitorCommands.GetMonitors(),
|
||||||
"get-layouts" or "layouts" or "ls" => GetLayouts(),
|
"get-layouts" or "layouts" or "ls" => LayoutCommands.GetLayouts(),
|
||||||
"get-active-layout" or "active" or "get-active" or "a" => GetActiveLayout(),
|
"get-active-layout" or "active" or "get-active" or "a" => LayoutCommands.GetActiveLayout(),
|
||||||
"set-layout" or "set" or "s" => args.Length >= 2 ? SetLayout(args.Skip(1).ToArray()) : (1, "Error: set-layout requires a UUID parameter"),
|
"set-layout" or "set" or "s" => args.Length >= 2
|
||||||
"open-settings" or "settings" => OpenSettings(),
|
? LayoutCommands.SetLayout(args.Skip(1).ToArray(), NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE)
|
||||||
"get-hotkeys" or "hotkeys" or "hk" => GetHotkeys(),
|
: (1, "Error: set-layout requires a UUID parameter"),
|
||||||
"set-hotkey" or "shk" => args.Length >= 3 ? SetHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), args[2]) : (1, "Error: set-hotkey requires <key> <uuid>"),
|
"open-settings" or "settings" => EditorCommands.OpenSettings(),
|
||||||
"remove-hotkey" or "rhk" => args.Length >= 2 ? RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture)) : (1, "Error: remove-hotkey requires <key>"),
|
"get-hotkeys" or "hotkeys" or "hk" => HotkeyCommands.GetHotkeys(),
|
||||||
|
"set-hotkey" or "shk" => args.Length >= 3
|
||||||
|
? HotkeyCommands.SetHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), args[2], NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE)
|
||||||
|
: (1, "Error: set-hotkey requires <key> <uuid>"),
|
||||||
|
"remove-hotkey" or "rhk" => args.Length >= 2
|
||||||
|
? HotkeyCommands.RemoveHotkey(int.Parse(args[1], CultureInfo.InvariantCulture), NativeMethods.NotifyFancyZones, NativeMethods.WM_PRIV_LAYOUT_HOTKEYS_FILE_UPDATE)
|
||||||
|
: (1, "Error: remove-hotkey requires <key>"),
|
||||||
"help" or "--help" or "-h" => (0, GetUsageText()),
|
"help" or "--help" or "-h" => (0, GetUsageText()),
|
||||||
_ => (1, $"Error: Unknown command: {command}\n\n{GetUsageText()}"),
|
_ => (1, $"Error: Unknown command: {command}\n\n{GetUsageText()}"),
|
||||||
};
|
};
|
||||||
@@ -96,482 +98,4 @@ internal sealed class Program
|
|||||||
FancyZonesCLI.exe set-hotkey 3 {12345678-1234-1234-1234-123456789012}
|
FancyZonesCLI.exe set-hotkey 3 {12345678-1234-1234-1234-123456789012}
|
||||||
""";
|
""";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (int ExitCode, string Output) OpenEditor()
|
|
||||||
{
|
|
||||||
var editorExe = "PowerToys.FancyZonesEditor.exe";
|
|
||||||
|
|
||||||
// Check if editor-parameters.json exists
|
|
||||||
if (!FancyZonesData.EditorParametersExist())
|
|
||||||
{
|
|
||||||
return (1, "Error: editor-parameters.json not found.\nPlease launch FancyZones Editor using Win+` (Win+Backtick) hotkey first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if editor is already running
|
|
||||||
var existingProcess = Process.GetProcessesByName("PowerToys.FancyZonesEditor").FirstOrDefault();
|
|
||||||
if (existingProcess != null)
|
|
||||||
{
|
|
||||||
NativeMethods.SetForegroundWindow(existingProcess.MainWindowHandle);
|
|
||||||
return (0, "FancyZones Editor is already running. Brought window to foreground.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
return (0, "FancyZones Editor launched successfully.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return (1, $"Failed to launch: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (1, $"Error: Could not find {editorExe} in {AppContext.BaseDirectory}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int ExitCode, string Output) 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)
|
|
||||||
{
|
|
||||||
return (1, "Error: PowerToys.exe not found. Please ensure PowerToys is installed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Process.Start(new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = powertoysExe,
|
|
||||||
Arguments = "--open-settings=FancyZones",
|
|
||||||
UseShellExecute = false,
|
|
||||||
});
|
|
||||||
return (0, "FancyZones Settings opened successfully.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return (1, $"Error: Failed to open FancyZones Settings. {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int ExitCode, string Output) GetMonitors()
|
|
||||||
{
|
|
||||||
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
|
||||||
{
|
|
||||||
return (1, $"Error: {error}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
|
|
||||||
{
|
|
||||||
return (0, "No monitors found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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++)
|
|
||||||
{
|
|
||||||
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 ExitCode, string Output) GetLayouts()
|
|
||||||
{
|
|
||||||
var sb = new System.Text.StringBuilder();
|
|
||||||
|
|
||||||
// Print template layouts
|
|
||||||
var templatesJson = FancyZonesData.ReadLayoutTemplates();
|
|
||||||
if (templatesJson?.Templates != null)
|
|
||||||
{
|
|
||||||
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Built-in Template Layouts ({templatesJson.Templates.Count} total) ===\n");
|
|
||||||
|
|
||||||
for (int i = 0; i < templatesJson.Templates.Count; i++)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
sb.Append(CultureInfo.InvariantCulture, $", Spacing: {template.Spacing}px");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
// Draw visual preview
|
|
||||||
sb.Append(LayoutVisualizer.DrawTemplateLayout(template));
|
|
||||||
|
|
||||||
if (i < templatesJson.Templates.Count - 1)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print custom layouts
|
|
||||||
var customLayouts = FancyZonesData.ReadCustomLayouts();
|
|
||||||
if (customLayouts?.Layouts != null)
|
|
||||||
{
|
|
||||||
sb.AppendLine(CultureInfo.InvariantCulture, $"=== Custom Layouts ({customLayouts.Layouts.Count} total) ===");
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine("\nUse 'FancyZonesCLI.exe set-layout <UUID>' to apply a layout.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (0, sb.ToString().TrimEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int ExitCode, string Output) GetActiveLayout()
|
|
||||||
{
|
|
||||||
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
|
||||||
{
|
|
||||||
return (1, $"Error: {error}");
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
sb.AppendLine(CultureInfo.InvariantCulture, $" Spacing: {layout.AppliedLayout.Spacing}px");
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine(CultureInfo.InvariantCulture, $" Sensitivity Radius: {layout.AppliedLayout.SensitivityRadius}px");
|
|
||||||
|
|
||||||
if (i < appliedLayouts.Layouts.Count - 1)
|
|
||||||
{
|
|
||||||
sb.AppendLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (0, sb.ToString().TrimEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int ExitCode, string Output) SetLayout(string[] args)
|
|
||||||
{
|
|
||||||
if (args.Length == 0)
|
|
||||||
{
|
|
||||||
return (1, "Error: set-layout requires a UUID parameter");
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
return (1, $"Error: Invalid monitor number: {args[i + 1]}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (args[i] == "--all")
|
|
||||||
{
|
|
||||||
applyToAll = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetMonitor.HasValue && applyToAll)
|
|
||||||
{
|
|
||||||
return (1, "Error: Cannot specify both --monitor and --all");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 not found in custom layouts, try template layouts (by type name)
|
|
||||||
TemplateLayout targetTemplate = null;
|
|
||||||
if (targetCustomLayout == null)
|
|
||||||
{
|
|
||||||
var templates = FancyZonesData.ReadLayoutTemplates();
|
|
||||||
targetTemplate = templates?.Templates?.FirstOrDefault(t => t.Type.Equals(uuid, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetCustomLayout == null && targetTemplate == null)
|
|
||||||
{
|
|
||||||
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'");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read current applied layouts
|
|
||||||
if (!FancyZonesData.TryReadAppliedLayouts(out var appliedLayouts, out var error))
|
|
||||||
{
|
|
||||||
return (1, $"Error: {error}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appliedLayouts.Layouts == null || appliedLayouts.Layouts.Count == 0)
|
|
||||||
{
|
|
||||||
return (1, "Error: No monitors configured");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
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 ExitCode, string Output) GetHotkeys()
|
|
||||||
{
|
|
||||||
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
|
|
||||||
if (hotkeys?.Hotkeys == null || hotkeys.Hotkeys.Count == 0)
|
|
||||||
{
|
|
||||||
return (0, "No hotkeys configured.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb = new System.Text.StringBuilder();
|
|
||||||
sb.AppendLine("=== Layout Hotkeys ===\n");
|
|
||||||
sb.AppendLine("Press Win + Ctrl + Alt + <number> to switch layouts:\n");
|
|
||||||
|
|
||||||
foreach (var hotkey in hotkeys.Hotkeys.OrderBy(h => h.Key))
|
|
||||||
{
|
|
||||||
sb.AppendLine(CultureInfo.InvariantCulture, $" [{hotkey.Key}] → {hotkey.LayoutId}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (0, sb.ToString().TrimEnd());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int ExitCode, string Output) SetHotkey(int key, string layoutUuid)
|
|
||||||
{
|
|
||||||
if (key < 0 || key > 9)
|
|
||||||
{
|
|
||||||
return (1, "Error: Key must be between 0 and 9");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this is a custom layout UUID
|
|
||||||
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;
|
|
||||||
|
|
||||||
var hotkeys = FancyZonesData.ReadLayoutHotkeys() ?? 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(FancyZonesPaths.LayoutHotkeys, JsonSerializer.Serialize(hotkeys, FancyZonesJsonContext.Default.LayoutHotkeys));
|
|
||||||
|
|
||||||
// Notify FancyZones
|
|
||||||
NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
|
|
||||||
|
|
||||||
if (isCustomLayout)
|
|
||||||
{
|
|
||||||
return (0, $"✓ Hotkey {key} assigned to custom layout '{layoutName}'\n Press Win + Ctrl + Alt + {key} to switch to this layout");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
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 ExitCode, string Output) RemoveHotkey(int key)
|
|
||||||
{
|
|
||||||
var hotkeys = FancyZonesData.ReadLayoutHotkeys();
|
|
||||||
if (hotkeys?.Hotkeys == null)
|
|
||||||
{
|
|
||||||
return (0, $"No hotkey assigned to key {key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var removed = hotkeys.Hotkeys.RemoveAll(h => h.Key == key);
|
|
||||||
if (removed == 0)
|
|
||||||
{
|
|
||||||
return (0, $"No hotkey assigned to key {key}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
FancyZonesData.WriteLayoutHotkeys(hotkeys);
|
|
||||||
|
|
||||||
// Notify FancyZones
|
|
||||||
NotifyFancyZones(wmPrivLayoutHotkeysFileUpdate);
|
|
||||||
|
|
||||||
return (0, $"Hotkey {key} removed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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