merge main

This commit is contained in:
vanzue
2025-12-11 13:26:50 +08:00
71 changed files with 2860 additions and 523 deletions

View File

@@ -335,3 +335,7 @@ azp
feedbackhub
needinfo
reportbug
#ffmpeg
crf
nostdin

View File

@@ -144,6 +144,8 @@ BLENDFUNCTION
blittable
Blockquotes
blt
bluelightreduction
bluelightreductionstate
BLURBEHIND
BLURREGION
bmi
@@ -1116,6 +1118,7 @@ NEWPLUSSHELLEXTENSIONWIN
newrow
nicksnettravels
NIF
nightlight
NLog
NLSTEXT
NMAKE
@@ -1852,6 +1855,8 @@ uitests
UITo
ULONGLONG
ums
UMax
UMin
uncompilable
UNCPRIORITY
UNDNAME

View File

@@ -27,7 +27,8 @@ $versionExceptions = @(
"WyHash.dll",
"Microsoft.Recognizers.Text.DataTypes.TimexExpression.dll",
"ObjectModelCsProjection.dll",
"RendererCsProjection.dll") -join '|';
"RendererCsProjection.dll",
"Microsoft.ML.OnnxRuntime.dll") -join '|';
$nullVersionExceptions = @(
"SkiaSharp.Views.WinUI.Native.dll",
"libSkiaSharp.dll",

View File

@@ -42,11 +42,6 @@
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<!-- Make angle-bracket includes external and turn off code analysis for them -->
<TreatAngleIncludeAsExternal>true</TreatAngleIncludeAsExternal>
<ExternalWarningLevel>TurnOffAllWarnings</ExternalWarningLevel>
<DisableAnalyzeExternal>true</DisableAnalyzeExternal>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
@@ -116,11 +111,13 @@
</PropertyGroup>
<!-- Debug/Release props -->
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<PropertyGroup Condition="'$(Configuration)'=='Debug'"
Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<PropertyGroup Condition="'$(Configuration)'=='Release'"
Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>

View File

@@ -7,8 +7,6 @@
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
<PackageVersion Include="AdaptiveCards.Rendering.WinUI3" Version="2.1.0-beta" />
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.5" />
<PackageVersion Include="boost" Version="1.87.0" TargetFramework="native" />
<PackageVersion Include="boost_regex-vc143" Version="1.87.0" TargetFramework="native" />
<PackageVersion Include="CommunityToolkit.Labs.WinUI.Controls.OpacityMaskView" Version="0.1.251101-build.2372" />
<PackageVersion Include="Microsoft.Bot.AdaptiveExpressions.Core" Version="4.23.0" />
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
@@ -72,12 +70,10 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.231216.1"/>
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="1.8.251104000" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.39" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.251106002" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
@@ -117,7 +113,6 @@
<PackageVersion Include="System.IO.Abstractions.TestingHelpers" Version="22.0.13" />
<PackageVersion Include="System.Management" Version="9.0.10" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Numerics.Tensors" Version="9.0.11" />
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Runtime.Caching" Version="9.0.10" />

View File

@@ -0,0 +1,399 @@
// 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.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.PowerToys.UITest
{
/// <summary>
/// Provides methods for recording the screen during UI tests.
/// Requires FFmpeg to be installed and available in PATH.
/// </summary>
internal class ScreenRecording : IDisposable
{
private readonly string outputDirectory;
private readonly string framesDirectory;
private readonly string outputFilePath;
private readonly List<string> capturedFrames;
private readonly SemaphoreSlim recordingLock = new(1, 1);
private readonly Stopwatch recordingStopwatch = new();
private readonly string? ffmpegPath;
private CancellationTokenSource? recordingCancellation;
private Task? recordingTask;
private bool isRecording;
private int frameCount;
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("user32.dll")]
private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
private static extern bool GetCursorInfo(out ScreenCapture.CURSORINFO pci);
[DllImport("user32.dll")]
private static extern bool DrawIconEx(IntPtr hdc, int x, int y, IntPtr hIcon, int cx, int cy, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
private const int CURSORSHOWING = 0x00000001;
private const int DESKTOPHORZRES = 118;
private const int DESKTOPVERTRES = 117;
private const int DINORMAL = 0x0003;
private const int TargetFps = 15; // 15 FPS for good balance of quality and size
/// <summary>
/// Initializes a new instance of the <see cref="ScreenRecording"/> class.
/// </summary>
/// <param name="outputDirectory">Directory where the recording will be saved.</param>
public ScreenRecording(string outputDirectory)
{
this.outputDirectory = outputDirectory;
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
framesDirectory = Path.Combine(outputDirectory, $"frames_{timestamp}");
outputFilePath = Path.Combine(outputDirectory, $"recording_{timestamp}.mp4");
capturedFrames = new List<string>();
frameCount = 0;
// Check if FFmpeg is available
ffmpegPath = FindFfmpeg();
if (ffmpegPath == null)
{
Console.WriteLine("FFmpeg not found. Screen recording will be disabled.");
Console.WriteLine("To enable video recording, install FFmpeg: https://ffmpeg.org/download.html");
}
}
/// <summary>
/// Gets a value indicating whether screen recording is available (FFmpeg found).
/// </summary>
public bool IsAvailable => ffmpegPath != null;
/// <summary>
/// Starts recording the screen.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task StartRecordingAsync()
{
await recordingLock.WaitAsync();
try
{
if (isRecording || !IsAvailable)
{
return;
}
// Create frames directory
Directory.CreateDirectory(framesDirectory);
recordingCancellation = new CancellationTokenSource();
isRecording = true;
recordingStopwatch.Start();
// Start the recording task
recordingTask = Task.Run(() => RecordFrames(recordingCancellation.Token));
Console.WriteLine($"Started screen recording at {TargetFps} FPS");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to start recording: {ex.Message}");
isRecording = false;
}
finally
{
recordingLock.Release();
}
}
/// <summary>
/// Stops recording and encodes video.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task StopRecordingAsync()
{
await recordingLock.WaitAsync();
try
{
if (!isRecording || recordingCancellation == null)
{
return;
}
// Signal cancellation
recordingCancellation.Cancel();
// Wait for recording task to complete
if (recordingTask != null)
{
await recordingTask;
}
recordingStopwatch.Stop();
isRecording = false;
double duration = recordingStopwatch.Elapsed.TotalSeconds;
Console.WriteLine($"Recording stopped. Captured {capturedFrames.Count} frames in {duration:F2} seconds");
// Encode to video
await EncodeToVideoAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Error stopping recording: {ex.Message}");
}
finally
{
Cleanup();
recordingLock.Release();
}
}
/// <summary>
/// Records frames from the screen.
/// </summary>
private void RecordFrames(CancellationToken cancellationToken)
{
try
{
int frameInterval = 1000 / TargetFps;
var frameTimer = Stopwatch.StartNew();
while (!cancellationToken.IsCancellationRequested)
{
var frameStart = frameTimer.ElapsedMilliseconds;
try
{
CaptureFrame();
}
catch (Exception ex)
{
Console.WriteLine($"Error capturing frame: {ex.Message}");
}
// Sleep for remaining time to maintain target FPS
var frameTime = frameTimer.ElapsedMilliseconds - frameStart;
var sleepTime = Math.Max(0, frameInterval - (int)frameTime);
if (sleepTime > 0)
{
Thread.Sleep(sleepTime);
}
}
}
catch (OperationCanceledException)
{
// Expected when stopping
}
catch (Exception ex)
{
Console.WriteLine($"Error during recording: {ex.Message}");
}
}
/// <summary>
/// Captures a single frame.
/// </summary>
private void CaptureFrame()
{
IntPtr hdc = GetDC(IntPtr.Zero);
int screenWidth = GetDeviceCaps(hdc, DESKTOPHORZRES);
int screenHeight = GetDeviceCaps(hdc, DESKTOPVERTRES);
ReleaseDC(IntPtr.Zero, hdc);
Rectangle bounds = new Rectangle(0, 0, screenWidth, screenHeight);
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
ScreenCapture.CURSORINFO cursorInfo;
cursorInfo.CbSize = Marshal.SizeOf<ScreenCapture.CURSORINFO>();
if (GetCursorInfo(out cursorInfo) && cursorInfo.Flags == CURSORSHOWING)
{
IntPtr hdcDest = g.GetHdc();
DrawIconEx(hdcDest, cursorInfo.PTScreenPos.X, cursorInfo.PTScreenPos.Y, cursorInfo.HCursor, 0, 0, 0, IntPtr.Zero, DINORMAL);
g.ReleaseHdc(hdcDest);
}
}
string framePath = Path.Combine(framesDirectory, $"frame_{frameCount:D6}.jpg");
bitmap.Save(framePath, ImageFormat.Jpeg);
capturedFrames.Add(framePath);
frameCount++;
}
}
/// <summary>
/// Encodes captured frames to video using ffmpeg.
/// </summary>
private async Task EncodeToVideoAsync()
{
if (capturedFrames.Count == 0)
{
Console.WriteLine("No frames captured");
return;
}
try
{
// Build ffmpeg command with proper non-interactive flags
string inputPattern = Path.Combine(framesDirectory, "frame_%06d.jpg");
// -y: overwrite without asking
// -nostdin: disable interaction
// -loglevel error: only show errors
// -stats: show encoding progress
string args = $"-y -nostdin -loglevel error -stats -framerate {TargetFps} -i \"{inputPattern}\" -c:v libx264 -pix_fmt yuv420p -crf 23 \"{outputFilePath}\"";
Console.WriteLine($"Encoding {capturedFrames.Count} frames to video...");
var startInfo = new ProcessStartInfo
{
FileName = ffmpegPath!,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true, // Important: redirect stdin to prevent hanging
CreateNoWindow = true,
};
using var process = Process.Start(startInfo);
if (process != null)
{
// Close stdin immediately to ensure FFmpeg doesn't wait for input
process.StandardInput.Close();
// Read output streams asynchronously to prevent deadlock
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
// Wait for process to exit
await process.WaitForExitAsync();
// Get the output
string stdout = await outputTask;
string stderr = await errorTask;
if (process.ExitCode == 0 && File.Exists(outputFilePath))
{
var fileInfo = new FileInfo(outputFilePath);
Console.WriteLine($"Video created: {outputFilePath} ({fileInfo.Length / 1024 / 1024:F1} MB)");
}
else
{
Console.WriteLine($"FFmpeg encoding failed with exit code {process.ExitCode}");
if (!string.IsNullOrWhiteSpace(stderr))
{
Console.WriteLine($"FFmpeg error: {stderr}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error encoding video: {ex.Message}");
}
}
/// <summary>
/// Finds ffmpeg executable.
/// </summary>
private static string? FindFfmpeg()
{
// Check if ffmpeg is in PATH
var pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(Path.PathSeparator) ?? Array.Empty<string>();
foreach (var dir in pathDirs)
{
var ffmpegPath = Path.Combine(dir, "ffmpeg.exe");
if (File.Exists(ffmpegPath))
{
return ffmpegPath;
}
}
// Check common installation locations
var commonPaths = new[]
{
@"C:\.tools\ffmpeg\bin\ffmpeg.exe",
@"C:\ffmpeg\bin\ffmpeg.exe",
@"C:\Program Files\ffmpeg\bin\ffmpeg.exe",
@"C:\Program Files (x86)\ffmpeg\bin\ffmpeg.exe",
@$"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Microsoft\WinGet\Links\ffmpeg.exe",
};
foreach (var path in commonPaths)
{
if (File.Exists(path))
{
return path;
}
}
return null;
}
/// <summary>
/// Gets the path to the recorded video file.
/// </summary>
public string OutputFilePath => outputFilePath;
/// <summary>
/// Gets the directory containing recordings.
/// </summary>
public string OutputDirectory => outputDirectory;
/// <summary>
/// Cleans up resources.
/// </summary>
private void Cleanup()
{
recordingCancellation?.Dispose();
recordingCancellation = null;
recordingTask = null;
// Clean up frames directory if it exists
try
{
if (Directory.Exists(framesDirectory))
{
Directory.Delete(framesDirectory, true);
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to cleanup frames directory: {ex.Message}");
}
}
/// <summary>
/// Disposes resources.
/// </summary>
public void Dispose()
{
if (isRecording)
{
StopRecordingAsync().GetAwaiter().GetResult();
}
Cleanup();
recordingLock.Dispose();
GC.SuppressFinalize(this);
}
}
}

View File

@@ -130,9 +130,13 @@ namespace Microsoft.PowerToys.UITest
/// </summary>
/// <param name="appPath">The path to the application executable.</param>
/// <param name="args">Optional command line arguments to pass to the application.</param>
public void StartExe(string appPath, string[]? args = null)
public void StartExe(string appPath, string[]? args = null, string? enableModules = null)
{
var opts = new AppiumOptions();
if (!string.IsNullOrEmpty(enableModules))
{
opts.AddAdditionalCapability("enableModules", enableModules);
}
if (scope == PowerToysModule.PowerToysSettings)
{
@@ -169,27 +173,66 @@ namespace Microsoft.PowerToys.UITest
private void TryLaunchPowerToysSettings(AppiumOptions opts)
{
try
if (opts.ToCapabilities().HasCapability("enableModules"))
{
var runnerProcessInfo = new ProcessStartInfo
var modulesString = (string)opts.ToCapabilities().GetCapability("enableModules");
var modulesArray = modulesString.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
SettingsConfigHelper.ConfigureGlobalModuleSettings(modulesArray);
}
else
{
SettingsConfigHelper.ConfigureGlobalModuleSettings();
}
const int maxTries = 3;
const int delayMs = 5000;
const int maxRetries = 3;
for (int tryCount = 1; tryCount <= maxTries; tryCount++)
{
try
{
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
var runnerProcessInfo = new ProcessStartInfo
{
FileName = locationPath + runnerPath,
Verb = "runas",
Arguments = "--open-settings",
};
ExitExe(runnerProcessInfo.FileName);
runner = Process.Start(runnerProcessInfo);
ExitExe(runnerProcessInfo.FileName);
WaitForWindowAndSetCapability(opts, "PowerToys Settings", 5000, 5);
// Verify process was killed
string exeName = Path.GetFileNameWithoutExtension(runnerProcessInfo.FileName);
var remainingProcesses = Process.GetProcessesByName(exeName);
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to launch PowerToys Settings: {ex.Message}", ex);
runner = Process.Start(runnerProcessInfo);
if (WaitForWindowAndSetCapability(opts, "PowerToys Settings", delayMs, maxRetries))
{
// Exit CmdPal UI before launching new process if use installer for test
ExitExeByName("Microsoft.CmdPal.UI");
return;
}
// Window not found, kill all PowerToys processes and retry
if (tryCount < maxTries)
{
KillPowerToysProcesses();
}
}
catch (Exception ex)
{
if (tryCount == maxTries)
{
throw new InvalidOperationException($"Failed to launch PowerToys Settings after {maxTries} attempts: {ex.Message}", ex);
}
// Kill processes and retry
KillPowerToysProcesses();
}
}
throw new InvalidOperationException($"Failed to launch PowerToys Settings: Window not found after {maxTries} attempts.");
}
private void TryLaunchCommandPalette(AppiumOptions opts)
@@ -211,7 +254,10 @@ namespace Microsoft.PowerToys.UITest
var process = Process.Start(processStartInfo);
process?.WaitForExit();
WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10);
if (!WaitForWindowAndSetCapability(opts, "Command Palette", 5000, 10))
{
throw new TimeoutException("Failed to find Command Palette window after multiple attempts.");
}
}
catch (Exception ex)
{
@@ -219,7 +265,7 @@ namespace Microsoft.PowerToys.UITest
}
}
private void WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
private bool WaitForWindowAndSetCapability(AppiumOptions opts, string windowName, int delayMs, int maxRetries)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
@@ -230,18 +276,16 @@ namespace Microsoft.PowerToys.UITest
{
var hexHwnd = window[0].HWnd.ToString("x");
opts.AddAdditionalCapability("appTopLevelWindow", hexHwnd);
return;
return true;
}
if (attempt < maxRetries)
{
Thread.Sleep(delayMs);
}
else
{
throw new TimeoutException($"Failed to find {windowName} window after multiple attempts.");
}
}
return false;
}
/// <summary>
@@ -292,17 +336,17 @@ namespace Microsoft.PowerToys.UITest
catch (Exception ex)
{
// Handle exceptions if needed
Debug.WriteLine($"Exception during Cleanup: {ex.Message}");
Console.WriteLine($"Exception during Cleanup: {ex.Message}");
}
}
/// <summary>
/// Restarts now exe and takes control of it.
/// </summary>
public void RestartScopeExe()
public void RestartScopeExe(string? enableModules = null)
{
ExitScopeExe();
StartExe(locationPath + sessionPath, this.commandLineArgs);
StartExe(locationPath + sessionPath, commandLineArgs, enableModules);
}
public WindowsDriver<WindowsElement> GetRoot()
@@ -327,5 +371,31 @@ namespace Microsoft.PowerToys.UITest
this.ExitExe(winAppDriverProcessInfo.FileName);
SessionHelper.appDriver = Process.Start(winAppDriverProcessInfo);
}
private void KillPowerToysProcesses()
{
var powerToysProcessNames = new[] { "PowerToys", "Microsoft.CmdPal.UI" };
foreach (var processName in powerToysProcessNames)
{
try
{
var processes = Process.GetProcessesByName(processName);
foreach (var process in processes)
{
process.Kill();
process.WaitForExit();
}
// Verify processes are actually gone
var remainingProcesses = Process.GetProcessesByName(processName);
}
catch (Exception ex)
{
Console.WriteLine($"[KillPowerToysProcesses] Failed to kill process {processName}: {ex.Message}");
}
}
}
}
}

