diff --git a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs b/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs index 93c1474b0990..e73bc08b5aee 100644 --- a/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs +++ b/src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs @@ -39,98 +39,21 @@ public static double GetMonitorScale(this Window window) public static void BringToForeground(this Window window) { - var windowHandle = window.GetWindowHandle(); - - // Restore the window. - _ = NativeMethods.SendMessage(windowHandle, NativeMethods.WM_SYSCOMMAND, NativeMethods.SC_RESTORE, -2); + var foregroundWindowHandle = PInvoke.GetForegroundWindow(); - // Bring the window to the front. - if (!NativeMethods.SetWindowPos( - windowHandle, - NativeMethods.HWND_TOP, - 0, - 0, - 0, - 0, - NativeMethods.SWP_NOMOVE | NativeMethods.SWP_DRAWFRAME | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_SHOWWINDOW)) + uint targetProcessId = 0; + uint windowThreadProcessId = 0; + unsafe { - throw new InvalidOperationException("Failed to set window position."); + windowThreadProcessId = PInvoke.GetWindowThreadProcessId(foregroundWindowHandle, &targetProcessId); } - // Grab the SetForegroundWindow privilege from the shell process. - AcquireForegroundPrivilege(); - - // Make our window the foreground window. - _ = NativeMethods.SetForegroundWindow(windowHandle); - } - - private static void AcquireForegroundPrivilege() - { - IntPtr remoteProcessHandle = 0; - IntPtr user32Handle = 0; - IntPtr remoteThreadHandle = 0; - - try - { - // Get the handle of the shell window. - IntPtr topHandle = NativeMethods.GetShellWindow(); - if (topHandle == 0) - { - throw new InvalidOperationException("Failed to get the shell desktop window."); - } - - // Open the process that owns it. - IntPtr remoteProcessId = 0; - NativeMethods.GetWindowThreadProcessId(topHandle, ref remoteProcessId); - if (remoteProcessId == 0) - { - throw new InvalidOperationException("Failed to get the shell process ID."); - } - - remoteProcessHandle = NativeMethods.OpenProcess(NativeMethods.PROCESS_ALL_ACCESS, false, remoteProcessId); - if (remoteProcessHandle == 0) - { - throw new InvalidOperationException("Failed to open the shell process."); - } - - // Get the address of the AllowSetForegroundWindow API. - user32Handle = NativeMethods.LoadLibrary("user32.dll"); - IntPtr entryPoint = NativeMethods.GetProcAddress(user32Handle, "AllowSetForegroundWindow"); - - // Create a remote thread in the other process and make it call the API. - remoteThreadHandle = NativeMethods.CreateRemoteThread( - remoteProcessHandle, - 0, - 100000, - entryPoint, - NativeMethods.GetCurrentProcessId(), - 0, - 0); - if (remoteThreadHandle == 0) - { - throw new InvalidOperationException("Failed to create the remote thread."); - } - - // Wait for the remote thread to terminate. - _ = NativeMethods.WaitForSingleObject(remoteThreadHandle, 5000); - } - finally - { - if (remoteProcessHandle != 0) - { - _ = NativeMethods.CloseHandle(remoteProcessHandle); - } - - if (remoteThreadHandle != 0) - { - _ = NativeMethods.CloseHandle(remoteThreadHandle); - } - - if (user32Handle != 0) - { - _ = NativeMethods.FreeLibrary(user32Handle); - } - } + var windowHandle = window.GetWindowHandle(); + var currentThreadId = PInvoke.GetCurrentThreadId(); + PInvoke.AttachThreadInput(windowThreadProcessId, currentThreadId, true); + PInvoke.BringWindowToTop(new HWND(windowHandle)); + PInvoke.ShowWindow(new HWND(windowHandle), Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_SHOW); + PInvoke.AttachThreadInput(windowThreadProcessId, currentThreadId, false); } } } diff --git a/src/modules/peek/Peek.UI/FolderItemsQuery.cs b/src/modules/peek/Peek.UI/FolderItemsQuery.cs index 6da09fc047df..116fc88b6517 100644 --- a/src/modules/peek/Peek.UI/FolderItemsQuery.cs +++ b/src/modules/peek/Peek.UI/FolderItemsQuery.cs @@ -36,6 +36,18 @@ public partial class FolderItemsQuery : ObservableObject private Task? InitializeFilesTask { get; set; } = null; + public static File? GetFileExplorerSelectedFile() + { + var shellItems = FileExplorerHelper.GetSelectedItems(); + var firstSelectedItem = shellItems?.Item(0); + if (shellItems == null || firstSelectedItem == null) + { + return null; + } + + return new File(firstSelectedItem.Path); + } + public void Clear() { CurrentFile = null; diff --git a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs index ce83f22952ba..0650ae8bc640 100644 --- a/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs +++ b/src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs @@ -13,6 +13,23 @@ namespace Peek.UI.Helpers { public static class FileExplorerHelper { + public static Shell32.FolderItems? GetSelectedItems() + { + var folderView = GetCurrentFolderView(); + if (folderView == null) + { + return null; + } + + Shell32.FolderItems selectedItems = folderView.SelectedItems(); + if (selectedItems == null || selectedItems.Count == 0) + { + return null; + } + + return selectedItems; + } + public static Shell32.IShellFolderViewDual2? GetCurrentFolderView() { var foregroundWindowHandle = NativeMethods.GetForegroundWindow(); diff --git a/src/modules/peek/Peek.UI/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/MainWindow.xaml.cs index 6453410c99ae..f9e926e6f5e9 100644 --- a/src/modules/peek/Peek.UI/MainWindow.xaml.cs +++ b/src/modules/peek/Peek.UI/MainWindow.xaml.cs @@ -10,6 +10,7 @@ namespace Peek.UI using Microsoft.UI.Xaml.Input; using Peek.FilePreviewer.Models; using Peek.UI.Extensions; + using Peek.UI.Helpers; using Peek.UI.Native; using Windows.Foundation; using WinUIEx; @@ -47,7 +48,14 @@ private void OnPeekHotkey() { if (AppWindow.IsVisible) { - Uninitialize(); + if (IsNewSingleSelectedItem()) + { + Initialize(); + } + else + { + Uninitialize(); + } } else { @@ -72,6 +80,7 @@ private void Initialize() private void Uninitialize() { + this.Restore(); this.Hide(); // TODO: move into general ViewModel method when needed @@ -125,6 +134,7 @@ private void FilePreviewer_PreviewSizeChanged(object sender, PreviewSizeChangedA var scaledWindowHeight = adjustedContentSize.Height / monitorScale; this.CenterOnScreen(scaledWindowWidth + WindowHeightContentPadding, scaledWindowHeight + titleBarHeight + WindowWidthContentPadding); + this.Show(); this.BringToForeground(); } @@ -138,5 +148,29 @@ private void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args) args.Cancel = true; Uninitialize(); } + + private bool IsNewSingleSelectedItem() + { + var folderView = FileExplorerHelper.GetCurrentFolderView(); + if (folderView == null) + { + return false; + } + + Shell32.FolderItems selectedItems = folderView.SelectedItems(); + if (selectedItems.Count > 1) + { + return false; + } + + var fileExplorerSelectedItemPath = selectedItems.Item(0)?.Path; + var currentFilePath = ViewModel.FolderItemsQuery.CurrentFile?.Path; + if (fileExplorerSelectedItemPath == null || currentFilePath == null || fileExplorerSelectedItemPath == currentFilePath) + { + return false; + } + + return true; + } } } diff --git a/src/modules/peek/Peek.UI/Native/NativeMethods.cs b/src/modules/peek/Peek.UI/Native/NativeMethods.cs index 5536c9f7a833..b3d1979c60b3 100644 --- a/src/modules/peek/Peek.UI/Native/NativeMethods.cs +++ b/src/modules/peek/Peek.UI/Native/NativeMethods.cs @@ -12,15 +12,6 @@ namespace Peek.UI.Native public static class NativeMethods { - internal const uint PROCESS_ALL_ACCESS = 0x1f0fff; - internal const IntPtr HWND_TOP = 0; - internal const uint SWP_DRAWFRAME = 0x0020; - internal const uint SWP_NOMOVE = 0x0002; - internal const uint SWP_NOSIZE = 0x0001; - internal const uint SWP_SHOWWINDOW = 0x0040; - internal const int WM_SYSCOMMAND = 0x0112; - internal const int SC_RESTORE = 0xF120; - [Flags] public enum AssocF { @@ -58,48 +49,6 @@ public enum AssocStr [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)] internal static extern HResult AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string? pszExtra, [Out] StringBuilder? pszOut, [In][Out] ref uint pcchOut); - [DllImport("user32.dll")] - internal static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, ref IntPtr ProcessId); - - [DllImport("kernel32.dll")] - internal static extern IntPtr OpenProcess(uint fdwAccess, bool fInherit, IntPtr IDProcess); - - [DllImport("kernel32.dll")] - internal static extern int CloseHandle(IntPtr hObject); - - [DllImport("kernel32.dll")] - internal static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); - - [DllImport("kernel32.dll")] - internal static extern IntPtr LoadLibrary(string lpLibName); - - [DllImport("kernel32.dll")] - internal static extern bool FreeLibrary(IntPtr lib); - - [DllImport("kernel32.dll")] - internal static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr bogusAttributes, int dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, int dwCreationFlags, IntPtr lpThreadId); - - [DllImport("kernel32.dll")] - internal static extern uint WaitForSingleObject(IntPtr hObject, int dwMilliseconds); - - [DllImport("user32.dll")] - internal static extern IntPtr GetShellWindow(); - - [DllImport("kernel32.dll")] - internal static extern IntPtr GetCurrentProcess(); - - [DllImport("kernel32.dll")] - internal static extern int GetCurrentProcessId(); - - [DllImport("user32.dll")] - internal static extern int SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); - - [DllImport("user32.dll")] - internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); - - [DllImport("user32.dll")] - internal static extern int SetForegroundWindow(IntPtr hWnd); - [DllImport("user32.dll")] internal static extern int GetWindowText(Windows.Win32.Foundation.HWND hWnd, StringBuilder lpString, int nMaxCount); } diff --git a/src/modules/peek/Peek.UI/NativeMethods.txt b/src/modules/peek/Peek.UI/NativeMethods.txt index 1d4be7adc77e..30f37506e821 100644 --- a/src/modules/peek/Peek.UI/NativeMethods.txt +++ b/src/modules/peek/Peek.UI/NativeMethods.txt @@ -1,4 +1,10 @@ MonitorFromWindow GetMonitorInfo GetDpiForWindow +GetForegroundWindow +GetWindowThreadProcessId +GetCurrentThreadId +AttachThreadInput +BringWindowToTop +ShowWindow GetWindowTextLength \ No newline at end of file