diff --git a/src/modules/peek/Peek.UI/MainWindowViewModel.cs b/src/modules/peek/Peek.UI/MainWindowViewModel.cs
index db54e346cc..775664c042 100644
--- a/src/modules/peek/Peek.UI/MainWindowViewModel.cs
+++ b/src/modules/peek/Peek.UI/MainWindowViewModel.cs
@@ -62,6 +62,12 @@ namespace Peek.UI
[ObservableProperty]
private IFileSystemItem? _currentItem;
+ ///
+ /// Work around missing navigation when peeking from CLI.
+ /// TODO: Implement navigation when peeking from CLI.
+ ///
+ 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;
diff --git a/src/modules/peek/Peek.UI/Models/SelectedItem.cs b/src/modules/peek/Peek.UI/Models/SelectedItem.cs
new file mode 100644
index 0000000000..9d0cc6568a
--- /dev/null
+++ b/src/modules/peek/Peek.UI/Models/SelectedItem.cs
@@ -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);
+ }
+}
diff --git a/src/modules/peek/Peek.UI/Models/SelectedItemByPath.cs b/src/modules/peek/Peek.UI/Models/SelectedItemByPath.cs
new file mode 100644
index 0000000000..5f53865bfd
--- /dev/null
+++ b/src/modules/peek/Peek.UI/Models/SelectedItemByPath.cs
@@ -0,0 +1,23 @@
+// 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;
+
+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 string.Equals(Path, path, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.UI/Models/SelectedItemByWindowHandle.cs b/src/modules/peek/Peek.UI/Models/SelectedItemByWindowHandle.cs
new file mode 100644
index 0000000000..e93b2f94ca
--- /dev/null
+++ b/src/modules/peek/Peek.UI/Models/SelectedItemByWindowHandle.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs
index 9bd66e380f..b89e871a4d 100644
--- a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs
+++ b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs
@@ -3,16 +3,19 @@
// See the LICENSE file in the project root for more information.
using System;
-
+using System.IO;
+using System.Threading;
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 +26,7 @@ namespace Peek.UI
///
/// Provides application-specific behavior to supplement the default Application class.
///
- public partial class App : Application, IApp
+ public partial class App : Application, IApp, IDisposable
{
public static int PowerToysPID { get; set; }
@@ -36,6 +39,10 @@ namespace Peek.UI
private MainWindow? Window { get; set; }
+ private bool _disposed;
+ private SelectedItem? _selectedItem;
+ private bool _launchedFromCli;
+
///
/// Initializes a new instance of the class.
/// Initializes the singleton application object. This is the first line of authored code
@@ -52,22 +59,22 @@ namespace Peek.UI
InitializeComponent();
Logger.InitializeLogger("\\Peek\\Logs");
- Host = Microsoft.Extensions.Hosting.Host.
- CreateDefaultBuilder().
- UseContentRoot(AppContext.BaseDirectory).
- ConfigureServices((context, services) =>
- {
- // Core Services
- services.AddTransient();
- services.AddSingleton();
- services.AddSingleton();
+ Host = Microsoft.Extensions.Hosting.Host
+ .CreateDefaultBuilder()
+ .UseContentRoot(AppContext.BaseDirectory)
+ .ConfigureServices((context, services) =>
+ {
+ // Core Services
+ services.AddTransient();
+ services.AddSingleton();
+ services.AddSingleton();
- // Views and ViewModels
- services.AddTransient();
- services.AddTransient();
- services.AddTransient();
- }).
- Build();
+ // Views and ViewModels
+ services.AddTransient();
+ services.AddTransient();
+ services.AddTransient();
+ })
+ .Build();
UnhandledException += App_UnhandledException;
}
@@ -99,6 +106,7 @@ namespace Peek.UI
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs?.Length > 1)
{
+ // Check if the last argument is a PowerToys Runner PID
if (int.TryParse(cmdArgs[^1], out int powerToysRunnerPid))
{
RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () =>
@@ -107,9 +115,25 @@ namespace Peek.UI
Environment.Exit(0);
});
}
+ else
+ {
+ // Command line argument is a file path - activate Peek with that file
+ string filePath = cmdArgs[^1];
+ if (File.Exists(filePath) || Directory.Exists(filePath))
+ {
+ _selectedItem = new SelectedItemByPath(filePath);
+ _launchedFromCli = true;
+ OnShowPeek();
+ return;
+ }
+ else
+ {
+ Logger.LogError($"Command line argument is not a valid file or directory: {filePath}");
+ }
+ }
}
- NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey);
+ NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnShowPeek);
NativeEventWaiter.WaitForEventLoop(Constants.TerminatePeekEvent(), () =>
{
ShellPreviewHandlerPreviewer.ReleaseHandlerFactories();
@@ -126,11 +150,16 @@ namespace Peek.UI
///
/// Handle Peek hotkey
///
- 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 +169,38 @@ namespace Peek.UI
Window = new MainWindow();
}
- Window.Toggle(firstActivation, foregroundWindowHandle);
+ Window.Toggle(firstActivation, _selectedItem, _launchedFromCli);
+ _launchedFromCli = false;
+ _selectedItem = null;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ // dispose managed state (managed objects)
+ }
+
+ // 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);
}
}
}
diff --git a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
index 4edad9a807..2c8983c634 100644
--- a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
+++ b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs
@@ -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;
@@ -38,6 +39,7 @@ namespace Peek.UI
/// dialog is open at a time.
///
private bool _isDeleteInProgress;
+ private bool _exitAfterClose;
public MainWindow()
{
@@ -116,12 +118,17 @@ namespace Peek.UI
///
/// Toggling the window visibility and querying files when necessary.
///
- public void Toggle(bool firstActivation, Windows.Win32.Foundation.HWND foregroundWindowHandle)
+ public void Toggle(bool firstActivation, SelectedItem selectedItem, bool exitAfterClose)
{
+ if (exitAfterClose)
+ {
+ _exitAfterClose = true;
+ }
+
if (firstActivation)
{
Activate();
- Initialize(foregroundWindowHandle);
+ Initialize(selectedItem);
return;
}
@@ -132,9 +139,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 +151,7 @@ namespace Peek.UI
}
else
{
- Initialize(foregroundWindowHandle);
+ Initialize(selectedItem);
}
}
@@ -182,12 +189,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;
@@ -207,6 +214,11 @@ namespace Peek.UI
this.Content.KeyUp -= Content_KeyUp;
ShellPreviewHandlerPreviewer.ReleaseHandlerFactories();
+
+ if (_exitAfterClose)
+ {
+ Environment.Exit(0);
+ }
}
///
@@ -272,20 +284,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)
{