View File

@@ -26,14 +26,13 @@ namespace Microsoft.PowerToys.UITest
/// <summary>
/// Configures global PowerToys settings to enable only specified modules and disable all others.
/// </summary>
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.</param>
/// <exception cref="ArgumentNullException">Thrown when modulesToEnable is null.</exception>
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled. If null or empty, all modules will be disabled.</param>
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable)
public static void ConfigureGlobalModuleSettings(params string[]? modulesToEnable)
{
ArgumentNullException.ThrowIfNull(modulesToEnable);
modulesToEnable ??= Array.Empty<string>();
try
{

View File

@@ -29,6 +29,8 @@ namespace Microsoft.PowerToys.UITest
public string? ScreenshotDirectory { get; set; }
public string? RecordingDirectory { get; set; }
public static MonitorInfoData.ParamsWrapper MonitorInfoData { get; set; } = new MonitorInfoData.ParamsWrapper() { Monitors = new List<MonitorInfoData.MonitorInfoDataWrapper>() };
private readonly PowerToysModule scope;
@@ -36,6 +38,7 @@ namespace Microsoft.PowerToys.UITest
private readonly string[]? commandLineArgs;
private SessionHelper? sessionHelper;
private System.Threading.Timer? screenshotTimer;
private ScreenRecording? screenRecording;
public UITestBase(PowerToysModule scope = PowerToysModule.PowerToysSettings, WindowSize size = WindowSize.UnSpecified, string[]? commandLineArgs = null)
{
@@ -65,12 +68,35 @@ namespace Microsoft.PowerToys.UITest
CloseOtherApplications();
if (IsInPipeline)
{
ScreenshotDirectory = Path.Combine(this.TestContext.TestResultsDirectory ?? string.Empty, "UITestScreenshots_" + Guid.NewGuid().ToString());
string baseDirectory = this.TestContext.TestResultsDirectory ?? string.Empty;
ScreenshotDirectory = Path.Combine(baseDirectory, "UITestScreenshots_" + Guid.NewGuid().ToString());
Directory.CreateDirectory(ScreenshotDirectory);
RecordingDirectory = Path.Combine(baseDirectory, "UITestRecordings_" + Guid.NewGuid().ToString());
Directory.CreateDirectory(RecordingDirectory);
// Take screenshot every 1 second
screenshotTimer = new System.Threading.Timer(ScreenCapture.TimerCallback, ScreenshotDirectory, TimeSpan.Zero, TimeSpan.FromMilliseconds(1000));
// Start screen recording (requires FFmpeg)
try
{
screenRecording = new ScreenRecording(RecordingDirectory);
if (screenRecording.IsAvailable)
{
_ = screenRecording.StartRecordingAsync();
}
else
{
screenRecording = null;
}
}
catch (Exception ex)
{
Console.WriteLine($"Failed to start screen recording: {ex.Message}");
screenRecording = null;
}
// Escape Popups before starting
System.Windows.Forms.SendKeys.SendWait("{ESC}");
}
@@ -88,15 +114,36 @@ namespace Microsoft.PowerToys.UITest
if (IsInPipeline)
{
screenshotTimer?.Change(Timeout.Infinite, Timeout.Infinite);
Dispose();
// Stop screen recording
if (screenRecording != null)
{
try
{
screenRecording.StopRecordingAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
Console.WriteLine($"Failed to stop screen recording: {ex.Message}");
}
}
if (TestContext.CurrentTestOutcome is UnitTestOutcome.Failed
or UnitTestOutcome.Error
or UnitTestOutcome.Unknown)
{
Task.Delay(1000).Wait();
AddScreenShotsToTestResultsDirectory();
AddRecordingsToTestResultsDirectory();
AddLogFilesToTestResultsDirectory();
}
else
{
// Clean up recording if test passed
CleanupRecordingDirectory();
}
Dispose();
}
this.Session.Cleanup();
@@ -106,6 +153,7 @@ namespace Microsoft.PowerToys.UITest
public void Dispose()
{
screenshotTimer?.Dispose();
screenRecording?.Dispose();
GC.SuppressFinalize(this);
}
@@ -600,6 +648,47 @@ namespace Microsoft.PowerToys.UITest
}
}
/// <summary>
/// Adds screen recordings to test results directory when test fails.
/// </summary>
protected void AddRecordingsToTestResultsDirectory()
{
if (RecordingDirectory != null && Directory.Exists(RecordingDirectory))
{
// Add video files (MP4)
var videoFiles = Directory.GetFiles(RecordingDirectory, "*.mp4");
foreach (string file in videoFiles)
{
this.TestContext.AddResultFile(file);
var fileInfo = new FileInfo(file);
Console.WriteLine($"Added video recording: {Path.GetFileName(file)} ({fileInfo.Length / 1024 / 1024:F1} MB)");
}
if (videoFiles.Length == 0)
{
Console.WriteLine("No video recording available (FFmpeg not found). Screenshots are still captured.");
}
}
}
/// <summary>
/// Cleans up recording directory when test passes.
/// </summary>
private void CleanupRecordingDirectory()
{
if (RecordingDirectory != null && Directory.Exists(RecordingDirectory))
{
try
{
Directory.Delete(RecordingDirectory, true);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to cleanup recording directory: {ex.Message}");
}
}
}
/// <summary>
/// Copies PowerToys log files to test results directory when test fails.
/// Renames files to include the directory structure after \PowerToys.
@@ -689,11 +778,11 @@ namespace Microsoft.PowerToys.UITest
/// <summary>
/// Restart scope exe.
/// </summary>
public void RestartScopeExe()
public Session RestartScopeExe(string? enableModules = null)
{
this.sessionHelper!.RestartScopeExe();
this.sessionHelper!.RestartScopeExe(enableModules);
this.Session = new Session(this.sessionHelper.GetRoot(), this.sessionHelper.GetDriver(), this.scope, this.size);
return;
return Session;
}
/// <summary>

View File

@@ -52,6 +52,7 @@ enum class ScheduleMode
Off,
FixedHours,
SunsetToSunrise,
FollowNightLight,
// add more later
};
@@ -63,6 +64,8 @@ inline std::wstring ToString(ScheduleMode mode)
return L"SunsetToSunrise";
case ScheduleMode::FixedHours:
return L"FixedHours";
case ScheduleMode::FollowNightLight:
return L"FollowNightLight";
default:
return L"Off";
}
@@ -74,6 +77,8 @@ inline ScheduleMode FromString(const std::wstring& str)
return ScheduleMode::SunsetToSunrise;
if (str == L"FixedHours")
return ScheduleMode::FixedHours;
if (str == L"FollowNightLight")
return ScheduleMode::FollowNightLight;
return ScheduleMode::Off;
}
@@ -178,7 +183,9 @@ public:
ToString(g_settings.m_scheduleMode),
{ { L"Off", L"Disable the schedule" },
{ L"FixedHours", L"Set hours manually" },
{ L"SunsetToSunrise", L"Use sunrise/sunset times" } });
{ L"SunsetToSunrise", L"Use sunrise/sunset times" },
{ L"FollowNightLight", L"Follow Windows Night Light state" }
});
// Integer spinners
settings.add_int_spinner(

View File

@@ -13,10 +13,12 @@
#include <utils/logger_helper.h>
#include "LightSwitchStateManager.h"
#include <LightSwitchUtils.h>
#include <NightLightRegistryObserver.h>
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
HANDLE g_ServiceStopEvent = nullptr;
static LightSwitchStateManager* g_stateManagerPtr = nullptr;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD dwCtrl);
@@ -168,7 +170,15 @@ static void DetectAndHandleExternalThemeChange(LightSwitchStateManager& stateMan
}
// Use shared helper (handles wraparound logic)
bool shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
bool shouldBeLight = false;
if (s.scheduleMode == ScheduleMode::FollowNightLight)
{
shouldBeLight = !IsNightLightEnabled();
}
else
{
shouldBeLight = ShouldBeLight(nowMinutes, effectiveLight, effectiveDark);
}
// Compare current system/apps theme
bool currentSystemLight = GetCurrentSystemTheme();
@@ -199,15 +209,40 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
// Initialization
// ────────────────────────────────────────────────────────────────
static LightSwitchStateManager stateManager;
g_stateManagerPtr = &stateManager;
LightSwitchSettings::instance().InitFileWatcher();
HANDLE hManualOverride = OpenEventW(SYNCHRONIZE | EVENT_MODIFY_STATE, FALSE, L"POWERTOYS_LIGHTSWITCH_MANUAL_OVERRIDE");
HANDLE hSettingsChanged = LightSwitchSettings::instance().GetSettingsChangedEvent();
static std::unique_ptr<NightLightRegistryObserver> g_nightLightWatcher;
LightSwitchSettings::instance().LoadSettings();
const auto& settings = LightSwitchSettings::instance().settings();
// after loading settings:
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
if (nightLightNeeded && !g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
HKEY_CURRENT_USER,
NIGHT_LIGHT_REGISTRY_PATH,
[]() {
if (g_stateManagerPtr)
g_stateManagerPtr->OnNightLightChange();
});
}
else if (!nightLightNeeded && g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
SYSTEMTIME st;
GetLocalTime(&st);
int nowMinutes = st.wHour * 60 + st.wMinute;
@@ -274,6 +309,31 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
ResetEvent(hSettingsChanged);
LightSwitchSettings::instance().LoadSettings();
stateManager.OnSettingsChanged();
const auto& settings = LightSwitchSettings::instance().settings();
bool nightLightNeeded = (settings.scheduleMode == ScheduleMode::FollowNightLight);
if (nightLightNeeded && !g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Starting Night Light registry watcher...");
g_nightLightWatcher = std::make_unique<NightLightRegistryObserver>(
HKEY_CURRENT_USER,
NIGHT_LIGHT_REGISTRY_PATH,
[]() {
if (g_stateManagerPtr)
g_stateManagerPtr->OnNightLightChange();
});
stateManager.OnNightLightChange();
}
else if (!nightLightNeeded && g_nightLightWatcher)
{
Logger::info(L"[LightSwitchService] Stopping Night Light registry watcher...");
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
continue;
}
}
@@ -285,6 +345,11 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
CloseHandle(hManualOverride);
if (hParent)
CloseHandle(hParent);
if (g_nightLightWatcher)
{
g_nightLightWatcher->Stop();
g_nightLightWatcher.reset();
}
Logger::info(L"[LightSwitchService] Worker thread exiting cleanly.");
return 0;

View File

@@ -76,6 +76,7 @@
<ClCompile Include="LightSwitchService.cpp" />
<ClCompile Include="LightSwitchSettings.cpp" />
<ClCompile Include="LightSwitchStateManager.cpp" />
<ClCompile Include="NightLightRegistryObserver.cpp" />
<ClCompile Include="SettingsConstants.cpp" />
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="ThemeScheduler.cpp" />
@@ -88,6 +89,7 @@
<ClInclude Include="LightSwitchSettings.h" />
<ClInclude Include="LightSwitchStateManager.h" />
<ClInclude Include="LightSwitchUtils.h" />
<ClInclude Include="NightLightRegistryObserver.h" />
<ClInclude Include="SettingsConstants.h" />
<ClInclude Include="SettingsObserver.h" />
<ClInclude Include="ThemeHelper.h" />

View File

@@ -36,6 +36,9 @@
<ClCompile Include="LightSwitchStateManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="NightLightRegistryObserver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ThemeScheduler.h">
@@ -62,6 +65,9 @@
<ClInclude Include="LightSwitchUtils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="NightLightRegistryObserver.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />

View File

@@ -19,7 +19,8 @@ enum class ScheduleMode
{
Off,
FixedHours,
SunsetToSunrise
SunsetToSunrise,
FollowNightLight,
// Add more in the future
};
@@ -31,6 +32,8 @@ inline std::wstring ToString(ScheduleMode mode)
return L"FixedHours";
case ScheduleMode::SunsetToSunrise:
return L"SunsetToSunrise";
case ScheduleMode::FollowNightLight:
return L"FollowNightLight";
default:
return L"Off";
}
@@ -42,6 +45,8 @@ inline ScheduleMode FromString(const std::wstring& str)
return ScheduleMode::SunsetToSunrise;
if (str == L"FixedHours")
return ScheduleMode::FixedHours;
if (str == L"FollowNightLight")
return ScheduleMode::FollowNightLight;
else
return ScheduleMode::Off;
}

View File

