Compare commits

...

15 Commits

Author SHA1 Message Date
Leilei Zhang
a27fb51b3a change the order 2025-07-22 22:34:18 +08:00
Leilei Zhang
c39f8533da update cache key 2025-07-22 22:17:29 +08:00
Leilei Zhang
82883a0a8e use string 2025-07-22 19:58:52 +08:00
Leilei Zhang
f15f7f7351 Merge branch 'leilzh/cache' of https://github.com/microsoft/PowerToys into leilzh/testci 2025-07-22 19:26:26 +08:00
Leilei Zhang
d38e755ae3 remove unused 2025-07-22 19:23:52 +08:00
Leilei Zhang
bbd30305fd update key for cache 2025-07-22 19:17:03 +08:00
Leilei Zhang
0931d8af86 update 2025-07-22 13:09:22 +08:00
Leilei Zhang
db286a0f04 refine the code 2025-07-22 11:55:58 +08:00
Leilei Zhang
e557decacb Merge branch 'main' of https://github.com/microsoft/PowerToys into feature/Peek.CLI 2025-07-22 09:17:57 +08:00
Leilei Zhang
dda41dd7f6 fix merge error 2025-07-18 18:08:35 +08:00
Leilei Zhang
9cb362c2df Merge branch 'main' of https://github.com/microsoft/PowerToys into feature/Peek.CLI 2025-07-18 17:53:44 +08:00
Clint Rutkas
9be46402a4 Merge branch 'main' into feature/Peek.CLI 2025-06-07 04:01:58 +00:00
Antonín Procházka
4aee9a6942 Merge branch 'feature/Peek.CLI' of https://github.com/prochan2/PowerToys into feature/Peek.CLI 2025-04-11 10:14:45 +02:00
Antonín Procházka
c60ad68f8a Peek file using command line and named pipe. (#26422) 2025-04-11 10:13:21 +02:00
Antonín Procházka
f334fb924b Peek file using command line and named pipe. (#26422) 2025-04-11 10:02:19 +02:00
11 changed files with 286 additions and 33 deletions

View File

@@ -120,6 +120,8 @@
"WinUI3Apps\\Powertoys.Peek.UI.dll",
"WinUI3Apps\\Powertoys.Peek.UI.exe",
"WinUI3Apps\\Powertoys.Peek.dll",
"WinUI3Apps\\Powertoys.Peek.CLI.dll",
"WinUI3Apps\\PowerToys.Peek.CLI.exe",
"WinUI3Apps\\PowerToys.EnvironmentVariablesModuleInterface.dll",
"WinUI3Apps\\PowerToys.EnvironmentVariablesUILib.dll",

View File

@@ -190,22 +190,30 @@ jobs:
displayName: Verify ARM64 configurations
- ${{ if eq(parameters.enablePackageCaching, true) }}:
- pwsh: |-
$dotnetVersion = dotnet --version
Write-Host "Current .NET SDK Version: $dotnetVersion"
Write-Host "##vso[task.setvariable variable=DotNetSdkVersion]$dotnetVersion"
displayName: Get .NET SDK Version for Cache Key
- task: Cache@2
displayName: 'Cache nuget packages (PackageReference)'
inputs:
key: '"PackageReference" | "$(Agent.OS)" | Directory.Packages.props'
key: '"$(DotNetSdkVersion)" | "PackageReference" | "$(Agent.OS)" | Directory.Packages.props'
restoreKeys: |
"PackageReference" | "$(Agent.OS)"
"PackageReference"
"$(DotNetSdkVersion)" | "PackageReference" | "$(Agent.OS)"
"$(DotNetSdkVersion)" | "PackageReference"
"$(DotNetSdkVersion)"
path: $(NUGET_PACKAGES)
- task: Cache@2
displayName: 'Cache nuget packages (packages.config)'
inputs:
key: '"packages.config" | "$(Agent.OS)" | **/packages.config'
key: '"$(DotNetSdkVersion)" | "packages.config" | "$(Agent.OS)" | **/packages.config'
restoreKeys: |
"packages.config" | "$(Agent.OS)"
"packages.config"
"$(DotNetSdkVersion)" | "packages.config" | "$(Agent.OS)"
"$(DotNetSdkVersion)" | "packages.config"
"$(DotNetSdkVersion)"
path: packages
- ${{ if eq(parameters.useLatestWinAppSDK, true)}}:

View File

@@ -711,6 +711,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CmdPalKeyboardService", "sr
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerRename.FuzzingTest", "src\modules\powerrename\PowerRename.FuzzingTest\PowerRename.FuzzingTest.vcxproj", "{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Peek.CLI", "src\modules\peek\Peek.CLI\Peek.CLI.csproj", "{886648E3-A9FC-D591-898F-246954EE5682}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BgcodePreviewHandler", "src\modules\previewpane\BgcodePreviewHandler\BgcodePreviewHandler.csproj", "{9E0CBC06-F29A-4810-B93C-97D53863B95E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BgcodePreviewHandlerCpp", "src\modules\previewpane\BgcodePreviewHandlerCpp\BgcodePreviewHandlerCpp.vcxproj", "{F6088A11-1C9E-4420-AA90-CF7E78DD7F1C}"
@@ -2631,6 +2633,14 @@ Global
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|ARM64.Build.0 = Release|ARM64
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.ActiveCfg = Release|x64
{64B88F02-CD88-4ED8-9624-989A800230F9}.Release|x64.Build.0 = Release|x64
{886648E3-A9FC-D591-898F-246954EE5682}.Debug|ARM64.ActiveCfg = Debug|ARM64
{886648E3-A9FC-D591-898F-246954EE5682}.Debug|ARM64.Build.0 = Debug|ARM64
{886648E3-A9FC-D591-898F-246954EE5682}.Debug|x64.ActiveCfg = Debug|x64
{886648E3-A9FC-D591-898F-246954EE5682}.Debug|x64.Build.0 = Debug|x64
{886648E3-A9FC-D591-898F-246954EE5682}.Release|ARM64.ActiveCfg = Release|ARM64
{886648E3-A9FC-D591-898F-246954EE5682}.Release|ARM64.Build.0 = Release|ARM64
{886648E3-A9FC-D591-898F-246954EE5682}.Release|x64.ActiveCfg = Release|x64
{886648E3-A9FC-D591-898F-246954EE5682}.Release|x64.Build.0 = Release|x64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.ActiveCfg = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|ARM64.Build.0 = Debug|ARM64
{0217E86E-3476-9946-DE8E-9D200CEBD47A}.Debug|x64.ActiveCfg = Debug|x64
@@ -3068,6 +3078,7 @@ Global
{4E0AE3A4-2EE0-44D7-A2D0-8769977254A0} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA}
{5702B3CC-8575-48D5-83D8-15BB42269CD3} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{64B88F02-CD88-4ED8-9624-989A800230F9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
{886648E3-A9FC-D591-898F-246954EE5682} = {17B4FA70-001E-4D33-BBBB-0D142DBC2E20}
{0217E86E-3476-9946-DE8E-9D200CEBD47A} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD}
{5F63C743-F6CE-4DBA-A200-2B3F8A14E8C2} = {3846508C-77EB-4034-A702-F8BB263C4F79}
{2694E2FB-DCD5-4BFF-A418-B6C3C7CE3B8E} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.SelfContained.props" />
<PropertyGroup>
<RootNamespace>Peek.CLI</RootNamespace>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType> <!-- Exe to show console output -->
<AssemblyName>PowerToys.Peek.CLI</AssemblyName>
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
</Project>

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 System;
using System.IO;
using System.IO.Pipes;
if (args.Length < 1)
{
Console.WriteLine("Usage: PowerToys.Peek.CLI.exe <path>");
return 1; // Return error code
}
try
{
using var pipe = new NamedPipeClientStream(".", "PeekPipe", PipeDirection.Out);
pipe.Connect(5000); // 5 second timeout
using var writer = new StreamWriter(pipe) { AutoFlush = true };
writer.WriteLine(args[0]);
return 0;
}
catch (Exception ex) when (IsConnectionError(ex))
{
Console.WriteLine("Error: Could not connect to PowerToys. Make sure PowerToys Peek is running.");
return 2;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return 3;
}
static bool IsConnectionError(Exception ex)
{
return ex is TimeoutException ||
(ex is InvalidOperationException &&
(ex.Message.Contains("Pipe is broken") || ex.Message.Contains("timeout")));
}

View File

@@ -62,6 +62,12 @@ namespace Peek.UI
[ObservableProperty]
private IFileSystemItem? _currentItem;
/// <summary>
/// Work around missing navigation when peeking from CLI.
/// TODO: Implement navigation when peeking from CLI.
/// </summary>
private bool _isFromCli;
partial void OnCurrentItemChanged(IFileSystemItem? value)
{
WindowTitle = value != null
@@ -129,7 +135,24 @@ namespace Peek.UI
NavigationThrottleTimer.Interval = TimeSpan.FromMilliseconds(NavigationThrottleDelayMs);
}
public void Initialize(HWND foregroundWindowHandle)
public void Initialize(SelectedItem selectedItem)
{
switch (selectedItem)
{
case SelectedItemByPath selectedItemByPath:
InitializeFromCli(selectedItemByPath.Path);
break;
case SelectedItemByWindowHandle selectedItemByWindowHandle:
InitializeFromExplorer(selectedItemByWindowHandle.WindowHandle);
break;
default:
throw new NotImplementedException($"Invalid type of selected item: '{selectedItem.GetType().FullName}'");
}
}
private void InitializeFromExplorer(HWND foregroundWindowHandle)
{
try
{
@@ -141,10 +164,20 @@ namespace Peek.UI
}
_currentIndex = DisplayIndex = 0;
_isFromCli = false;
CurrentItem = (Items != null && Items.Count > 0) ? Items[0] : null;
}
private void InitializeFromCli(string path)
{
// TODO: implement navigation
_isFromCli = true;
Items = null;
_currentIndex = DisplayIndex = 0;
CurrentItem = new FileItem(path, Path.GetFileName(path));
}
public void Uninitialize()
{
_currentIndex = DisplayIndex = 0;
@@ -153,6 +186,7 @@ namespace Peek.UI
Items = null;
_navigationDirection = NavigationDirection.Forwards;
IsErrorVisible = false;
_isFromCli = false;
}
public void AttemptPreviousNavigation() => Navigate(NavigationDirection.Backwards);
@@ -166,6 +200,12 @@ namespace Peek.UI
return;
}
// TODO: implement navigation.
if (_isFromCli)
{
return;
}
if (Items == null || Items.Count == _deletedItemIndexes.Count)
{
_currentIndex = DisplayIndex = 0;

View File

@@ -0,0 +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.
namespace Peek.UI.Models
{
public abstract class SelectedItem
{
public abstract bool Matches(string? path);
}
}

View File

@@ -0,0 +1,21 @@
// 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.
namespace Peek.UI.Models
{
public class SelectedItemByPath : SelectedItem
{
public string Path { get; }
public SelectedItemByPath(string path)
{
Path = path;
}
public override bool Matches(string? path)
{
return Path == path;
}
}
}

View File

@@ -0,0 +1,34 @@
// 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 Peek.UI.Extensions;
using Peek.UI.Helpers;
using Windows.Win32.Foundation;
namespace Peek.UI.Models
{
public class SelectedItemByWindowHandle : SelectedItem
{
public HWND WindowHandle { get; }
public SelectedItemByWindowHandle(HWND windowHandle)
{
WindowHandle = windowHandle;
}
public override bool Matches(string? path)
{
var selectedItems = FileExplorerHelper.GetSelectedItems(WindowHandle);
var selectedItemsCount = selectedItems?.GetCount() ?? 0;
if (selectedItems == null || selectedItemsCount == 0 || selectedItemsCount > 1)
{
return false;
}
var fileExplorerSelectedItemPath = selectedItems.GetItemAt(0).ToIFileSystemItem().Path;
var currentItemPath = path;
return fileExplorerSelectedItemPath != null && currentItemPath != null && fileExplorerSelectedItemPath != currentItemPath;
}
}
}

View File

@@ -3,16 +3,21 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.PowerToys.Telemetry;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls.Primitives;
using Peek.Common;
using Peek.FilePreviewer;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers;
using Peek.UI.Models;
using Peek.UI.Native;
using Peek.UI.Telemetry.Events;
using Peek.UI.Views;
@@ -23,7 +28,7 @@ namespace Peek.UI
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : Application, IApp
public partial class App : Application, IApp, IDisposable
{
public static int PowerToysPID { get; set; }
@@ -36,6 +41,11 @@ namespace Peek.UI
private MainWindow? Window { get; set; }
private CancellationTokenSource _appCts = new CancellationTokenSource();
private bool _disposed;
private SelectedItem? _selectedItem;
private EventWaitHandle? _peekWaitHandle;
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// Initializes the singleton application object. This is the first line of authored code
@@ -109,13 +119,15 @@ namespace Peek.UI
}
}
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnShowPeek);
NativeEventWaiter.WaitForEventLoop(Constants.TerminatePeekEvent(), () =>
{
ShellPreviewHandlerPreviewer.ReleaseHandlerFactories();
EtwTrace?.Dispose();
Environment.Exit(0);
});
Task.Run(() => ListenPipeLoop(_appCts.Token));
}
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
@@ -126,11 +138,16 @@ namespace Peek.UI
/// <summary>
/// Handle Peek hotkey
/// </summary>
private void OnPeekHotkey()
private void OnShowPeek()
{
// Need to read the foreground HWND before activating Peek to avoid focus stealing
// Foreground HWND must always be Explorer or Desktop
var foregroundWindowHandle = Windows.Win32.PInvoke_PeekUI.GetForegroundWindow();
// null means explorer, not null means CLI
if (_selectedItem == null)
{
// Need to read the foreground HWND before activating Peek to avoid focus stealing
// Foreground HWND must always be Explorer or Desktop
var foregroundWindowHandle = Windows.Win32.PInvoke_PeekUI.GetForegroundWindow();
_selectedItem = new SelectedItemByWindowHandle(foregroundWindowHandle);
}
bool firstActivation = false;
@@ -140,7 +157,71 @@ namespace Peek.UI
Window = new MainWindow();
}
Window.Toggle(firstActivation, foregroundWindowHandle);
Window.Toggle(firstActivation, _selectedItem);
_selectedItem = null;
}
private async Task ListenPipeLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
using (var server = new NamedPipeServerStream("PeekPipe", PipeDirection.In))
{
await server.WaitForConnectionAsync(token);
using (var reader = new StreamReader(server))
{
var path = await reader.ReadLineAsync(token);
if (!string.IsNullOrWhiteSpace(path))
{
// Validate that the file or directory exists before proceeding
if (File.Exists(path) || Directory.Exists(path))
{
_peekWaitHandle ??= EventWaitHandle.OpenExisting(Constants.ShowPeekEvent());
_selectedItem = new SelectedItemByPath(path);
_peekWaitHandle.Set();
}
else
{
Logger.LogError($"CLI requested file or directory does not exist: {path}");
}
}
}
}
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// dispose managed state (managed objects)
_peekWaitHandle?.Dispose();
_appCts.Cancel();
_appCts.Dispose();
}
// free unmanaged resources (unmanaged objects) and override finalizer
// set large fields to null
_disposed = true;
}
}
/* // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~App()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// } */
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -18,6 +18,7 @@ using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers;
using Peek.UI.Extensions;
using Peek.UI.Helpers;
using Peek.UI.Models;
using Peek.UI.Telemetry.Events;
using Windows.Foundation;
using WinUIEx;
@@ -116,12 +117,12 @@ namespace Peek.UI
/// <summary>
/// Toggling the window visibility and querying files when necessary.
/// </summary>
public void Toggle(bool firstActivation, Windows.Win32.Foundation.HWND foregroundWindowHandle)
public void Toggle(bool firstActivation, SelectedItem selectedItem)
{
if (firstActivation)
{
Activate();
Initialize(foregroundWindowHandle);
Initialize(selectedItem);
return;
}
@@ -132,9 +133,9 @@ namespace Peek.UI
if (AppWindow.IsVisible)
{
if (IsNewSingleSelectedItem(foregroundWindowHandle))
if (IsNewSingleSelectedItem(selectedItem))
{
Initialize(foregroundWindowHandle);
Initialize(selectedItem);
Activate(); // Brings existing window into focus in case it was previously minimized
}
else
@@ -144,7 +145,7 @@ namespace Peek.UI
}
else
{
Initialize(foregroundWindowHandle);
Initialize(selectedItem);
}
}
@@ -182,12 +183,12 @@ namespace Peek.UI
Uninitialize();
}
private void Initialize(Windows.Win32.Foundation.HWND foregroundWindowHandle)
private void Initialize(SelectedItem selectedItem)
{
var bootTime = new System.Diagnostics.Stopwatch();
bootTime.Start();
ViewModel.Initialize(foregroundWindowHandle);
ViewModel.Initialize(selectedItem);
ViewModel.ScalingFactor = this.GetMonitorScale();
this.Content.KeyUp += Content_KeyUp;
@@ -272,20 +273,11 @@ namespace Peek.UI
Uninitialize();
}
private bool IsNewSingleSelectedItem(Windows.Win32.Foundation.HWND foregroundWindowHandle)
private bool IsNewSingleSelectedItem(SelectedItem selectedItem)
{
try
{
var selectedItems = FileExplorerHelper.GetSelectedItems(foregroundWindowHandle);
var selectedItemsCount = selectedItems?.GetCount() ?? 0;
if (selectedItems == null || selectedItemsCount == 0 || selectedItemsCount > 1)
{
return false;
}
var fileExplorerSelectedItemPath = selectedItems.GetItemAt(0).ToIFileSystemItem().Path;
var currentItemPath = ViewModel.CurrentItem?.Path;
return fileExplorerSelectedItemPath != null && currentItemPath != null && fileExplorerSelectedItemPath != currentItemPath;
return selectedItem.Matches(ViewModel.CurrentItem?.Path);
}
catch (Exception ex)
{