Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Peek] Window foregrounding simplification and fixes + keep window visible if FE single selection changed #22657

Merged
merged 4 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 11 additions & 88 deletions src/modules/peek/Peek.UI/Extensions/WindowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
12 changes: 12 additions & 0 deletions src/modules/peek/Peek.UI/FolderItemsQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions src/modules/peek/Peek.UI/Helpers/FileExplorerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
36 changes: 35 additions & 1 deletion src/modules/peek/Peek.UI/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,7 +48,14 @@ private void OnPeekHotkey()
{
if (AppWindow.IsVisible)
{
Uninitialize();
if (IsNewSingleSelectedItem())
{
Initialize();
}
else
{
Uninitialize();
}
}
else
{
Expand All @@ -72,6 +80,7 @@ private void Initialize()

private void Uninitialize()
{
this.Restore();
this.Hide();

// TODO: move into general ViewModel method when needed
Expand Down Expand Up @@ -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();
}

Expand All @@ -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;
}
}
}
51 changes: 0 additions & 51 deletions src/modules/peek/Peek.UI/Native/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}
Expand Down
6 changes: 6 additions & 0 deletions src/modules/peek/Peek.UI/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
MonitorFromWindow
GetMonitorInfo
GetDpiForWindow
GetForegroundWindow
GetWindowThreadProcessId
GetCurrentThreadId
AttachThreadInput
BringWindowToTop
ShowWindow
GetWindowTextLength