@@ -31,7 +31,10 @@ void LightSwitchStateManager::OnSettingsChanged()
void LightSwitchStateManager::OnTick(int currentMinutes)
{
std::lock_guard<std::mutex> lock(_stateMutex);
EvaluateAndApplyIfNeeded();
if (_state.lastAppliedMode != ScheduleMode::FollowNightLight)
{
EvaluateAndApplyIfNeeded();
}
}
// Called when manual override is triggered
@@ -49,8 +52,38 @@ void LightSwitchStateManager::OnManualOverride()
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::debug(L"[LightSwitchStateManager] Synced internal theme state to current system theme ({}) and apps theme ({}).",
(_state.isSystemLightActive ? L"light" : L"dark"),
(_state.isAppsLightActive ? L"light" : L"dark"));
(_state.isSystemLightActive ? L"light" : L"dark"),
(_state.isAppsLightActive ? L"light" : L"dark"));
}
EvaluateAndApplyIfNeeded();
}
// Runs with the registry observer detects a change in Night Light settings.
void LightSwitchStateManager::OnNightLightChange()
{
std::lock_guard<std::mutex> lock(_stateMutex);
bool newNightLightState = IsNightLightEnabled();
// In Follow Night Light mode, treat a Night Light toggle as a boundary
if (_state.lastAppliedMode == ScheduleMode::FollowNightLight && _state.isManualOverride)
{
Logger::info(L"[LightSwitchStateManager] Night Light changed while manual override active; "
L"treating as a boundary and clearing manual override.");
_state.isManualOverride = false;
}
if (newNightLightState != _state.isNightLightActive)
{
Logger::info(L"[LightSwitchStateManager] Night Light toggled to {}",
newNightLightState ? L"ON" : L"OFF");
_state.isNightLightActive = newNightLightState;
}
else
{
Logger::debug(L"[LightSwitchStateManager] Night Light change event fired, but no actual change.");
}
EvaluateAndApplyIfNeeded();
@@ -77,9 +110,9 @@ void LightSwitchStateManager::SyncInitialThemeState()
_state.isSystemLightActive = GetCurrentSystemTheme();
_state.isAppsLightActive = GetCurrentAppsTheme();
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current system theme ({})",
_state.isSystemLightActive ? L"light" : L"dark");
_state.isSystemLightActive ? L"light" : L"dark");
Logger::debug(L"[LightSwitchStateManager] Synced initial state to current apps theme ({})",
_state.isAppsLightActive ? L"light" : L"dark");
_state.isAppsLightActive ? L"light" : L"dark");
}
static std::pair<int, int> update_sun_times(auto& settings)
@@ -194,7 +227,15 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
_state.lastAppliedMode = _currentSettings.scheduleMode;
bool shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
bool shouldBeLight = false;
if (_currentSettings.scheduleMode == ScheduleMode::FollowNightLight)
{
shouldBeLight = !_state.isNightLightActive;
}
else
{
shouldBeLight = ShouldBeLight(now, _state.effectiveLightMinutes, _state.effectiveDarkMinutes);
}
bool appsNeedsToChange = _currentSettings.changeApps && (_state.isAppsLightActive != shouldBeLight);
bool systemNeedsToChange = _currentSettings.changeSystem && (_state.isSystemLightActive != shouldBeLight);
@@ -227,6 +268,3 @@ void LightSwitchStateManager::EvaluateAndApplyIfNeeded()
_state.lastTickMinutes = now;
}

View File

@@ -9,6 +9,7 @@ struct LightSwitchState
bool isManualOverride = false;
bool isSystemLightActive = false;
bool isAppsLightActive = false;
bool isNightLightActive = false;
int lastEvaluatedDay = -1;
int lastTickMinutes = -1;
@@ -32,6 +33,9 @@ public:
// Called when manual override is toggled (via shortcut or system change).
void OnManualOverride();
// Called when night light changes in windows settings
void OnNightLightChange();
// Initial sync at startup to align internal state with system theme
void SyncInitialThemeState();

View File

@@ -0,0 +1 @@
#include "NightLightRegistryObserver.h"

View File

@@ -0,0 +1,134 @@
#pragma once
#include <wtypes.h>
#include <string>
#include <functional>
#include <thread>
#include <atomic>
#include <mutex>
class NightLightRegistryObserver
{
public:
NightLightRegistryObserver(HKEY root, const std::wstring& subkey, std::function<void()> callback) :
_root(root), _subkey(subkey), _callback(std::move(callback)), _stop(false)
{
_thread = std::thread([this]() { this->Run(); });
}
~NightLightRegistryObserver()
{
Stop();
}
void Stop()
{
_stop = true;
{
std::lock_guard<std::mutex> lock(_mutex);
if (_event)
SetEvent(_event);
}
if (_thread.joinable())
_thread.join();
std::lock_guard<std::mutex> lock(_mutex);
if (_hKey)
{
RegCloseKey(_hKey);
_hKey = nullptr;
}
if (_event)
{
CloseHandle(_event);
_event = nullptr;
}
}
private:
void Run()
{
{
std::lock_guard<std::mutex> lock(_mutex);
if (RegOpenKeyExW(_root, _subkey.c_str(), 0, KEY_NOTIFY, &_hKey) != ERROR_SUCCESS)
return;
_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
if (!_event)
{
RegCloseKey(_hKey);
_hKey = nullptr;
return;
}
}
while (!_stop)
{
HKEY hKeyLocal = nullptr;
HANDLE eventLocal = nullptr;
{
std::lock_guard<std::mutex> lock(_mutex);
if (_stop)
break;
hKeyLocal = _hKey;
eventLocal = _event;
}
if (!hKeyLocal || !eventLocal)
break;
if (_stop)
break;
if (RegNotifyChangeKeyValue(hKeyLocal, FALSE, REG_NOTIFY_CHANGE_LAST_SET, eventLocal, TRUE) != ERROR_SUCCESS)
break;
DWORD wait = WaitForSingleObject(eventLocal, INFINITE);
if (_stop || wait == WAIT_FAILED)
break;
ResetEvent(eventLocal);
if (!_stop && _callback)
{
try
{
_callback();
}
catch (...)
{
}
}
}
{
std::lock_guard<std::mutex> lock(_mutex);
if (_hKey)
{
RegCloseKey(_hKey);
_hKey = nullptr;
}
if (_event)
{
CloseHandle(_event);
_event = nullptr;
}
}
}
HKEY _root;
std::wstring _subkey;
std::function<void()> _callback;
HANDLE _event = nullptr;
HKEY _hKey = nullptr;
std::thread _thread;
std::atomic<bool> _stop;
std::mutex _mutex;
};

View File

@@ -11,4 +11,7 @@ enum class SettingId
Sunset_Offset,
ChangeSystem,
ChangeApps
};
};
constexpr wchar_t PERSONALIZATION_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
constexpr wchar_t NIGHT_LIGHT_REGISTRY_PATH[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\CloudStore\\Store\\DefaultAccount\\Current\\default$windows.data.bluelightreduction.bluelightreductionstate\\windows.data.bluelightreduction.bluelightreductionstate";

View File

@@ -3,6 +3,7 @@
#include <logger/logger.h>
#include <utils/logger_helper.h>
#include "ThemeHelper.h"
#include <SettingsConstants.h>
// Controls changing the themes.
@@ -10,7 +11,7 @@ static void ResetColorPrevalence()
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
@@ -31,7 +32,7 @@ void SetAppsTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
@@ -50,7 +51,7 @@ void SetSystemTheme(bool mode)
{
HKEY hKey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_SET_VALUE,
&hKey) == ERROR_SUCCESS)
@@ -79,7 +80,7 @@ bool GetCurrentSystemTheme()
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
@@ -98,7 +99,7 @@ bool GetCurrentAppsTheme()
DWORD size = sizeof(value);
if (RegOpenKeyEx(HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
PERSONALIZATION_REGISTRY_PATH,
0,
KEY_READ,
&hKey) == ERROR_SUCCESS)
@@ -109,3 +110,30 @@ bool GetCurrentAppsTheme()
return value == 1; // true = light, false = dark
}
bool IsNightLightEnabled()
{
HKEY hKey;
const wchar_t* path = NIGHT_LIGHT_REGISTRY_PATH;
if (RegOpenKeyExW(HKEY_CURRENT_USER, path, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return false;
// RegGetValueW will set size to the size of the data and we expect that to be at least 25 bytes (we need to access bytes 23 and 24)
DWORD size = 0;
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, nullptr, &size) != ERROR_SUCCESS || size < 25)
{
RegCloseKey(hKey);
return false;
}
std::vector<BYTE> data(size);
if (RegGetValueW(hKey, nullptr, L"Data", RRF_RT_REG_BINARY, nullptr, data.data(), &size) != ERROR_SUCCESS)
{
RegCloseKey(hKey);
return false;
}
RegCloseKey(hKey);
return data[23] == 0x10 && data[24] == 0x00;
}

View File

@@ -3,3 +3,4 @@ void SetSystemTheme(bool dark);
void SetAppsTheme(bool dark);
bool GetCurrentSystemTheme();
bool GetCurrentAppsTheme();
bool IsNightLightEnabled();

View File

@@ -1,16 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="NuGet">
<!-- Tell NuGet this is PackageReference style -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- Tell NuGet we're a native project -->
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
</PropertyGroup>
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -33,11 +31,6 @@
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.ImplementationLibrary" GeneratePathProperty="true" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -45,6 +38,7 @@
<DesktopCompatible>true</DesktopCompatible>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
@@ -124,6 +118,9 @@
<WarnAsError>true</WarnAsError>
</Midl>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\Display\Display.vcxproj">
<Project>{caba8dfb-823b-4bf2-93ac-3f31984150d9}</Project>
@@ -145,5 +142,42 @@
<ResourceCompile Include="PowerToys.MeasureToolCore.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools" version="10.0.26100.4188" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

View File

@@ -73,13 +73,6 @@
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<BuildProject>true</BuildProject>
</ProjectReference>
<CsWinRTInputs Include="$(OutputPath)\PowerToys.MeasureToolCore.winmd" />
<None Include="$(OutputPath)\PowerToys.MeasureToolCore.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<ProjectReference Include="..\MeasureToolCore\PowerToys.MeasureToolCore.vcxproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="NuGet">
<!-- Tell NuGet this is PackageReference style -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- Tell NuGet we're a native project -->
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
</PropertyGroup>
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{e94fd11c-0591-456f-899f-efc0ca548336}</ProjectGuid>
@@ -23,12 +20,9 @@
<WindowsAppSdkBootstrapInitialize>false</WindowsAppSdkBootstrapInitialize>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
<!-- Force NuGet to treat this project strictly as packages.config style -->
<RestoreProjectStyle>packages.config</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true"/>
<PackageReference Include="Microsoft.WindowsAppSDK.Foundation" GeneratePathProperty="true"/>
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true"/>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
@@ -133,18 +127,18 @@
<ItemGroup>
<ResourceCompile Include="FindMyMouse.rc" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<!-- Deduplicate WindowsAppRuntimeAutoInitializer.cpp (added twice via transitive imports causing LNK4042). Remove all then add exactly once. -->
<Target Name="FixWinAppSDKAutoInitializer" BeforeTargets="ClCompile" AfterTargets="WindowsAppRuntimeAutoInitializer">
<ItemGroup>
<!-- Remove ALL injected versions of the file -->
<ClCompile Remove="@(ClCompile)" Condition="'%(Filename)' == 'WindowsAppRuntimeAutoInitializer'" />
<!-- Add ONE copy back manually -->
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK_Foundation)\include\WindowsAppRuntimeAutoInitializer.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
</Target>
<ItemGroup Condition="'$(PkgMicrosoft_WindowsAppSDK)'!=''">
<!-- Remove any transitive inclusion first -->
<ClCompile Remove="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp" />
<!-- Re-add once, but disable PCH because the SDK file doesn't include our pch.h -->
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK)\include\WindowsAppRuntimeAutoInitializer.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<Target Name="RemoveManagedWebView2CoreFromNativeOutDir" AfterTargets="Build">
<ItemGroup>
<_ToDelete Include="$(OutDir)Microsoft.Web.WebView2.Core.dll" />
@@ -154,4 +148,38 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Windows.CppWinRT.2.0.240111.5\\build\\native\\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.Web.WebView2.1.0.2903.40\\build\\native\\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Base.1.8.250831001\\build\\native\\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\\build\\native\\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\\build\\native\\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\\build\\native\\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\\build\\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\\..\\..\\..\\packages\\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\\build\\native\\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
</packages>

View File

@@ -617,6 +617,8 @@ namespace MouseUtils.UITests
private void LaunchFromSetting(bool reload = false, bool launchAsAdmin = false)
{
Session = RestartScopeExe("FindMyMouse,MouseHighlighter,MouseJump,MousePointerCrosshairs,CursorWrap");
// this.Session.Attach(PowerToysModule.PowerToysSettings);
this.Session.SetMainWindowSize(WindowSize.Large);

View File

@@ -19,6 +19,8 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
public string Body { get; private set; } = string.Empty;
public ContentSize? Size { get; private set; } = ContentSize.Small;
// Metadata is an array of IDetailsElement,
// where IDetailsElement = {IDetailsTags, IDetailsLink, IDetailsSeparator}
public List<DetailsElementViewModel> Metadata { get; private set; } = [];
@@ -40,6 +42,21 @@ public partial class DetailsViewModel(IDetails _details, WeakReference<IPageCont
UpdateProperty(nameof(Body));
UpdateProperty(nameof(HeroImage));
if (model is IExtendedAttributesProvider provider)
{
if (provider.GetProperties()?.TryGetValue("Size", out var rawValue) == true)
{
if (rawValue is int sizeAsInt)
{
Size = (ContentSize)sizeAsInt;
}
}
}
Size ??= ContentSize.Small;
UpdateProperty(nameof(Size));
var meta = model.Metadata;
if (meta is not null)
{

View File

@@ -24,6 +24,8 @@ public partial class ListItemViewModel : CommandItemViewModel
public string Section { get; private set; } = string.Empty;
public bool IsSectionOrSeparator { get; private set; }
public DetailsViewModel? Details { get; private set; }
[MemberNotNullWhen(true, nameof(Details))]
@@ -82,14 +84,18 @@ public partial class ListItemViewModel : CommandItemViewModel
}
UpdateTags(li.Tags);
Section = li.Section ?? string.Empty;
UpdateProperty(nameof(Section));
IsSectionOrSeparator = IsSeparator(li);
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
UpdateAccessibleName();
}
private bool IsSeparator(IListItem item)
{
return item.Command is null;
}
public override void SlowInitializeProperties()
{
base.SlowInitializeProperties();
@@ -104,8 +110,7 @@ public partial class ListItemViewModel : CommandItemViewModel
{
Details = new(extensionDetails, PageContext);
Details.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
UpdateProperty(nameof(Details), nameof(HasDetails));
}
AddShowDetailsCommands();
@@ -135,14 +140,18 @@ public partial class ListItemViewModel : CommandItemViewModel
break;
case nameof(model.Section):
Section = model.Section ?? string.Empty;
UpdateProperty(nameof(Section));
IsSectionOrSeparator = IsSeparator(model);
UpdateProperty(nameof(Section), nameof(IsSectionOrSeparator));
break;
case nameof(model.Details):
case nameof(model.Command):
IsSectionOrSeparator = IsSeparator(model);
UpdateProperty(nameof(IsSectionOrSeparator));
break;
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties();
UpdateProperty(nameof(Details));
UpdateProperty(nameof(HasDetails));
UpdateProperty(nameof(Details), nameof(HasDetails));
UpdateShowDetailsCommand();
break;
case nameof(model.MoreCommands):
@@ -194,8 +203,7 @@ public partial class ListItemViewModel : CommandItemViewModel
MoreCommands.Add(showDetailsContextItemViewModel);
}
UpdateProperty(nameof(MoreCommands));
UpdateProperty(nameof(AllCommands));
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
}
}
@@ -227,8 +235,7 @@ public partial class ListItemViewModel : CommandItemViewModel
showDetailsContextItemViewModel.SlowInitializeProperties();
MoreCommands.Add(showDetailsContextItemViewModel);
UpdateProperty(nameof(MoreCommands));
UpdateProperty(nameof(AllCommands));
UpdateProperty(nameof(MoreCommands), nameof(AllCommands));
}
}

View File

@@ -3,13 +3,14 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public partial class SeparatorViewModel() :
CommandItem,
IContextItemViewModel,
IFilterItemViewModel,
ISeparatorContextItem,

View File

@@ -6,12 +6,12 @@
<!-- For MVVM Toolkit Partial Properties/AOT support -->
<LangVersion>preview</LangVersion>
<OutputPath>..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutputPath>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>$(RootNamespace).pri</ProjectPriFileName>
<!-- Disable SA1313 for Primary Constructor fields conflict https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors -->
<NoWarn>SA1313;</NoWarn>
@@ -42,5 +42,5 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -24,7 +24,7 @@
<ItemGroup>
<!-- Images -->
<Content Include=".\Assets\$(CmdPalAssetSuffix)\**\*">
<Content Include="$(SolutionDir)\src\modules\cmdpal\Microsoft.CmdPal.UI\Assets\$(CmdPalAssetSuffix)\**\*">
<DeploymentContent>true</DeploymentContent>
<Link>Assets\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Content>
@@ -35,10 +35,14 @@
<!-- In the future, when we actually want to support "preview" and "canary",
add a Package-Pre.appxmanifest, etc. -->
<AppxManifest Include="Package.appxmanifest" Condition="'$(CommandPaletteBranding)'=='Release'" />
<AppxManifest Include="Package.appxmanifest" Condition="'$(CommandPaletteBranding)'=='Preview'" />
<AppxManifest Include="Package.appxmanifest" Condition="'$(CommandPaletteBranding)'=='Canary'" />
<AppxManifest Include="Package-Dev.appxmanifest" Condition="'$(CommandPaletteBranding)'=='' or '$(CommandPaletteBranding)'=='Dev'" />
<AppxManifest Include="Package.appxmanifest"
Condition="'$(CommandPaletteBranding)'=='Release'" />
<AppxManifest Include="Package.appxmanifest"
Condition="'$(CommandPaletteBranding)'=='Preview'" />
<AppxManifest Include="Package.appxmanifest"
Condition="'$(CommandPaletteBranding)'=='Canary'" />
<AppxManifest Include="Package-Dev.appxmanifest"
Condition="'$(CommandPaletteBranding)'=='' or '$(CommandPaletteBranding)'=='Dev'" />
</ItemGroup>
</Project>

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<PropertyGroup>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<!-- Reset this because the Versioning task might have overwritten it before it knew about OutDir -->
<AppxPackageDir>$(OutputPath)\AppPackages\</AppxPackageDir>
</PropertyGroup>

View File

@@ -0,0 +1,37 @@
// 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 Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls;
internal sealed class UVBounds
{
public double UMin { get; }
public double UMax { get; }
public double VMin { get; }
public double VMax { get; }
public UVBounds(Orientation orientation, Rect rect)
{
if (orientation == Orientation.Horizontal)
{
UMin = rect.Left;
UMax = rect.Right;
VMin = rect.Top;
VMax = rect.Bottom;
}
else
{
UMin = rect.Top;
UMax = rect.Bottom;
VMin = rect.Left;
VMax = rect.Right;
}
}
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls;
[DebuggerDisplay("U = {U} V = {V}")]
internal struct UvMeasure
{
internal double U { get; set; }
internal double V { get; set; }
internal static UvMeasure Zero => default(UvMeasure);
public UvMeasure(Orientation orientation, Size size)
: this(orientation, size.Width, size.Height)
{
}
public UvMeasure(Orientation orientation, double width, double height)
{
if (orientation == Orientation.Horizontal)
{
U = width;
V = height;
}
else
{
U = height;
V = width;
}
}
public UvMeasure Add(double u, double v)
{
UvMeasure result = default(UvMeasure);
result.U = U + u;
result.V = V + v;
return result;
}
public UvMeasure Add(UvMeasure measure)
{
return Add(measure.U, measure.V);
}
public Size ToSize(Orientation orientation)
{
if (orientation != Orientation.Horizontal)
{
return new Size(V, U);
}
return new Size(U, V);
}
public Point GetPoint(Orientation orientation)
{
return orientation is Orientation.Horizontal ? new Point(U, V) : new Point(V, U);
}
public Size GetSize(Orientation orientation)
{
return orientation is Orientation.Horizontal ? new Size(U, V) : new Size(V, U);
}
public static bool operator ==(UvMeasure measure1, UvMeasure measure2)
{
return measure1.U == measure2.U && measure1.V == measure2.V;
}
public static bool operator !=(UvMeasure measure1, UvMeasure measure2)
{
return !(measure1 == measure2);
}
public override bool Equals(object? obj)
{
return obj is UvMeasure measure && this == measure;
}
public bool Equals(UvMeasure value)
{
return this == value;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}

View File

@@ -0,0 +1,416 @@
// 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 CommunityToolkit.WinUI.Controls;
using Microsoft.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
namespace Microsoft.CmdPal.UI.Controls;
/// <summary>
/// Arranges elements by wrapping them to fit the available space.
/// When <see cref="Orientation"/> is set to Orientation.Horizontal, element are arranged in rows until the available width is reached and then to a new row.
/// When <see cref="Orientation"/> is set to Orientation.Vertical, element are arranged in columns until the available height is reached.
/// </summary>
public sealed partial class WrapPanel : Panel
{
private struct UvRect
{
public UvMeasure Position { get; set; }
public UvMeasure Size { get; set; }
public Rect ToRect(Orientation orientation)
{
return orientation switch
{
Orientation.Vertical => new Rect(Position.V, Position.U, Size.V, Size.U),
Orientation.Horizontal => new Rect(Position.U, Position.V, Size.U, Size.V),
_ => ThrowArgumentException(),
};
}
private static Rect ThrowArgumentException()
{
throw new ArgumentException("The input orientation is not valid.");
}
}
private struct Row
{
public List<UvRect> ChildrenRects { get; }
public UvMeasure Size { get; set; }
public UvRect Rect
{
get
{
UvRect result;
if (ChildrenRects.Count <= 0)
{
result = default(UvRect);
result.Position = UvMeasure.Zero;
result.Size = Size;
return result;
}
result = default(UvRect);
result.Position = ChildrenRects.First().Position;
result.Size = Size;
return result;
}
}
public Row(List<UvRect> childrenRects, UvMeasure size)
{
ChildrenRects = childrenRects;
Size = size;
}
public void Add(UvMeasure position, UvMeasure size)
{
ChildrenRects.Add(new UvRect
{
Position = position,
Size = size,
});
Size = new UvMeasure
{
U = position.U + size.U,
V = Math.Max(Size.V, size.V),
};
}
}
/// <summary>
/// Gets or sets a uniform Horizontal distance (in pixels) between items when <see cref="Orientation"/> is set to Horizontal,
/// or between columns of items when <see cref="Orientation"/> is set to Vertical.
/// </summary>
public double HorizontalSpacing
{
get { return (double)GetValue(HorizontalSpacingProperty); }
set { SetValue(HorizontalSpacingProperty, value); }
}
private bool IsSectionItem(UIElement element) => element is FrameworkElement fe && fe.DataContext is ListItemViewModel item && item.IsSectionOrSeparator;
/// <summary>
/// Identifies the <see cref="HorizontalSpacing"/> dependency property.
/// </summary>
public static readonly DependencyProperty HorizontalSpacingProperty =
DependencyProperty.Register(
nameof(HorizontalSpacing),
typeof(double),
typeof(WrapPanel),
new PropertyMetadata(0d, LayoutPropertyChanged));
/// <summary>
/// Gets or sets a uniform Vertical distance (in pixels) between items when <see cref="Orientation"/> is set to Vertical,
/// or between rows of items when <see cref="Orientation"/> is set to Horizontal.
/// </summary>
public double VerticalSpacing
{
get { return (double)GetValue(VerticalSpacingProperty); }
set { SetValue(VerticalSpacingProperty, value); }
}
/// <summary>
/// Identifies the <see cref="VerticalSpacing"/> dependency property.
/// </summary>
public static readonly DependencyProperty VerticalSpacingProperty =
DependencyProperty.Register(
nameof(VerticalSpacing),
typeof(double),
typeof(WrapPanel),
new PropertyMetadata(0d, LayoutPropertyChanged));
/// <summary>
/// Gets or sets the orientation of the WrapPanel.
/// Horizontal means that child controls will be added horizontally until the width of the panel is reached, then a new row is added to add new child controls.
/// Vertical means that children will be added vertically until the height of the panel is reached, then a new column is added.
/// </summary>
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Identifies the <see cref="Orientation"/> dependency property.
/// </summary>
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(
nameof(Orientation),
typeof(Orientation),
typeof(WrapPanel),
new PropertyMetadata(Orientation.Horizontal, LayoutPropertyChanged));
/// <summary>
/// Gets or sets the distance between the border and its child object.
/// </summary>
/// <returns>
/// The dimensions of the space between the border and its child as a Thickness value.
/// Thickness is a structure that stores dimension values using pixel measures.
/// </returns>
public Thickness Padding
{
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
/// <summary>
/// Identifies the Padding dependency property.
/// </summary>
/// <returns>The identifier for the <see cref="Padding"/> dependency property.</returns>
public static readonly DependencyProperty PaddingProperty =
DependencyProperty.Register(
nameof(Padding),
typeof(Thickness),
typeof(WrapPanel),
new PropertyMetadata(default(Thickness), LayoutPropertyChanged));
/// <summary>
/// Gets or sets a value indicating how to arrange child items
/// </summary>
public StretchChild StretchChild
{
get { return (StretchChild)GetValue(StretchChildProperty); }
set { SetValue(StretchChildProperty, value); }
}
/// <summary>
/// Identifies the <see cref="StretchChild"/> dependency property.
/// </summary>
/// <returns>The identifier for the <see cref="StretchChild"/> dependency property.</returns>
public static readonly DependencyProperty StretchChildProperty =
DependencyProperty.Register(
nameof(StretchChild),
typeof(StretchChild),
typeof(WrapPanel),
new PropertyMetadata(StretchChild.None, LayoutPropertyChanged));
/// <summary>
/// Identifies the IsFullLine attached dependency property.
/// If true, the child element will occupy the entire width of the panel and force a line break before and after itself.
/// </summary>
public static readonly DependencyProperty IsFullLineProperty =
DependencyProperty.RegisterAttached(
"IsFullLine",
typeof(bool),
typeof(WrapPanel),
new PropertyMetadata(false, OnIsFullLineChanged));
public static bool GetIsFullLine(DependencyObject obj)
{
return (bool)obj.GetValue(IsFullLineProperty);
}
public static void SetIsFullLine(DependencyObject obj, bool value)
{
obj.SetValue(IsFullLineProperty, value);
}
private static void OnIsFullLineChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (FindVisualParentWrapPanel(d) is WrapPanel wp)
{
wp.InvalidateMeasure();
}
}
private static WrapPanel? FindVisualParentWrapPanel(DependencyObject child)
{
var parent = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(child);
while (parent != null)
{
if (parent is WrapPanel wrapPanel)
{
return wrapPanel;
}
parent = Microsoft.UI.Xaml.Media.VisualTreeHelper.GetParent(parent);
}
return null;
}
private static void LayoutPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WrapPanel wp)
{
wp.InvalidateMeasure();
wp.InvalidateArrange();
}
}
private readonly List<Row> _rows = new List<Row>();
/// <inheritdoc />
protected override Size MeasureOverride(Size availableSize)
{
var childAvailableSize = new Size(
availableSize.Width - Padding.Left - Padding.Right,
availableSize.Height - Padding.Top - Padding.Bottom);
foreach (var child in Children)
{
child.Measure(childAvailableSize);
}
var requiredSize = UpdateRows(availableSize);
return requiredSize;
}
/// <inheritdoc />
protected override Size ArrangeOverride(Size finalSize)
{
if ((Orientation == Orientation.Horizontal && finalSize.Width < DesiredSize.Width) ||
(Orientation == Orientation.Vertical && finalSize.Height < DesiredSize.Height))
{
// We haven't received our desired size. We need to refresh the rows.
UpdateRows(finalSize);
}
if (_rows.Count > 0)
{
// Now that we have all the data, we do the actual arrange pass
var childIndex = 0;
foreach (var row in _rows)
{
foreach (var rect in row.ChildrenRects)
{
var child = Children[childIndex++];
while (child.Visibility == Visibility.Collapsed)
{
// Collapsed children are not added into the rows,
// we skip them.
child = Children[childIndex++];
}
var arrangeRect = new UvRect
{
Position = rect.Position,
Size = new UvMeasure { U = rect.Size.U, V = row.Size.V },
};
var finalRect = arrangeRect.ToRect(Orientation);
child.Arrange(finalRect);
}
}
}
return finalSize;
}
private Size UpdateRows(Size availableSize)
{
_rows.Clear();
var paddingStart = new UvMeasure(Orientation, Padding.Left, Padding.Top);
var paddingEnd = new UvMeasure(Orientation, Padding.Right, Padding.Bottom);
if (Children.Count == 0)
{
return paddingStart.Add(paddingEnd).ToSize(Orientation);
}
var parentMeasure = new UvMeasure(Orientation, availableSize.Width, availableSize.Height);
var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing);
var position = new UvMeasure(Orientation, Padding.Left, Padding.Top);
var currentRow = new Row(new List<UvRect>(), default);
var finalMeasure = new UvMeasure(Orientation, width: 0.0, height: 0.0);
void CommitRow()
{
// Only adds if the row has a content
if (currentRow.ChildrenRects.Count > 0)
{
_rows.Add(currentRow);
position.V += currentRow.Size.V + spacingMeasure.V;
}
position.U = paddingStart.U;
currentRow = new Row(new List<UvRect>(), default);
}
void Arrange(UIElement child, bool isLast = false)
{
if (child.Visibility == Visibility.Collapsed)
{
return;
}
var isFullLine = IsSectionItem(child);
var desiredMeasure = new UvMeasure(Orientation, child.DesiredSize);
if (isFullLine)
{
if (currentRow.ChildrenRects.Count > 0)
{
CommitRow();
}
// Forces the width to fill all the available space
// (Total width - Padding Left - Padding Right)
desiredMeasure.U = parentMeasure.U - paddingStart.U - paddingEnd.U;
// Adds the Section Header to the row
currentRow.Add(position, desiredMeasure);
// Updates the global measures
position.U += desiredMeasure.U + spacingMeasure.U;
finalMeasure.U = Math.Max(finalMeasure.U, position.U);
CommitRow();
}
else
{
// Checks if the item can fit in the row
if ((desiredMeasure.U + position.U + paddingEnd.U) > parentMeasure.U)
{
CommitRow();
}
if (isLast)
{
desiredMeasure.U = parentMeasure.U - position.U;
}
currentRow.Add(position, desiredMeasure);
position.U += desiredMeasure.U + spacingMeasure.U;
finalMeasure.U = Math.Max(finalMeasure.U, position.U);
}
}
var lastIndex = Children.Count - 1;
for (var i = 0; i < lastIndex; i++)
{
Arrange(Children[i]);
}
Arrange(Children[lastIndex], StretchChild == StretchChild.Last);
if (currentRow.ChildrenRects.Count > 0)
{
_rows.Add(currentRow);
}
if (_rows.Count == 0)
{
return paddingStart.Add(paddingEnd).ToSize(Orientation);
}
var lastRowRect = _rows.Last().Rect;
finalMeasure.V = lastRowRect.Position.V + lastRowRect.Size.V;
return finalMeasure.Add(paddingEnd).ToSize(Orientation);
}
}

View File

@@ -0,0 +1,39 @@
// 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 Microsoft.CommandPalette.Extensions;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Microsoft.CmdPal.UI;
public partial class DetailsSizeToGridLengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is ContentSize size)
{
// This converter calculates the Star width for the LIST.
//
// The input 'size' (ContentSize) represents the TARGET WIDTH desired for the DETAILS PANEL.
//
// To ensure the Details Panel achieves its target size (e.g. ContentSize.Large),
// we must shrink the List and let the Details fill the available space.
// (e.g., A larger target size for Details results in a smaller Star value for the List).
var starValue = size switch
{
ContentSize.Small => 3.0,
ContentSize.Medium => 2.0,
ContentSize.Large => 1.0,
_ => 3.0,
};
return new GridLength(starValue, GridUnitType.Star);
}
return new GridLength(3.0, GridUnitType.Star);
}
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

View File

@@ -18,8 +18,23 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
public DataTemplate? Gallery { get; set; }
public DataTemplate? Section { get; set; }
public DataTemplate? Separator { get; set; }
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
if (item is ListItemViewModel element && element.IsSectionOrSeparator)
{
if (dependencyObject is UIElement li)
{
li.IsTabStop = false;
li.IsHitTestVisible = false;
}
return string.IsNullOrWhiteSpace(element.Section) ? Separator : Section;
}
return GridProperties switch
{
SmallGridPropertiesViewModel => Small,

View File

@@ -0,0 +1,47 @@
// 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 Microsoft.CmdPal.Core.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI;
public sealed partial class ListItemTemplateSelector : DataTemplateSelector
{
public DataTemplate? ListItem { get; set; }
public DataTemplate? Separator { get; set; }
public DataTemplate? Section { get; set; }
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject container)
{
DataTemplate? dataTemplate = ListItem;
if (container is ListViewItem listItem)
{
if (item is ListItemViewModel element)
{
if (container is ListViewItem li && element.IsSectionOrSeparator)
{
li.IsEnabled = false;
li.AllowFocusWhenDisabled = false;
li.AllowFocusOnInteraction = false;
li.IsHitTestVisible = false;
dataTemplate = string.IsNullOrWhiteSpace(element.Section) ? Separator : Section;
}
else
{
listItem.IsEnabled = true;
listItem.AllowFocusWhenDisabled = true;
listItem.AllowFocusOnInteraction = true;
listItem.IsHitTestVisible = true;
}
}
}
return dataTemplate;
}
}

View File

@@ -28,6 +28,8 @@
<CornerRadius x:Key="MediumGridViewItemCornerRadius">8</CornerRadius>
<Style x:Key="IconGridViewItemStyle" TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
@@ -90,6 +92,8 @@
</Style>
<Style x:Key="GalleryGridViewItemStyle" TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
@@ -168,8 +172,17 @@
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
Medium="{StaticResource MediumGridItemViewModelTemplate}"
Section="{StaticResource ListSectionViewModelTemplate}"
Separator="{StaticResource ListSeparatorViewModelTemplate}"
Small="{StaticResource SmallGridItemViewModelTemplate}" />
<cmdpalUI:ListItemTemplateSelector
x:Key="ListItemTemplateSelector"
x:DataType="coreViewModels:ListItemViewModel"
ListItem="{StaticResource ListItemViewModelTemplate}"
Section="{StaticResource ListSectionViewModelTemplate}"
Separator="{StaticResource ListSeparatorViewModelTemplate}" />
<cmdpalUI:GridItemContainerStyleSelector
x:Key="GridItemContainerStyleSelector"
Gallery="{StaticResource GalleryGridViewItemStyle}"
@@ -241,12 +254,46 @@
</Grid>
</DataTemplate>
<DataTemplate x:Key="ListSeparatorViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle
Grid.Column="1"
Height="1"
Margin="0,2,0,2"
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="ListSectionViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid
Margin="0"
VerticalAlignment="Center"
cpcontrols:WrapPanel.IsFullLine="True"
ColumnSpacing="8"
IsTabStop="False"
IsTapEnabled="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Foreground="{ThemeResource TextFillColorDisabled}"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="{x:Bind Section}" />
<Rectangle
Grid.Column="1"
Height="1"
Fill="{ThemeResource DividerStrokeColorDefaultBrush}" />
</Grid>
</DataTemplate>
<!-- Grid item templates for visual grid representation -->
<DataTemplate x:Key="SmallGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<StackPanel
Width="60"
Height="60"
Padding="8,16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
@@ -265,7 +312,6 @@
Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
</StackPanel>
</DataTemplate>
@@ -399,7 +445,7 @@
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"
ItemTemplate="{StaticResource ListItemViewModelTemplate}"
ItemTemplateSelector="{StaticResource ListItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped"
SelectionChanged="Items_SelectionChanged">
@@ -411,7 +457,7 @@
<controls:Case Value="True">
<GridView
x:Name="ItemsGrid"
Padding="8"
Padding="16,0"
ContextCanceled="Items_OnContextCanceled"
ContextRequested="Items_OnContextRequested"
DoubleTapped="Items_DoubleTapped"
@@ -423,10 +469,14 @@
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped"
SelectionChanged="Items_SelectionChanged">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<cpcontrols:WrapPanel HorizontalSpacing="8" Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemContainerTransitions>
<TransitionCollection />
</GridView.ItemContainerTransitions>
<GridView.ItemContainerStyle />
</GridView>
</controls:Case>
</controls:SwitchPresenter>

View File

@@ -76,12 +76,18 @@ public sealed partial class ListPage : Page,
ViewModel = listViewModel;
if (e.NavigationMode == NavigationMode.Back
|| (e.NavigationMode == NavigationMode.New && ItemView.Items.Count > 0))
if (e.NavigationMode == NavigationMode.Back)
{
// Upon navigating _back_ to this page, immediately select the
// first item in the list
ItemView.SelectedIndex = 0;
// Must dispatch the selection to run at a lower priority; otherwise, GetFirstSelectableIndex
// may return an incorrect index because item containers are not yet rendered.
_ = DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
var firstUsefulIndex = GetFirstSelectableIndex();
if (firstUsefulIndex != -1)
{
ItemView.SelectedIndex = firstUsefulIndex;
}
});
}
// RegisterAll isn't AOT compatible
@@ -128,6 +134,29 @@ public sealed partial class ListPage : Page,
GC.Collect();
}
/// <summary>
/// Finds the index of the first item in the list that is not a separator.
/// Returns -1 if the list is empty or only contains separators.
/// </summary>
private int GetFirstSelectableIndex()
{
var items = ItemView.Items;
if (items is null || items.Count == 0)
{
return -1;
}
for (var i = 0; i < items.Count; i++)
{
if (!IsSeparator(items[i]))
{
return i;
}
}
return -1;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "VS is too aggressive at pruning methods bound in XAML")]
private void Items_ItemClick(object sender, ItemClickEventArgs e)
{
@@ -183,19 +212,33 @@ public sealed partial class ListPage : Page,
// here, then in Page_ItemsUpdated trying to select that cached item if
// it's in the list (otherwise, clear the cache), but that seems
// aggressively BODGY for something that mostly just works today.
if (ItemView.SelectedItem is not null)
if (ItemView.SelectedItem is not null && !IsSeparator(ItemView.SelectedItem))
{
ItemView.ScrollIntoView(ItemView.SelectedItem);
var items = ItemView.Items;
var firstUsefulIndex = GetFirstSelectableIndex();
var shouldScroll = false;
if (e.RemovedItems.Count > 0)
{
shouldScroll = true;
}
else if (ItemView.SelectedIndex > firstUsefulIndex)
{
shouldScroll = true;
}
if (shouldScroll)
{
ItemView.ScrollIntoView(ItemView.SelectedItem);
}
// Automation notification for screen readers
var listViewPeer = Microsoft.UI.Xaml.Automation.Peers.ListViewAutomationPeer.CreatePeerForElement(ItemView);
if (listViewPeer is not null && li is not null)
{
var notificationText = li.Title;
UIHelper.AnnounceActionForAccessibility(
ItemsList,
notificationText,
li.Title,
"CommandPaletteSelectedItemChanged");
}
}
@@ -271,14 +314,7 @@ public sealed partial class ListPage : Page,
else
{
// For list views, use simple linear navigation
if (ItemView.SelectedIndex < ItemView.Items.Count - 1)
{
ItemView.SelectedIndex++;
}
else
{
ItemView.SelectedIndex = 0;
}
NavigateDown();
}
}
@@ -291,15 +327,7 @@ public sealed partial class ListPage : Page,
}
else
{
// For list views, use simple linear navigation
if (ItemView.SelectedIndex > 0)
{
ItemView.SelectedIndex--;
}
else
{
ItemView.SelectedIndex = ItemView.Items.Count - 1;
}
NavigateUp();
}
}
@@ -366,7 +394,10 @@ public sealed partial class ListPage : Page,
if (indexes.Value.CurrentIndex != indexes.Value.TargetIndex)
{
ItemView.SelectedIndex = indexes.Value.TargetIndex;
ItemView.ScrollIntoView(ItemView.SelectedItem);
if (ItemView.SelectedItem is not null)
{
ItemView.ScrollIntoView(ItemView.SelectedItem);
}
}
}
@@ -381,7 +412,10 @@ public sealed partial class ListPage : Page,
if (indexes.Value.CurrentIndex != indexes.Value.TargetIndex)
{
ItemView.SelectedIndex = indexes.Value.TargetIndex;
ItemView.ScrollIntoView(ItemView.SelectedItem);
if (ItemView.SelectedItem is not null)
{
ItemView.ScrollIntoView(ItemView.SelectedItem);
}
}
}
@@ -524,17 +558,65 @@ public sealed partial class ListPage : Page,
// ItemView_SelectionChanged again to give us another chance to change
// the selection from null -> something. Better to just update the
// selection once, at the end of all the updating.
if (ItemView.SelectedItem is null)
// The selection logic must be deferred to the DispatcherQueue
// to ensure the UI has processed the updated ItemsSource binding,
// preventing ItemView.Items from appearing empty/null immediately after update.
_ = DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
ItemView.SelectedIndex = 0;
}
var items = ItemView.Items;
// Always reset the selected item when the top-level list page changes
// its items
if (!sender.IsNested)
{
ItemView.SelectedIndex = 0;
}
// If the list is null or empty, clears the selection and return
if (items is null || items.Count == 0)
{
ItemView.SelectedIndex = -1;
return;
}
// Finds the first item that is not a separator
var firstUsefulIndex = GetFirstSelectableIndex();
// If there is only separators in the list, don't select anything.
if (firstUsefulIndex == -1)
{
ItemView.SelectedIndex = -1;
return;
}
var shouldUpdateSelection = false;
// If it's a top level list update we force the reset to the top useful item
if (!sender.IsNested)
{
shouldUpdateSelection = true;
}
// No current selection or current selection is null
else if (ItemView.SelectedItem is null)
{
shouldUpdateSelection = true;
}
// The current selected item is a separator
else if (IsSeparator(ItemView.SelectedItem))
{
shouldUpdateSelection = true;
}
// The selected item does not exist in the new list
else if (!items.Contains(ItemView.SelectedItem))
{
shouldUpdateSelection = true;
}
if (shouldUpdateSelection)
{
if (firstUsefulIndex != -1)
{
ItemView.SelectedIndex = firstUsefulIndex;
}
}
});
}
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -604,6 +686,11 @@ public sealed partial class ListPage : Page,
continue;
}
if (IsSeparator(ItemView.Items[i]))
{
continue;
}
if (ItemView.ContainerFromIndex(i) is FrameworkElement c && c.ActualWidth > 0 && c.ActualHeight > 0)
{
var p = c.TransformToVisual(ItemView).TransformPoint(new Point(0, 0));
@@ -764,6 +851,102 @@ public sealed partial class ListPage : Page,
}
}
/// <summary>
/// Code stealed from <see cref="Controls.ContextMenu.NavigateUp"/>
/// </summary>
private void NavigateUp()
{
var newIndex = ItemView.SelectedIndex;
if (ItemView.SelectedIndex > 0)
{
newIndex--;
while (
newIndex >= 0 &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex--;
}
if (newIndex < 0)
{
newIndex = ItemView.Items.Count - 1;
while (
newIndex >= 0 &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex--;
}
}
}
else
{
newIndex = ItemView.Items.Count - 1;
}
ItemView.SelectedIndex = newIndex;
}
/// <summary>
/// Code stealed from <see cref="Controls.ContextMenu.NavigateDown"/>
/// </summary>
private void NavigateDown()
{
var newIndex = ItemView.SelectedIndex;
if (ItemView.SelectedIndex == ItemView.Items.Count - 1)
{
newIndex = 0;
while (
newIndex < ItemView.Items.Count &&
IsSeparator(ItemView.Items[newIndex]))
{
newIndex++;
}
if (newIndex >= ItemView.Items.Count)
{
return;
}
}
else
{
newIndex++;
while (
newIndex < ItemView.Items.Count &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex++;
}
if (newIndex >= ItemView.Items.Count)
{
newIndex = 0;
while (
newIndex < ItemView.Items.Count &&
IsSeparator(ItemView.Items[newIndex]) &&
newIndex != ItemView.SelectedIndex)
{
newIndex++;
}
}
}
ItemView.SelectedIndex = newIndex;
}
/// <summary>
/// Code stealed from <see cref="Controls.ContextMenu.IsSeparator(object)"/>
/// </summary>
private bool IsSeparator(object? item) => item is ListItemViewModel li && li.IsSectionOrSeparator;
private enum InputSource
{
None,

View File

@@ -15,7 +15,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<LangVersion>preview</LangVersion>
<LangVersion>preview</LangVersion>
<Version>$(CmdPalVersion)</Version>
@@ -23,7 +23,6 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<UseWinRT>true</UseWinRT>
</PropertyGroup>
<PropertyGroup>
@@ -31,10 +30,10 @@
</PropertyGroup>
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
<!--<PropertyGroup>
<!--<PropertyGroup>
<EnableCmdPalAOT>true</EnableCmdPalAOT>
<GeneratePackageLocally>true</GeneratePackageLocally>
</PropertyGroup>-->
</PropertyGroup>-->
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
<SelfContained>true</SelfContained>
@@ -50,7 +49,7 @@
</PropertyGroup>
<PropertyGroup>
<!-- This lets us actually reference types from Microsoft.Terminal.UI and CmdPalKeyboardService -->
<!-- This lets us actually reference types from Microsoft.Terminal.UI -->
<CsWinRTIncludes>Microsoft.Terminal.UI;CmdPalKeyboardService</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
</PropertyGroup>
@@ -106,7 +105,7 @@
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="WinUIEx" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
@@ -152,16 +151,12 @@
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WinGet\Microsoft.CmdPal.Ext.WinGet.csproj" />
<ProjectReference Include="$(ProjectDir)\..\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj">
<ReferenceOutputAssembly>False</ReferenceOutputAssembly>
<BuildProject>True</BuildProject>
<ProjectReference Include="..\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj">
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
<Private>True</Private>
<CopyLocalSatelliteAssemblies>True</CopyLocalSatelliteAssemblies>
</ProjectReference>
<!-- WinRT metadata reference -->
<CsWinRTInputs Include="$(OutputPath)\Microsoft.Terminal.UI.winmd" />
<!-- Native implementation DLL -->
<None Include="$(OutputPath)\Microsoft.Terminal.UI.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<ProjectReference Include="..\CmdPalKeyboardService\CmdPalKeyboardService.vcxproj">
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
<Private>True</Private>

View File

@@ -26,6 +26,7 @@
EmptyValue="Collapsed"
NotEmptyValue="Visible" />
<cmdpalUI:DetailsSizeToGridLengthConverter x:Key="SizeToWidthConverter" />
<cmdpalUI:MessageStateToSeverityConverter x:Key="MessageStateToSeverityConverter" />
<cmdpalUI:DetailsDataTemplateSelector
@@ -370,7 +371,7 @@
<Grid x:Name="ContentGrid" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="{x:Bind ViewModel.Details.Size, Mode=OneWay, Converter={StaticResource SizeToWidthConverter}}" />
<ColumnDefinition x:Name="DetailsColumn" Width="Auto" />
</Grid.ColumnDefinitions>

View File

@@ -105,6 +105,7 @@ public sealed partial class SettingsWindow : WindowEx,
"Extensions" => typeof(ExtensionsPage),
_ => null,
};
if (pageType is not null)
{
NavFrame.Navigate(pageType);

View File

@@ -12,9 +12,9 @@ namespace winrt::Microsoft::Terminal::UI::implementation
// Check if the code point is in the Private Use Area range used by Fluent UI icons.
[[nodiscard]] constexpr bool _isFluentIconPua(const UChar32 cp) noexcept
{
constexpr UChar32 fluentIconsPrivateUseAreaStart = 0xE700;
constexpr UChar32 fluentIconsPrivateUseAreaEnd = 0xF8FF;
return cp >= fluentIconsPrivateUseAreaStart && cp <= fluentIconsPrivateUseAreaEnd;
static constexpr UChar32 _fluentIconsPrivateUseAreaStart = 0xE700;
static constexpr UChar32 _fluentIconsPrivateUseAreaEnd = 0xF8FF;
return cp >= _fluentIconsPrivateUseAreaStart && cp <= _fluentIconsPrivateUseAreaEnd;
}
// Determine if the given text (as a sequence of UChar code units) is emoji

View File

@@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="NuGet">
<!-- Tell NuGet this is PackageReference style -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- Tell NuGet we're a native project -->
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
</PropertyGroup>
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup>
<PathToRoot>..\..\..\..\</PathToRoot>
<WasdkNuget>$(PathToRoot)packages\Microsoft.WindowsAppSDK.1.8.250907003</WasdkNuget>
</PropertyGroup>
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -27,11 +28,6 @@
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.26100.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.19041.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.ImplementationLibrary" GeneratePathProperty="true" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
@@ -51,6 +47,10 @@
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
@@ -200,11 +200,43 @@
<Midl Include="FontIconGlyphClassifier.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="Microsoft.Terminal.UI.def" />
</ItemGroup>
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(WasdkNuget)\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', 'Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- The packages.config acts as the global version for all of the NuGet packages contained within. -->
<packages>
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Windows.SDK.BuildTools.MSIX" version="1.7.20250829.1" targetFramework="native" />
</packages>

View File

@@ -5,7 +5,6 @@
using System;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.UI.Xaml;
namespace SamplePagesExtension;
@@ -23,14 +22,34 @@ internal sealed partial class SampleListPageWithDetails : ListPage
return [
new ListItem(new NoOpCommand())
{
Title = "This page demonstrates Details on ListItems",
Title = "Details on ListItems (Small)",
Details = new Details()
{
Title = "List Item 1",
Title = "This item has default details size",
Body = "Each of these items can have a `Body` formatted with **Markdown**",
},
},
new ListItem(new NoOpCommand())
{
Title = "Details on ListItems (Medium)",
Details = new Details()
{
Title = "This item has medium details size",
Body = "Each of these items can have a `Body` formatted with **Markdown**",
Size = ContentSize.Medium,
},
},
new ListItem(new NoOpCommand())
{
Title = "Details on ListItems (Large)",
Details = new Details()
{
Title = "This item has large details size",
Body = "Each of these items can have a `Body` formatted with **Markdown**",
Size = ContentSize.Large,
},
},
new ListItem(new NoOpCommand())
{
Title = "This one has a subtitle too",
Subtitle = "Example Subtitle",
@@ -70,11 +89,13 @@ internal sealed partial class SampleListPageWithDetails : ListPage
new ListItem(new NoOpCommand())
{
Title = "This one has metadata",
Subtitle = "And Large Details panel",
Tags = [],
Details = new Details()
{
Title = "Metadata Example",
Body = "Each of the sections below is some sample metadata",
Size = ContentSize.Large,
Metadata = [
new DetailsElement()
{

View File

@@ -0,0 +1,114 @@
// 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 Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace SamplePagesExtension.Pages.SectionsPages;
internal sealed partial class SampleListPageWithSections : ListPage
{
public SampleListPageWithSections()
{
Icon = new IconInfo("\uE7C5");
Name = "Sample Gallery List Page";
}
public SampleListPageWithSections(IGridProperties gridProperties)
{
Icon = new IconInfo("\uE7C5");
Name = "Sample Gallery List Page";
GridProperties = gridProperties;
}
public override IListItem[] GetItems()
{
var sectionList = new Section("This is a section list", [
new ListItem(new NoOpCommand())
{
Title = "Sample Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
},
]);
var anotherSectionList = new Section("This is another section list", [
new ListItem(new NoOpCommand())
{
Title = "Another Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
},
new ListItem(new NoOpCommand())
{
Title = "More Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Stop With The Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
]);
var yesTheresAnother = new Section("There's another", [
new ListItem(new NoOpCommand())
{
Title = "Sample Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Another Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new NoOpCommand())
{
Title = "More Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Stop With The Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/RedRectangle.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Another Title",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Space.png"),
},
new ListItem(new NoOpCommand())
{
Title = "More Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new NoOpCommand())
{
Title = "Stop With The Titles",
Subtitle = "I don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
]);
return [
..sectionList,
..anotherSectionList,
new Separator(),
new ListItem(new NoOpCommand())
{
Title = "Separators also work",
Subtitle = "But I still don't do anything",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
..yesTheresAnother
];
}
}

View File

@@ -0,0 +1,40 @@
// 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 Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using SamplePagesExtension.Pages.SectionsPages;
namespace SamplePagesExtension.Pages;
internal sealed partial class SectionsIndexPage : ListPage
{
public SectionsIndexPage()
{
Name = "Sections Index Page";
Icon = new IconInfo("\uF168");
}
public override IListItem[] GetItems()
{
return [
new ListItem(new SampleListPageWithSections())
{
Title = "A list page with sections",
},
new ListItem(new SampleListPageWithSections(new SmallGridLayout()))
{
Title = "A small grid page with sections",
},
new ListItem(new SampleListPageWithSections(new MediumGridLayout()))
{
Title = "A medium grid page with sections",
},
new ListItem(new SampleListPageWithSections(new GalleryGridLayout()))
{
Title = "A Gallery grid page with sections",
},
];
}
}

View File

@@ -24,6 +24,11 @@ public partial class SamplesListPage : ListPage
Title = "List Page With Details",
Subtitle = "A list of items, each with additional details to display",
},
new ListItem(new SectionsIndexPage())
{
Title = "List Pages With Sections",
Subtitle = "A list of items, with sections header",
},
new ListItem(new SampleUpdatingItemsPage())
{
Title = "List page with items that change",

View File

@@ -1,10 +1,11 @@
// 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 Windows.Foundation.Collections;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Details : BaseObservable, IDetails
public partial class Details : BaseObservable, IDetails, IExtendedAttributesProvider
{
public virtual IIconInfo HeroImage
{
@@ -53,4 +54,21 @@ public partial class Details : BaseObservable, IDetails
}
= [];
public virtual ContentSize Size
{
get;
set
{
field = value;
OnPropertyChanged(nameof(Size));
}
}
= ContentSize.Small;
public IDictionary<string, object>? GetProperties() => new ValueSet()
{
{ "Size", (int)Size },
};
}

View File

@@ -0,0 +1,39 @@
// 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;
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public sealed partial class Section : IEnumerable<IListItem>
{
public IListItem[] Items { get; set; } = [];
public string SectionTitle { get; set; } = string.Empty;
private Separator CreateSectionListItem()
{
return new Separator(SectionTitle);
}
public Section(string sectionName, IListItem[] items)
{
SectionTitle = sectionName;
var listItems = items.ToList();
if (listItems.Count > 0)
{
listItems.Insert(0, CreateSectionListItem());
Items = [.. listItems];
}
}
public Section()
{
}
public IEnumerator<IListItem> GetEnumerator() => Items.ToList().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@@ -4,6 +4,40 @@
namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class Separator : ISeparatorContextItem, ISeparatorFilterItem
public partial class Separator : IListItem, ISeparatorContextItem, ISeparatorFilterItem
{
public Separator(string? title = "")
: base()
{
Section = title ?? string.Empty;
Command = null;
}
public IDetails? Details => null;
public string? Section { get; private set; }
public ITag[]? Tags => null;
public string? TextToSuggest => null;
public ICommand? Command { get; private set; }
public IIconInfo? Icon => null;
public IContextItem[]? MoreCommands => null;
public string? Subtitle => null;
public string? Title
{
get => Section;
set => Section = value;
}
public event Windows.Foundation.TypedEventHandler<object, IPropChangedEventArgs>? PropChanged
{
add { }
remove { }
}
}

View File

@@ -160,6 +160,15 @@ namespace Microsoft.CommandPalette.Extensions
[uuid("6a6dd345-37a3-4a1e-914d-4f658a4d583d")]
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsData {}
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
enum ContentSize
{
Small = 0,
Medium = 1,
Large = 2,
};
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IDetailsElement {
String Key { get; };

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FancyZonesEditor.Models;
@@ -49,19 +50,16 @@ namespace UITests_FancyZones
[TestInitialize]
public void TestInitialize()
{
// ClearOpenWindows
Session.KillAllProcessesByName("PowerToys");
ClearOpenWindows();
// kill all processes related to FancyZones Editor to ensure a clean state
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
AppZoneHistory.DeleteFile();
this.RestartScopeExe();
FancyZonesEditorHelper.Files.Restore();
// Set a custom layout with 1 subzones and clear app zone history
SetupCustomLayouts();
RestartScopeExe("Hosts");
Thread.Sleep(2000);
// Get the current mouse button setting
nonPrimaryMouseButton = SystemInformation.MouseButtonsSwapped ? "Left" : "Right";
@@ -72,99 +70,6 @@ namespace UITests_FancyZones
LaunchFancyZones();
}
/// <summary>
/// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings
/// <list type="bullet">
/// <item>
/// <description>Verifies that holding Shift while dragging shows all zones as expected.</description>
/// </item>
/// </list>
/// </summary>
[TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")]
[TestCategory("FancyZones_Dragging #1")]
public void TestShowZonesOnShiftDuringDrag()
{
string testCaseName = nameof(TestShowZonesOnShiftDuringDrag);
Pane dragElement = Find<Pane>(By.Name("Non Client Input Sink Window")); // element to drag
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
var (initialColor, withShiftColor) = RunDragInteractions(
preAction: () =>
{
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
},
postAction: () =>
{
Session.PressKey(Key.Shift);
Task.Delay(500).Wait();
},
releaseAction: () =>
{
Session.ReleaseKey(Key.Shift);
Task.Delay(5000).Wait(); // Optional: Wait for a moment to ensure window switch
},
testCaseName: testCaseName);
string zoneColorWithoutShift = GetOutWindowPixelColor(30);
Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone display failed.");
Assert.IsTrue(
withShiftColor == inactivateColor || withShiftColor == highlightColor,
$"[{testCaseName}] Zone display failed: withShiftColor was {withShiftColor}, expected {inactivateColor} or {highlightColor}.");
Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] Zone display failed.");
Assert.AreEqual(zoneColorWithoutShift, initialColor, $"[{testCaseName}] Zone deactivated failed.");
dragElement.ReleaseDrag();
Clean();
}
/// <summary>
/// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings
/// <list type="bullet">
/// <item>
/// <description>Verifies that dragging activates zones as expected.</description>
/// </item>
/// </list>
/// </summary>
[TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")]
[TestCategory("FancyZones_Dragging #2")]
public void TestShowZonesOnDragDuringShift()
{
string testCaseName = nameof(TestShowZonesOnDragDuringShift);
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
var (initialColor, withDragColor) = RunDragInteractions(
preAction: () =>
{
dragElement.Drag(offSet.Dx, offSet.Dy);
Session.PressKey(Key.Shift);
},
postAction: () =>
{
dragElement.DragAndHold(0, 0);
Task.Delay(5000).Wait();
},
releaseAction: () =>
{
dragElement.ReleaseDrag();
Session.ReleaseKey(Key.Shift);
},
testCaseName: testCaseName);
Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed.");
Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed.");
// double check by app-zone-history.json
string appZoneHistoryJson = AppZoneHistory.GetData();
string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson);
Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set.");
Clean();
}
/// <summary>
/// Test toggling zones using a non-primary mouse click during window dragging.
/// <list type="bullet">
@@ -178,14 +83,19 @@ namespace UITests_FancyZones
public void TestToggleZonesWithNonPrimaryMouseClick()
{
string testCaseName = nameof(TestToggleZonesWithNonPrimaryMouseClick);
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 300;
int endY = startY + 300;
var (initialColor, withMouseColor) = RunDragInteractions(
preAction: () =>
{
// activate zone
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
Session.MoveMouseTo(startX, startY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
},
postAction: () =>
{
@@ -195,7 +105,7 @@ namespace UITests_FancyZones
},
releaseAction: () =>
{
dragElement.ReleaseDrag();
Session.PerformMouseAction(MouseActionType.LeftUp);
},
testCaseName: testCaseName);
@@ -204,8 +114,6 @@ namespace UITests_FancyZones
// check the zone color is activated
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
Clean();
}
/// <summary>
@@ -221,32 +129,35 @@ namespace UITests_FancyZones
public void TestShowZonesWhenShiftAndMouseOff()
{
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOff);
Pane dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 300;
int endY = startY + 300;
var (initialColor, withShiftColor) = RunDragInteractions(
preAction: () =>
{
// activate zone
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
Session.MoveMouseTo(startX, startY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
},
postAction: () =>
{
// press Shift Key to deactivate zones
Session.PressKey(Key.Shift);
Task.Delay(500).Wait();
Task.Delay(1000).Wait();
},
releaseAction: () =>
{
dragElement.ReleaseDrag();
Session.PerformMouseAction(MouseActionType.LeftUp);
Session.ReleaseKey(Key.Shift);
},
testCaseName: testCaseName);
Assert.AreEqual(highlightColor, initialColor, $"[{testCaseName}] Zone activation failed.");
Assert.AreNotEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone deactivation failed.");
Clean();
}
/// <summary>
@@ -263,12 +174,17 @@ namespace UITests_FancyZones
{
string testCaseName = nameof(TestShowZonesWhenShiftAndMouseOn);
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 300;
int endY = startY + 300;
var (initialColor, withShiftColor) = RunDragInteractions(
preAction: () =>
{
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
Session.MoveMouseTo(startX, startY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
},
postAction: () =>
{
@@ -279,7 +195,7 @@ namespace UITests_FancyZones
},
testCaseName: testCaseName);
Assert.AreEqual(inactivateColor, withShiftColor, $"[{testCaseName}] show zone failed.");
Assert.AreEqual(highlightColor, withShiftColor, $"[{testCaseName}] show zone failed.");
Session.PerformMouseAction(
nonPrimaryMouseButton == "Right" ? MouseActionType.RightClick : MouseActionType.LeftClick);
@@ -288,9 +204,7 @@ namespace UITests_FancyZones
Assert.AreEqual(initialColor, zoneColorWithMouse, $"[{nameof(TestShowZonesWhenShiftAndMouseOff)}] Zone deactivate failed.");
Session.ReleaseKey(Key.Shift);
dragElement.ReleaseDrag();
Clean();
Session.PerformMouseAction(MouseActionType.LeftUp);
}
/// <summary>
@@ -307,8 +221,6 @@ namespace UITests_FancyZones
{
var pixel = GetPixelWhenMakeDraggedWindow();
Assert.AreNotEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOn)}] Window transparency failed.");
Clean();
}
/// <summary>
@@ -325,14 +237,103 @@ namespace UITests_FancyZones
{
var pixel = GetPixelWhenMakeDraggedWindow();
Assert.AreEqual(pixel.PixelInWindow, pixel.TransPixel, $"[{nameof(TestMakeDraggedWindowTransparentOff)}] Window without transparency failed.");
Clean();
}
private void Clean()
/// <summary>
/// Test Use Shift key to activate zones while dragging a window in FancyZones Zone Behaviour Settings
/// <list type="bullet">
/// <item>
/// <description>Verifies that holding Shift while dragging shows all zones as expected.</description>
/// </item>
/// </list>
/// </summary>
[TestMethod("FancyZones.Settings.TestShowZonesOnShiftDuringDrag")]
[TestCategory("FancyZones_Dragging #1")]
public void TestShowZonesOnShiftDuringDrag()
{
// clean app zone history file
AppZoneHistory.DeleteFile();
string testCaseName = nameof(TestShowZonesOnShiftDuringDrag);
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 300;
int endY = startY + 300;
var (initialColor, withShiftColor) = RunDragInteractions(
preAction: () =>
{
Session.MoveMouseTo(startX, startY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
},
postAction: () =>
{
Session.PressKey(Key.Shift);
Task.Delay(500).Wait();
},
releaseAction: () =>
{
Session.ReleaseKey(Key.Shift);
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure window switch
},
testCaseName: testCaseName);
string zoneColorWithoutShift = GetOutWindowPixelColor(30);
Assert.AreNotEqual(initialColor, withShiftColor, $"[{testCaseName}] Zone color did not change; zone activation failed.");
Assert.AreEqual(highlightColor, withShiftColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed.");
Session.PerformMouseAction(MouseActionType.LeftUp);
}
/// <summary>
/// Test dragging a window during Shift key press in FancyZones Zone Behaviour Settings
/// <list type="bullet">
/// <item>
/// <description>Verifies that dragging activates zones as expected.</description>
/// </item>
/// </list>
/// </summary>
[TestMethod("FancyZones.Settings.TestShowZonesOnDragDuringShift")]
[TestCategory("FancyZones_Dragging #2")]
public void TestShowZonesOnDragDuringShift()
{
string testCaseName = nameof(TestShowZonesOnDragDuringShift);
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 300;
int endY = startY + 300;
var (initialColor, withDragColor) = RunDragInteractions(
preAction: () =>
{
Session.PressKey(Key.Shift);
Task.Delay(100).Wait();
},
postAction: () =>
{
Session.MoveMouseTo(startX, startY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
Task.Delay(1000).Wait();
},
releaseAction: () =>
{
Session.PerformMouseAction(MouseActionType.LeftUp);
Session.ReleaseKey(Key.Shift);
Task.Delay(100).Wait();
},
testCaseName: testCaseName);
Assert.AreNotEqual(initialColor, withDragColor, $"[{testCaseName}] Zone color did not change; zone activation failed.");
Assert.AreEqual(highlightColor, withDragColor, $"[{testCaseName}] Zone color did not match the highlight color; activation failed.");
// double check by app-zone-history.json
string appZoneHistoryJson = AppZoneHistory.GetData();
string? zoneNumber = ZoneSwitchHelper.GetZoneIndexSetByAppName(powertoysWindowName, appZoneHistoryJson);
Assert.IsNull(zoneNumber, $"[{testCaseName}] AppZoneHistory layout was unexpectedly set.");
}
// Helper method to ensure the desktop has no open windows by clicking the "Show Desktop" button
@@ -352,7 +353,7 @@ namespace UITests_FancyZones
desktopButtonName = "Show Desktop";
}
this.Find<Microsoft.PowerToys.UITest.Button>(By.Name(desktopButtonName), 5000, true).Click(false, 500, 2000);
this.Find<Microsoft.PowerToys.UITest.Button>(By.Name(desktopButtonName), 5000, true).Click(false, 500, 1000);
}
// Setup custom layout with 1 subzones
@@ -382,6 +383,11 @@ namespace UITests_FancyZones
this.Scroll(6, "Down"); // Pull the settings page up to make sure the settings are visible
ZoneBehaviourSettings(TestContext.TestName);
// Go back and forth to make sure settings applied
this.Find<NavigationViewItem>("Workspaces").Click();
Task.Delay(200).Wait();
this.Find<NavigationViewItem>("FancyZones").Click();
this.Find<Microsoft.PowerToys.UITest.Button>(By.AccessibilityId("LaunchLayoutEditorButton")).Click(false, 500, 10000);
this.Session.Attach(PowerToysModule.FancyZone);
@@ -435,22 +441,26 @@ namespace UITests_FancyZones
// Get the mouse color of the pixel when make dragged window
private (string PixelInWindow, string TransPixel) GetPixelWhenMakeDraggedWindow()
{
var dragElement = Find<Pane>(By.Name("Non Client Input Sink Window"));
var windowRect = Session.GetMainWindowRect();
int startX = windowRect.Left + 70;
int startY = windowRect.Top + 25;
int endX = startX + 100;
int endY = startY + 100;
// maximize the window to make sure get pixel color more accurate
dragElement.DoubleClick();
Session.MoveMouseTo(startX, startY);
var offSet = ZoneSwitchHelper.GetOffset(dragElement, quarterX, quarterY);
// Session.PerformMouseAction(MouseActionType.LeftDoubleClick);
Session.PressKey(Key.Shift);
dragElement.DragAndHold(offSet.Dx, offSet.Dy);
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.MoveMouseTo(endX, endY);
Tuple<int, int> pos = GetMousePosition();
string pixelInWindow = this.GetPixelColorString(pos.Item1, pos.Item2);
Session.ReleaseKey(Key.Shift);
Task.Delay(1000).Wait(); // Optional: Wait for a moment to ensure the window is in position
Task.Delay(1000).Wait();
string transPixel = this.GetPixelColorString(pos.Item1, pos.Item2);
dragElement.ReleaseDrag();
Session.PerformMouseAction(MouseActionType.LeftUp);
return (pixelInWindow, transPixel);
}

View File

@@ -271,7 +271,7 @@ namespace UITests_FancyZones
};
FancyZonesEditorHelper.Files.AppliedLayoutsIOHelper.WriteData(appliedLayouts.Serialize(appliedLayoutsWrapper));
this.RestartScopeExe();
RestartScopeExe("Hosts");
}
[TestMethod("FancyZones.Settings.TestApplyHotKey")]
@@ -598,10 +598,12 @@ namespace UITests_FancyZones
this.TryReaction();
int tries = 24;
Pull(tries, "down"); // Pull the setting page up to make sure the setting is visible
this.Find<ToggleSwitch>("Enable quick layout switch").Toggle(flag);
this.Find<ToggleSwitch>("FancyZonesQuickLayoutSwitch").Toggle(flag);
tries = 24;
Pull(tries, "up");
// Go back and forth to make sure settings applied
this.Find<NavigationViewItem>("Workspaces").Click();
Task.Delay(200).Wait();
this.Find<NavigationViewItem>("FancyZones").Click();
}
private void TryReaction()

View File

@@ -34,7 +34,7 @@ namespace UITests_FancyZones
Session.KillAllProcessesByName("PowerToys.FancyZonesEditor");
AppZoneHistory.DeleteFile();
this.RestartScopeExe();
RestartScopeExe("Hosts");
FancyZonesEditorHelper.Files.Restore();
// Set a custom layout with 1 subzones and clear app zone history
@@ -137,7 +137,7 @@ namespace UITests_FancyZones
Task.Delay(500).Wait(); // Optional: Wait for a moment to ensure window switch
activeWindowTitle = ZoneSwitchHelper.GetActiveWindowTitle();
Assert.AreNotEqual(preWindow, activeWindowTitle);
Assert.AreEqual(postWindow, activeWindowTitle);
Clean(); // close the windows
}
@@ -151,9 +151,23 @@ namespace UITests_FancyZones
var rect = Session.GetMainWindowRect();
var (targetX, targetY) = ZoneSwitchHelper.GetScreenMargins(rect, 4);
var offSet = ZoneSwitchHelper.GetOffset(hostsView, targetX, targetY);
DragWithShift(hostsView, offSet);
// Snap first window (Hosts) to left zone using shift+drag with direct mouse movement
var hostsRect = hostsView.Rect ?? throw new InvalidOperationException("Failed to get hosts window rect");
int hostsStartX = hostsRect.Left + 70;
int hostsStartY = hostsRect.Top + 25;
// For a 2-column layout, left zone is at approximately 1/4 of screen width
int hostsEndX = rect.Left + (3 * (rect.Right - rect.Left) / 4);
int hostsEndY = rect.Top + ((rect.Bottom - rect.Top) / 2);
Session.MoveMouseTo(hostsStartX, hostsStartY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.PressKey(Key.Shift);
Session.MoveMouseTo(hostsEndX, hostsEndY);
Session.PerformMouseAction(MouseActionType.LeftUp);
Session.ReleaseKey(Key.Shift);
Task.Delay(500).Wait(); // Wait for snap to complete
string preWindow = ZoneSwitchHelper.GetActiveWindowTitle();
@@ -163,11 +177,26 @@ namespace UITests_FancyZones
Pane settingsView = Find<Pane>(By.Name("Non Client Input Sink Window"));
settingsView.DoubleClick(); // maximize the window
DragWithShift(settingsView, offSet);
var windowRect = Session.GetMainWindowRect();
var settingsRect = settingsView.Rect ?? throw new InvalidOperationException("Failed to get settings window rect");
int settingsStartX = settingsRect.Left + 70;
int settingsStartY = settingsRect.Top + 25;
// For a 2-column layout, right zone is at approximately 3/4 of screen width
int settingsEndX = windowRect.Left + (3 * (windowRect.Right - windowRect.Left) / 4);
int settingsEndY = windowRect.Top + ((windowRect.Bottom - windowRect.Top) / 2);
Session.MoveMouseTo(settingsStartX, settingsStartY);
Session.PerformMouseAction(MouseActionType.LeftDown);
Session.PressKey(Key.Shift);
Session.MoveMouseTo(settingsEndX, settingsEndY);
Session.PerformMouseAction(MouseActionType.LeftUp);
Session.ReleaseKey(Key.Shift);
Task.Delay(500).Wait(); // Wait for snap to complete
string appZoneHistoryJson = AppZoneHistory.GetData();
string? zoneIndexOfFileWindow = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Hosts.exe", appZoneHistoryJson); // explorer.exe
string? zoneIndexOfFileWindow = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Hosts.exe", appZoneHistoryJson);
string? zoneIndexOfPowertoys = ZoneSwitchHelper.GetZoneIndexSetByAppName("PowerToys.Settings.exe", appZoneHistoryJson);
// check the AppZoneHistory layout is set and in the same zone
@@ -176,16 +205,6 @@ namespace UITests_FancyZones
return (preWindow, powertoysWindowName);
}
private void DragWithShift(Pane settingsView, (int Dx, int Dy) offSet)
{
Session.PressKey(Key.Shift);
settingsView.DragAndHold(offSet.Dx, offSet.Dy);
Task.Delay(1000).Wait(); // Wait for drag to start (optional)
settingsView.ReleaseDrag();
Task.Delay(1000).Wait(); // Wait after drag (optional)
Session.ReleaseKey(Key.Shift);
}
private static readonly CustomLayouts.CustomLayoutListWrapper CustomLayoutsList = new CustomLayouts.CustomLayoutListWrapper
{
CustomLayouts = new List<CustomLayouts.CustomLayoutWrapper>
@@ -253,11 +272,14 @@ namespace UITests_FancyZones
this.Scroll(9, "Down"); // Pull the setting page up to make sure the setting is visible
bool switchWindowEnable = TestContext.TestName == "TestSwitchShortCutDisable" ? false : true;
this.Find<ToggleSwitch>("Switch between windows in the current zone").Toggle(switchWindowEnable);
this.Find<ToggleSwitch>("FancyZonesWindowSwitchingToggle").Toggle(switchWindowEnable);
Task.Delay(500).Wait(); // Wait for the setting to be applied
this.Scroll(9, "Up"); // Pull the setting page down to make sure the setting is visible
this.Find<Button>("Launch layout editor").Click(false, 500, 5000);
// Go back and forth to make sure settings applied
this.Find<NavigationViewItem>("Workspaces").Click();
Task.Delay(200).Wait();
this.Find<NavigationViewItem>("FancyZones").Click();
this.Find<Button>("Open layout editor").Click(false, 500, 5000);
this.Session.Attach(PowerToysModule.FancyZone);
// pipeline machine may have an unstable delays, causing the custom layout to be unavailable as we set. then A retry is required.
@@ -273,7 +295,7 @@ namespace UITests_FancyZones
this.Find<Microsoft.PowerToys.UITest.Button>("Close").Click();
this.Session.Attach(PowerToysModule.PowerToysSettings);
SetupCustomLayouts();
this.Find<Microsoft.PowerToys.UITest.Button>("Launch layout editor").Click(false, 5000, 5000);
this.Find<Microsoft.PowerToys.UITest.Button>("Open layout editor").Click(false, 5000, 5000);
this.Session.Attach(PowerToysModule.FancyZone);
// customLayoutData = FancyZonesEditorHelper.Files.CustomLayoutsIOHelper.GetData();
@@ -301,11 +323,11 @@ namespace UITests_FancyZones
Task.Delay(1000).Wait();
this.Find<ToggleSwitch>("Enable Hosts File Editor").Toggle(true);
this.Find<ToggleSwitch>("Launch as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Open as administrator").Toggle(launchAsAdmin);
this.Find<ToggleSwitch>("Show a warning at startup").Toggle(showWarning);
// launch Hosts File Editor
this.Find<Button>("Launch Hosts File Editor").Click();
this.Find<Button>("Open Hosts File Editor").Click();
Task.Delay(5000).Wait();
}

View File

@@ -54,8 +54,6 @@ namespace Peek.FilePreviewer.Previewers
private bool IsPng() => Item.Extension == ".png";
private bool IsSvg() => Item.Extension == ".svg";
private bool IsQoi() => Item.Extension == ".qoi";
private DispatcherQueue Dispatcher { get; }
@@ -63,7 +61,7 @@ namespace Peek.FilePreviewer.Previewers
private static readonly HashSet<string> _supportedFileTypes =
BitmapDecoder.GetDecoderInformationEnumerator()
.SelectMany(di => di.FileExtensions)
.Union([".svg", ".qoi"])
.Union([".qoi"])
.ToHashSet(StringComparer.OrdinalIgnoreCase);
public static bool IsItemSupported(IFileSystemItem item)
@@ -75,15 +73,7 @@ namespace Peek.FilePreviewer.Previewers
{
cancellationToken.ThrowIfCancellationRequested();
if (IsSvg())
{
var size = await Task.Run(Item.GetSvgSize);
if (size != null)
{
ImageSize = size.Value;
}
}
else if (IsQoi())
if (IsQoi())
{
var size = await Task.Run(Item.GetQoiSize);
if (size != null)
@@ -176,31 +166,16 @@ namespace Peek.FilePreviewer.Previewers
{
cancellationToken.ThrowIfCancellationRequested();
using FileStream stream = ReadHelper.OpenReadOnly(Item.Path);
if (IsSvg())
{
var source = new SvgImageSource();
source.RasterizePixelHeight = ImageSize?.Height ?? 0;
source.RasterizePixelWidth = ImageSize?.Width ?? 0;
var loadStatus = await source.SetSourceAsync(stream.AsRandomAccessStream());
if (loadStatus != SvgImageSourceLoadStatus.Success)
{
Logger.LogError("Error loading SVG: " + loadStatus.ToString());
throw new ImageLoadingException(nameof(source));
}
Preview = source;
}
else if (IsQoi())
if (IsQoi())
{
using FileStream stream = ReadHelper.OpenReadOnly(Item.Path);
using var bitmap = QoiImage.FromStream(stream);
Preview = await BitmapHelper.BitmapToImageSource(bitmap, true, cancellationToken);
}
else
{
using FileStream stream = ReadHelper.OpenReadOnly(Item.Path);
Preview = new BitmapImage();
await ((BitmapImage)Preview).SetSourceAsync(stream.AsRandomAccessStream());
}

View File

@@ -33,6 +33,10 @@ namespace Peek.FilePreviewer.Previewers
// Markdown
".md",
// SVG - using WebView2 for better compatibility with complex SVGs
// (e.g., from Adobe Illustrator, Inkscape)
".svg",
};
[ObservableProperty]
@@ -111,9 +115,10 @@ namespace Peek.FilePreviewer.Previewers
{
bool isHtml = File.Extension == ".html" || File.Extension == ".htm";
bool isMarkdown = File.Extension == ".md";
bool isSvg = File.Extension == ".svg";
bool supportedByMonaco = MonacoHelper.SupportedMonacoFileTypes.Contains(File.Extension);
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown;
bool useMonaco = supportedByMonaco && !isHtml && !isMarkdown && !isSvg;
IsDevFilePreview = supportedByMonaco;
CustomContextMenu = useMonaco;
@@ -128,6 +133,13 @@ namespace Peek.FilePreviewer.Previewers
var raw = await ReadHelper.Read(File.Path.ToString());
Preview = new Uri(MarkdownHelper.PreviewTempFile(raw, File.Path, TempFolderPath.Path));
}
else if (isSvg)
{
// SVG files are rendered directly by WebView2 for better compatibility
// with complex SVGs from Adobe Illustrator, Inkscape, etc.
IsDevFilePreview = false;
Preview = new Uri(File.Path);
}
else
{
// Simple html file to preview. Shouldn't do things like enabling scripts or using a virtual mapped directory.

View File

@@ -124,9 +124,6 @@ public class PeekFilePreviewTests : UITestBase
settings["properties"] = properties;
});
// Disable all modules except Peek in global settings
SettingsConfigHelper.ConfigureGlobalModuleSettings("Peek");
Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled");
}
catch (Exception ex)
@@ -138,6 +135,7 @@ public class PeekFilePreviewTests : UITestBase
[TestInitialize]
public void TestInitialize()
{
RestartScopeExe("Peek");
Session.CloseMainWindow();
SendKeys(Key.Win, Key.M);
}

View File

@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="NuGet">
<!-- Tell NuGet this is PackageReference style -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- Tell NuGet we're a native project -->
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
</PropertyGroup>
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.props')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
@@ -35,17 +37,9 @@
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
<ProjectPriFileName>PowerToys.PowerRename.pri</ProjectPriFileName>
<RuntimeIdentifier>win10-x64;win10-arm64</RuntimeIdentifier>
<WindowsAppSDKVerifyTransitiveDependencies>false</WindowsAppSDKVerifyTransitiveDependencies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.ImplementationLibrary" GeneratePathProperty="true" />
<PackageReference Include="boost" GeneratePathProperty="true" />
<PackageReference Include="boost_regex-vc143" GeneratePathProperty="true" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
@@ -218,10 +212,54 @@
<ResourceCompile Include="PowerRenameUI.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Import Project="..\..\..\..\packages\boost.1.87.0\build\boost.targets" Condition="Exists('..\..\..\..\packages\boost.1.87.0\build\boost.targets')" />
<Import Project="..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets" Condition="Exists('..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.10.0.26100.4188\build\Microsoft.Windows.SDK.BuildTools.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.SDK.BuildTools.MSIX.1.7.20250829.1\build\Microsoft.Windows.SDK.BuildTools.MSIX.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost.1.87.0\build\boost.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost.1.87.0\build\boost.targets'))" />
<Error Condition="!Exists('..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\boost_regex-vc143.1.87.0\build\boost_regex-vc143.targets'))" />
</Target>
<Target Name="AddWildCardItems" AfterTargets="BuildGenerateSources">
<ItemGroup>
<PRIResource Include="@(_WildCardPRIResource)" />
</ItemGroup>
</Target>
</Project>
</Project>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
<!-- Windows App SDK and all transitive dependencies -->
<package id="Microsoft.WindowsAppSDK" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Base" version="1.8.250831001" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Foundation" version="1.8.250906002" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.WinUI" version="1.8.250906003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Runtime" version="1.8.250907003" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.DWrite" version="1.8.25090401" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.InteractiveExperiences" version="1.8.250906004" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.Widgets" version="1.8.250904007" targetFramework="native" />
<package id="Microsoft.WindowsAppSDK.AI" version="1.8.37" targetFramework="native" />
<package id="Microsoft.Web.WebView2" version="1.0.2903.40" targetFramework="native" />
</packages>

View File

@@ -1,34 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted ..\..\tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h runner.base.rc runner.rc" />
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)\tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h runner.base.rc runner.rc" />
</Target>
<PropertyGroup>
<NoWarn>81010002</NoWarn>
</PropertyGroup>
<PropertyGroup Label="NuGet">
<!-- Tell NuGet this is PackageReference style -->
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<!-- Tell NuGet we're a native project -->
<NuGetTargetMoniker>native,Version=v0.0</NuGetTargetMoniker>
<!-- Tell NuGet we target Windows (use your existing WindowsTargetPlatformVersion) -->
<NuGetTargetPlatformIdentifier>Windows</NuGetTargetPlatformIdentifier>
<NuGetTargetPlatformVersion>$(WindowsTargetPlatformVersion)</NuGetTargetPlatformVersion>
</PropertyGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{9412D5C6-2CF2-4FC2-A601-B55508EA9B27}</ProjectGuid>
<RootNamespace>powertoys</RootNamespace>
<ProjectName>runner</ProjectName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.WindowsAppSDK.Foundation" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.CppWinRT" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Windows.ImplementationLibrary" GeneratePathProperty="true" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<Import Project="..\..\deps\expected.props" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" />
<Import Project="..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props" Condition="Exists('..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" />
<Import Project="..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<ImportGroup Label="Shared" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
@@ -38,6 +31,10 @@
<WindowsAppSdkUndockedRegFreeWinRTInitialize>true</WindowsAppSdkUndockedRegFreeWinRTInitialize>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
@@ -143,16 +140,39 @@
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\deps\spdlog.props" />
<!-- Deduplicate WindowsAppRuntimeAutoInitializer.cpp (added twice via transitive imports causing LNK4042). Remove all then add exactly once. -->
<Target Name="FixWinAppSDKAutoInitializer" BeforeTargets="ClCompile" AfterTargets="WindowsAppRuntimeAutoInitializer">
<ItemGroup>
<!-- Remove ALL injected versions of the file -->
<ClCompile Remove="@(ClCompile)" Condition="'%(Filename)' == 'WindowsAppRuntimeAutoInitializer'" />
<!-- Add ONE copy back manually -->
<ClCompile Include="$(PkgMicrosoft_WindowsAppSDK_Foundation)\include\WindowsAppRuntimeAutoInitializer.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(SolutionDir)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.1.8.250907003\build\native\Microsoft.WindowsAppSDK.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Base.1.8.250831001\build\native\Microsoft.WindowsAppSDK.Base.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Foundation.1.8.250906002\build\native\Microsoft.WindowsAppSDK.Foundation.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.WinUI.1.8.250906003\build\native\Microsoft.WindowsAppSDK.WinUI.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.Runtime.1.8.250907003\build\native\Microsoft.WindowsAppSDK.Runtime.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.DWrite.1.8.25090401\build\Microsoft.WindowsAppSDK.DWrite.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.WindowsAppSDK.InteractiveExperiences.1.8.250906004\build\native\Microsoft.WindowsAppSDK.InteractiveExperiences.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
<Error Condition="!Exists('..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Web.WebView2.1.0.2903.40\build\native\Microsoft.Web.WebView2.targets'))" />
</Target>
</Project>

View File

@@ -11,6 +11,7 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
{
public const string ColorsSettings = "ms-settings:colors";
public const string DiagnosticsAndFeedback = "ms-settings:privacy-feedback";
public const string NightLightSettings = "ms-settings:nightlight";
public static string AnimationsSettings => OSVersionHelper.IsWindows11()
? "ms-settings:easeofaccess-visualeffects"

View File

@@ -192,7 +192,10 @@
x:Uid="FancyZones_WindowSwitching_GroupSettings"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}"
IsExpanded="True">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.WindowSwitching, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.Name="FancyZonesWindowSwitchingToggle"
IsOn="{x:Bind ViewModel.WindowSwitching, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<!-- HACK: For some weird reason, a Shortcut Control is not working correctly if it's the first item in the expander, so we add an invisible card as the first one. -->
<tkcontrols:SettingsCard Name="FancyZonesWindowSwitchingPlaceholder" Visibility="Collapsed" />
@@ -259,7 +262,10 @@
Name="FancyZonesQuickLayoutSwitch"
x:Uid="FancyZones_QuickLayoutSwitch"
HeaderIcon="{ui:FontIcon Glyph=&#xEDA7;}">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.QuickLayoutSwitch, Mode=TwoWay}" />
<ToggleSwitch
x:Uid="ToggleSwitch"
AutomationProperties.Name="FancyZonesQuickLayoutSwitch"
IsOn="{x:Bind ViewModel.QuickLayoutSwitch, Mode=TwoWay}" />
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard
Name="FancyZonesFlashZonesOnQuickSwitch"

View File

@@ -67,6 +67,10 @@
x:Uid="LightSwitch_ModeSunsetToSunrise"
AutomationProperties.AutomationId="SunCBItem_LightSwitch"
Tag="SunsetToSunrise" />
<ComboBoxItem
x:Uid="LightSwitch_ModeFollowNightLight"
AutomationProperties.AutomationId="FollowNightLightCBItem_LightSwitch"
Tag="FollowNightLight" />
</ComboBox>
<tkcontrols:SettingsExpander.Items>
<tkcontrols:SettingsCard
@@ -152,6 +156,33 @@
IsOpen="True"
Severity="Informational" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
x:Name="FollowNightLightCard"
Padding="0"
HorizontalContentAlignment="Stretch"
Background="{ThemeResource InfoBarInformationalSeverityBackgroundBrush}"
ContentAlignment="Vertical"
Visibility="Collapsed">
<InfoBar
Background="Transparent"
BorderThickness="0"
IsClosable="False"
IsOpen="True"
Severity="Informational">
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center">
<Run x:Uid="LightSwitch_FollowNightLightCardMessage" />
</TextBlock>
<HyperlinkButton
x:Uid="LightSwitch_NightLightSettingsButton"
Margin="3,0,0,0"
Padding="0"
Click="OpenNightLightSettings_Click" />
</StackPanel>
</InfoBar>
</tkcontrols:SettingsCard>
</tkcontrols:SettingsExpander.Items>
</tkcontrols:SettingsExpander>
<InfoBar
@@ -350,13 +381,25 @@
<Setter Target="SunLocation_Card.Visibility" Value="Visible" />
<Setter Target="SunOffset_Card.Visibility" Value="Visible" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="ManualState">
<VisualState.Setters>
<Setter Target="Fixed_TurnOnCard.Visibility" Value="Visible" />
<Setter Target="Fixed_TurnOffCard.Visibility" Value="Visible" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="FollowNightLightState">
<VisualState.Setters>
<Setter Target="Fixed_TurnOnCard.Visibility" Value="Collapsed" />
<Setter Target="Fixed_TurnOffCard.Visibility" Value="Collapsed" />
<Setter Target="NoScheduleCard.Visibility" Value="Collapsed" />
<Setter Target="FollowNightLightCard.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

View File

@@ -355,6 +355,10 @@ namespace Microsoft.PowerToys.Settings.UI.Views
VisualStateManager.GoToState(this, "SunsetToSunriseState", true);
this.SunriseModeChartState();
break;
case "FollowNightLight":
VisualStateManager.GoToState(this, "FollowNightLightState", true);
TimelineCard.Visibility = Visibility.Collapsed;
break;
default:
VisualStateManager.GoToState(this, "OffState", true);
this.TimelineCard.Visibility = Visibility.Collapsed;
@@ -362,6 +366,18 @@ namespace Microsoft.PowerToys.Settings.UI.Views
}
}
private void OpenNightLightSettings_Click(object sender, RoutedEventArgs e)
{
try
{
Helpers.StartProcessHelper.Start(Helpers.StartProcessHelper.NightLightSettings);
}
catch (Exception ex)
{
Logger.LogError("Error while trying to open the system night light settings", ex);
}
}
private void SunriseModeChartState()
{
if (this.ViewModel.Latitude != "0.0" && this.ViewModel.Longitude != "0.0")

View File

@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@@ -5760,4 +5760,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
<value>A modern UI built with Fluent Design</value>
<comment>Fluent Design is a product name, do not loc</comment>
</data>
</root>
<data name="LightSwitch_ModeFollowNightLight.Content" xml:space="preserve">
<value>Follow Night Light</value>
</data>
<data name="LightSwitch_NightLightSettingsButton.Content" xml:space="preserve">
<value>Personalize your Night Light settings.</value>
</data>
<data name="LightSwitch_FollowNightLightCardMessage.Text" xml:space="preserve">
<value>Following Night Light settings.</value>
</data>
</root>

View File

@@ -42,6 +42,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
"Off",
"FixedHours",
"SunsetToSunrise",
"FollowNightLight",
};
_toggleThemeHotkey = _moduleSettings.Properties.ToggleThemeHotkey.Value;

View File

@@ -55,7 +55,7 @@ if ($Help) {
Write-Host " -Platform Target platform (default: auto-detect or x64)"
Write-Host " -Configuration Build configuration (default: Release)"
Write-Host " -PerUser Build per-user installer (default: true)"
Write-Host " -Version Overwrites the PowerToys version (default: from src\Version.props)"
Write-Host " -Version Sets the PowerToys version (default: from src\Version.props)"
Write-Host " -EnableCmdPalAOT Enable AOT compilation for CmdPal (slower build)"
Write-Host " -Clean Clean output directories before building"
Write-Host " -SkipBuild Skip building the main solution and tools (assumes they are already built)"
@@ -144,7 +144,8 @@ if ($currentScriptPath.StartsWith($repoRoot)) {
Push-Location $repoRoot
try {
if (git status --porcelain) {
$gitStatus = git status --porcelain
if ($gitStatus.Length -gt 0) {
Write-Host "[GIT] Uncommitted changes detected. Stashing (excluding this script)..."
$stashCountBefore = (git stash list).Count
@@ -257,7 +258,7 @@ try {
$versionPropsPath = Join-Path $repoRoot "src\Version.props"
[xml]$versionProps = Get-Content $versionPropsPath
$ptVersion = $versionProps.Project.PropertyGroup.Version
# Directory.Build.props appends .0 to the version for csproj files
# Directory.Build.props appends .0 to the version for .csproj files
$ptVersionFull = "$ptVersion.0"
# 2. Build the Generator
@@ -359,16 +360,6 @@ try {
RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
}
if ($Clean) {
Write-Host '[CLEAN] installer (keep *.exe)'
Push-Location $repoRoot
try {
git clean -xfd -e '*.exe' -- .\installer\ | Out-Null
} finally {
Pop-Location
}
}
# Set NUGET_PACKAGES environment variable if not set, to help wixproj find heat.exe
if (-not $env:NUGET_PACKAGES) {
$env:NUGET_PACKAGES = Join-Path $env:USERPROFILE ".nuget\packages"