From f5e6e4c76db4b288b7fda9e1d65069247e7489f7 Mon Sep 17 00:00:00 2001 From: Hank McCord Date: Mon, 19 Dec 2022 11:04:24 -0500 Subject: [PATCH] Implement more reliable foreground swap --- ReadySetTarkov/App.xaml.cs | 5 +- ReadySetTarkov/MainContextMenu.xaml | 50 +++++++++----- ReadySetTarkov/MainContextMenu.xaml.cs | 5 ++ ReadySetTarkov/NativeMethods.txt | 10 ++- ReadySetTarkov/Properties/launchSettings.json | 12 ++++ ReadySetTarkov/ReadySetTarkov.csproj | 22 ++++--- .../ReadySetTarkovHostBuilderExtensions.cs | 4 +- ReadySetTarkov/Services/ShutdownHandler.cs | 17 +++-- .../Settings/ReadySetTarkovSettings.cs | 3 +- ReadySetTarkov/Tarkov/GameEventHandler.cs | 13 ++-- ReadySetTarkov/TrayViewModel.cs | 24 +++---- ReadySetTarkov/Utility/INativeMethods.cs | 6 +- ReadySetTarkov/Utility/IUser32.cs | 58 ++++++++++++++-- ReadySetTarkov/Utility/Kernel32.cs | 3 + ReadySetTarkov/Utility/NativeMethods.cs | 66 ++++++++++++++----- ReadySetTarkov/Utility/User32.cs | 24 +++++-- 16 files changed, 228 insertions(+), 94 deletions(-) create mode 100644 ReadySetTarkov/Properties/launchSettings.json diff --git a/ReadySetTarkov/App.xaml.cs b/ReadySetTarkov/App.xaml.cs index 17c9c5e..f8d35bc 100644 --- a/ReadySetTarkov/App.xaml.cs +++ b/ReadySetTarkov/App.xaml.cs @@ -7,8 +7,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using ReadySetTarkov.Settings; - using Serilog; using Serilog.Events; @@ -62,7 +60,6 @@ private void Exiting() } _exitHandled = true; - _host.Services.GetRequiredService().Save(); } private void InstallExceptionHandlers() @@ -86,7 +83,7 @@ private async void LogUnhandledExceptionAsync(Exception ex, string sources) #pragma warning restore VSTHRD100, VSTHRD200 { #if DEBUG - await _icon!.ShowBalloonTipAsync("ReadySetTarkove fatal error", sources, Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Error); + await _icon!.ShowBalloonTipAsync("ReadySetTarkov fatal error", sources, Hardcodet.Wpf.TaskbarNotification.BalloonIcon.Error); await Task.Delay(5000); await _icon.CloseBalloonTipAsync(); #endif diff --git a/ReadySetTarkov/MainContextMenu.xaml b/ReadySetTarkov/MainContextMenu.xaml index 05f315e..92e2659 100644 --- a/ReadySetTarkov/MainContextMenu.xaml +++ b/ReadySetTarkov/MainContextMenu.xaml @@ -1,17 +1,25 @@ - - - + + + - + @@ -22,12 +30,18 @@ - - + + - + - - - + + + diff --git a/ReadySetTarkov/MainContextMenu.xaml.cs b/ReadySetTarkov/MainContextMenu.xaml.cs index 034ac12..ea34684 100644 --- a/ReadySetTarkov/MainContextMenu.xaml.cs +++ b/ReadySetTarkov/MainContextMenu.xaml.cs @@ -6,4 +6,9 @@ public partial class MainContextMenu { public MainContextMenu() => InitializeComponent(); + + private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e) + { + + } } diff --git a/ReadySetTarkov/NativeMethods.txt b/ReadySetTarkov/NativeMethods.txt index 582aa1d..041765a 100644 --- a/ReadySetTarkov/NativeMethods.txt +++ b/ReadySetTarkov/NativeMethods.txt @@ -3,6 +3,8 @@ QueryFullProcessImageName OpenProcess // User32 +AttachThreadInput +GetCurrentThreadId GetForegroundWindow SetForegroundWindow GetWindowLong @@ -13,10 +15,14 @@ SetFocus SetActiveWindow GetClassName FlashWindow +ShowWindow ShowWindowAsync FindWindow IsWindow +IsIconic keybd_event -GetWindowThreadProcessId +SystemParametersInfo +AllowSetForegroundWindow +LockSetForegroundWindow -SET_WINDOW_POS_FLAGS \ No newline at end of file +SET_WINDOW_POS_FLAGS diff --git a/ReadySetTarkov/Properties/launchSettings.json b/ReadySetTarkov/Properties/launchSettings.json new file mode 100644 index 0000000..4236249 --- /dev/null +++ b/ReadySetTarkov/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "ReadySetTarkov": { + "commandName": "Project" + }, + "Desktop": { + "commandName": "Executable", + "executablePath": "C:\\Users\\Hank\\Desktop\\ReadySetTarkov\\ReadySetTarkov.exe", + "workingDirectory": "C:\\Users\\Hank\\Desktop\\ReadySetTarkov" + } + } +} \ No newline at end of file diff --git a/ReadySetTarkov/ReadySetTarkov.csproj b/ReadySetTarkov/ReadySetTarkov.csproj index a113926..e191c29 100644 --- a/ReadySetTarkov/ReadySetTarkov.csproj +++ b/ReadySetTarkov/ReadySetTarkov.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net7.0-windows enable latest true @@ -20,17 +20,19 @@ - - + + - - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - + + diff --git a/ReadySetTarkov/ReadySetTarkovHostBuilderExtensions.cs b/ReadySetTarkov/ReadySetTarkovHostBuilderExtensions.cs index 514d4d2..17b77ae 100644 --- a/ReadySetTarkov/ReadySetTarkovHostBuilderExtensions.cs +++ b/ReadySetTarkov/ReadySetTarkovHostBuilderExtensions.cs @@ -50,6 +50,8 @@ public static IHostBuilder AddServices(this IHostBuilder hostBuilder) _ = services.AddTransient(); _ = services.AddTransient(); _ = services.AddTransient(); + _ = services.AddTransient(); + _ = services.AddTransient(); // Tarkov Log Watchers _ = services.AddSingleton(); @@ -66,7 +68,7 @@ public static IHostBuilder AddServices(this IHostBuilder hostBuilder) public static LoggerConfiguration MinimumLevelFromConfiguration(this LoggerConfiguration builder, IConfiguration config) { - foreach ((string key, string value) in config.AsEnumerable()) + foreach ((string key, string? value) in config.AsEnumerable()) { int idx = key.LastIndexOf(':'); string? eventName = key[(idx + 1)..]; diff --git a/ReadySetTarkov/Services/ShutdownHandler.cs b/ReadySetTarkov/Services/ShutdownHandler.cs index 89d72e9..cb878a1 100644 --- a/ReadySetTarkov/Services/ShutdownHandler.cs +++ b/ReadySetTarkov/Services/ShutdownHandler.cs @@ -1,23 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using System.Windows; using Microsoft.Extensions.Hosting; using Microsoft.VisualStudio.Threading; +using ReadySetTarkov.Settings; + namespace ReadySetTarkov.Services; internal class ShutdownHandler : IHostedService { + private readonly ISettingsProvider _settingsProvider; private readonly JoinableTaskFactory _joinableTaskFactory; - public ShutdownHandler(IHostApplicationLifetime hostApplicationLifetime, JoinableTaskFactory joinableTaskFactory) + public ShutdownHandler( + IHostApplicationLifetime hostApplicationLifetime, + ISettingsProvider settingsProvider, + JoinableTaskFactory joinableTaskFactory) { _joinableTaskFactory = joinableTaskFactory; - + _settingsProvider = settingsProvider; hostApplicationLifetime.ApplicationStopping.Register(static (c) => _ = (c as ShutdownHandler)!.ShutdownAppAsync(), this); } @@ -26,6 +28,7 @@ public ShutdownHandler(IHostApplicationLifetime hostApplicationLifetime, Joinabl private async Task ShutdownAppAsync() { + _settingsProvider.Save(); await _joinableTaskFactory.SwitchToMainThreadAsync(); Application.Current.Shutdown(); diff --git a/ReadySetTarkov/Settings/ReadySetTarkovSettings.cs b/ReadySetTarkov/Settings/ReadySetTarkovSettings.cs index fd29934..8bf6d0f 100644 --- a/ReadySetTarkov/Settings/ReadySetTarkovSettings.cs +++ b/ReadySetTarkov/Settings/ReadySetTarkovSettings.cs @@ -3,8 +3,7 @@ public class ReadySetTarkovSettings { public bool FlashTaskbar { get; set; } = true; - public bool SetTopMost { get; set; } = true; - public int WithSecondsLeft { get; set; } = 0; + public int WithSecondsLeft { get; set; } = 3; public SoundSettings Sounds { get; } = new SoundSettings(); } diff --git a/ReadySetTarkov/Tarkov/GameEventHandler.cs b/ReadySetTarkov/Tarkov/GameEventHandler.cs index 78b71e1..ebe821e 100644 --- a/ReadySetTarkov/Tarkov/GameEventHandler.cs +++ b/ReadySetTarkov/Tarkov/GameEventHandler.cs @@ -40,22 +40,19 @@ private async void GameStartingEventHandler(object? sender, EventArgs e) player.Play(); } - if (_settingsProvider.Settings.SetTopMost && _settingsProvider.Settings.WithSecondsLeft > 0) + if (_settingsProvider.Settings.WithSecondsLeft > 0) { // Going to assume 20 seconds, there's no log way to get the amount of time remaining. - await Task.Delay((20 * 1000) - (_settingsProvider.Settings.WithSecondsLeft * 1000)) - .ContinueWith(t => - _nativeMethods.BringTarkovToForeground(), - TaskScheduler.Default - ); + await Task.Delay((20 * 1000) - (_settingsProvider.Settings.WithSecondsLeft * 1000)); + await _nativeMethods.BringTarkovToForegroundAsync(); } } private void GameStartedEventHandler(object? sender, EventArgs e) { - if (_settingsProvider.Settings.SetTopMost && _settingsProvider.Settings.WithSecondsLeft == 0) + if (_settingsProvider.Settings.WithSecondsLeft == 0) { - _nativeMethods.BringTarkovToForeground(); + _ = _nativeMethods.BringTarkovToForegroundAsync(); } } diff --git a/ReadySetTarkov/TrayViewModel.cs b/ReadySetTarkov/TrayViewModel.cs index cc3a777..c2e63db 100644 --- a/ReadySetTarkov/TrayViewModel.cs +++ b/ReadySetTarkov/TrayViewModel.cs @@ -10,8 +10,7 @@ namespace ReadySetTarkov; -[ObservableObject] -public partial class TrayViewModel : ITray +public partial class TrayViewModel : ObservableObject, ITray { private readonly ISettingsProvider _settingsProvider; private readonly Lazy _coreService; @@ -26,6 +25,7 @@ public TrayViewModel(ISettingsProvider settingsProvider, Lazy core _hostApplicationLifetime = hostApplicationLifetime; TimeLeftOptions = new ObservableCollection { + new TimeLeftOption(settingsProvider, -1), new TimeLeftOption(settingsProvider, 20), new TimeLeftOption(settingsProvider, 10), new TimeLeftOption(settingsProvider, 5), @@ -46,11 +46,7 @@ public string CurrentIcon public bool Visible { get; set; } - public bool SetTopMost - { - get => _settingsProvider.Settings.SetTopMost; - set => _settingsProvider.Settings.SetTopMost = value; - } + public bool SetTopMost => _settingsProvider.Settings.WithSecondsLeft >= 0; public bool FlashTaskbar { @@ -79,14 +75,13 @@ public string Status public void SetStatus(string text) => Status = text; - [ICommand] + [RelayCommand] private void Reset() => _ = _coreService.Value.ResetAsync(); - [ICommand] + [RelayCommand] private void Exit() => _hostApplicationLifetime.StopApplication(); - [ObservableObject] - public partial class TimeLeftOption + public partial class TimeLeftOption : ObservableObject { private readonly ISettingsProvider _settingsProvider; @@ -95,6 +90,11 @@ public TimeLeftOption(ISettingsProvider settingsProvider, int value) _settingsProvider = settingsProvider; Header = $"{value}s left"; Value = value; + + if (value < 0) + { + Header += " (disabled)"; + } } public string Header { get; } @@ -111,7 +111,7 @@ public bool IsChecked } } - [ICommand] + [RelayCommand] private void SetTimeLeft() => IsChecked = !IsChecked; } } diff --git a/ReadySetTarkov/Utility/INativeMethods.cs b/ReadySetTarkov/Utility/INativeMethods.cs index 7d5262b..f9c1374 100644 --- a/ReadySetTarkov/Utility/INativeMethods.cs +++ b/ReadySetTarkov/Utility/INativeMethods.cs @@ -1,8 +1,10 @@ -namespace ReadySetTarkov.Utility; +using System.Threading.Tasks; + +namespace ReadySetTarkov.Utility; public interface INativeMethods { - void BringTarkovToForeground(); + Task BringTarkovToForegroundAsync(); void FlashTarkov(); string GetProcessFilename(uint processId); uint GetTarkovProcId(); diff --git a/ReadySetTarkov/Utility/IUser32.cs b/ReadySetTarkov/Utility/IUser32.cs index 9240c6e..861f70a 100644 --- a/ReadySetTarkov/Utility/IUser32.cs +++ b/ReadySetTarkov/Utility/IUser32.cs @@ -1,17 +1,65 @@ -namespace ReadySetTarkov.Utility; +using Windows.Win32; +using Windows.Win32.Foundation; -public interface IUser32 +namespace ReadySetTarkov.Utility; + +internal interface IUser32 { + void AttachThreadInput(uint attachId, uint attachToId, bool attach); nint FindWindow(string className, string windowName); void FlashWindow(nint hWnd, bool invert); - nint GetForegroundWindow(); int GetWindowLong(nint hWnd); - uint GetWindowThreadProcessId(nint hWnd); + (uint threadId, uint processId) GetWindowThreadProcessId(nint hWnd); bool IsWindow(nint hWnd); nint SetActiveWindow(nint hWnd); nint SetFocus(nint hWnd); - bool SetForegroundWindow(nint hWnd); bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "VSTHRD200:Use \"Async\" suffix for async methods", Justification = "PInvoke method name")] bool ShowWindowAsync(nint hWnd, bool showDefault); + bool ShowWindow(nint hWnd, Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD nCmdShow); + bool IsIconic(nint hWnd); + + ISystemParametersInfo SystemParametersInfo { get; } + IWindowForegrounding WindowForegrounding { get; } +} + +internal interface ISystemParametersInfo +{ + uint GetForegroundLockTimeout(); + void SetForegroundLockTimeout(uint timeout); +} + +class SystemParameters : ISystemParametersInfo +{ + public unsafe uint GetForegroundLockTimeout() + { + uint timeout = 0; + PInvoke.SystemParametersInfo(Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETFOREGROUNDLOCKTIMEOUT, 0, &timeout, 0); + return timeout; + } + public unsafe void SetForegroundLockTimeout(uint timeout) + { + PInvoke.SystemParametersInfo(Windows.Win32.UI.WindowsAndMessaging.SYSTEM_PARAMETERS_INFO_ACTION.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, &timeout, 0); + } +} + +internal interface IWindowForegrounding +{ + void AllowSetForegroundWindow(); + nint GetForegroundWindow(); + bool SetForegroundWindow(nint hWnd); + void SetForegroundWindowLock(); + void SetForegroundWindowUnlock(); +} + +/// +/// User32 invokes related to foreground window methods +/// +class WindowForegrounding : IWindowForegrounding +{ + public void AllowSetForegroundWindow() => PInvoke.AllowSetForegroundWindow(0xFFFF_FFFF /*ASFW_ANY*/); + public nint GetForegroundWindow() => PInvoke.GetForegroundWindow(); + public bool SetForegroundWindow(nint hWnd) => PInvoke.SetForegroundWindow((HWND)hWnd); + public void SetForegroundWindowLock() => PInvoke.LockSetForegroundWindow(Windows.Win32.UI.WindowsAndMessaging.FOREGROUND_WINDOW_LOCK_CODE.LSFW_LOCK); + public void SetForegroundWindowUnlock() => PInvoke.LockSetForegroundWindow(Windows.Win32.UI.WindowsAndMessaging.FOREGROUND_WINDOW_LOCK_CODE.LSFW_UNLOCK); } diff --git a/ReadySetTarkov/Utility/Kernel32.cs b/ReadySetTarkov/Utility/Kernel32.cs index cc910a5..4bb6236 100644 --- a/ReadySetTarkov/Utility/Kernel32.cs +++ b/ReadySetTarkov/Utility/Kernel32.cs @@ -11,6 +11,7 @@ public interface IKernel32 { SafeHandle OpenProcess(bool inheritHandle, uint processId); string QueryFullProcessImageName(SafeHandle process); + uint GetCurrentThreadId(); } public class Kernel32 : IKernel32 @@ -38,4 +39,6 @@ public SafeHandle OpenProcess(bool inheritHandle, uint processId) Windows.Win32.Foundation.HANDLE handle = PInvoke.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_LIMITED_INFORMATION, inherit, processId); return new Microsoft.Win32.SafeHandles.SafeProcessHandle(handle.Value, true); } + + public uint GetCurrentThreadId() => PInvoke.GetCurrentThreadId(); } diff --git a/ReadySetTarkov/Utility/NativeMethods.cs b/ReadySetTarkov/Utility/NativeMethods.cs index 778c300..bcb7b41 100644 --- a/ReadySetTarkov/Utility/NativeMethods.cs +++ b/ReadySetTarkov/Utility/NativeMethods.cs @@ -1,17 +1,26 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; + +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.Threading; + +using Windows.Win32; +using Windows.Win32.Foundation; namespace ReadySetTarkov.Utility; /// -/// PInvoke area. See NativeMethods.txt for methods that are beingn generated by CsWin32. +/// PInvoke area. See NativeMethods.txt for methods that are being generated by CsWin32. /// -public class NativeMethods : INativeMethods +internal class NativeMethods : INativeMethods { - public NativeMethods(IKernel32 kernel32, IUser32 user32) + public NativeMethods(ILogger logger, IKernel32 kernel32, IUser32 user32, JoinableTaskFactory joinableTaskFactory) { + _logger = logger; _kernel32 = kernel32; _user32 = user32; + _joinableTaskFactory = joinableTaskFactory; } private static DateTime s_lastCheck; @@ -20,10 +29,12 @@ public NativeMethods(IKernel32 kernel32, IUser32 user32) private static readonly Dictionary s_windowNameCache = new(); private static readonly string[] s_windowNames = { "EscapeFromTarkov" }; + private readonly ILogger _logger; private readonly IKernel32 _kernel32; private readonly IUser32 _user32; + private readonly JoinableTaskFactory _joinableTaskFactory; - public bool IsTarkovInForeground() => _user32.GetForegroundWindow() == GetTarkovWindow(); + public bool IsTarkovInForeground() => _user32.WindowForegrounding.GetForegroundWindow() == GetTarkovWindow(); public nint GetTarkovWindow() { @@ -65,14 +76,9 @@ public nint GetTarkovWindow() return s_tarkWindow; } - public uint GetTarkovProcId() => s_tarkWindow == IntPtr.Zero ? 0 : _user32.GetWindowThreadProcessId(s_tarkWindow); + public uint GetTarkovProcId() => s_tarkWindow == IntPtr.Zero ? 0 : _user32.GetWindowThreadProcessId(s_tarkWindow).processId; - private static readonly nint HWND_TOPMOST = new IntPtr(-1); - private static readonly nint HWND_NOTOPMOST = new IntPtr(-2); - private const uint SWP_NOSIZE = 0x0001; - private const uint SWP_NOMOVE = 0x0002; - private const uint TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE; - public void BringTarkovToForeground() + public async Task BringTarkovToForegroundAsync() { nint tHandle = GetTarkovWindow(); if (tHandle == IntPtr.Zero) @@ -80,11 +86,9 @@ public void BringTarkovToForeground() return; } - _ = _user32.SetWindowPos(tHandle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); - _ = _user32.SetWindowPos(tHandle, HWND_NOTOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS); - _ = _user32.SetForegroundWindow(tHandle); - _ = _user32.SetFocus(tHandle); - _ = _user32.SetActiveWindow(tHandle); + await _joinableTaskFactory.SwitchToMainThreadAsync(); + + ForceWindowIntoForeground((HWND)tHandle); } public void FlashTarkov() => _user32.FlashWindow(GetTarkovWindow(), false); @@ -94,4 +98,34 @@ public string GetProcessFilename(uint processId) System.Runtime.InteropServices.SafeHandle? handle = _kernel32.OpenProcess(false, processId); return _kernel32.QueryFullProcessImageName(handle); } + + public unsafe void ForceWindowIntoForeground(HWND window) + { + uint currentThread = _kernel32.GetCurrentThreadId(); + + nint activeWindow = _user32.WindowForegrounding.GetForegroundWindow(); + + (uint activeThread, _) = _user32.GetWindowThreadProcessId(activeWindow); + (uint windowThread, _) = _user32.GetWindowThreadProcessId(activeWindow); + + if (currentThread != activeThread) + _user32.AttachThreadInput(currentThread, activeThread, true); + if (windowThread != currentThread) + _user32.AttachThreadInput(windowThread, currentThread, true); + + uint oldTimeout = _user32.SystemParametersInfo.GetForegroundLockTimeout(); + _user32.SystemParametersInfo.SetForegroundLockTimeout(0); + _user32.WindowForegrounding.SetForegroundWindowUnlock(); + _user32.WindowForegrounding.AllowSetForegroundWindow(); + + _user32.WindowForegrounding.SetForegroundWindow(window); + _user32.ShowWindow(window, Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_RESTORE); + + _user32.SystemParametersInfo.SetForegroundLockTimeout(oldTimeout); + + if (currentThread != activeThread) + _user32.AttachThreadInput(currentThread, activeThread, false); + if (windowThread != currentThread) + _user32.AttachThreadInput(windowThread, currentThread, false); + } } diff --git a/ReadySetTarkov/Utility/User32.cs b/ReadySetTarkov/Utility/User32.cs index 02b1ce8..2715833 100644 --- a/ReadySetTarkov/Utility/User32.cs +++ b/ReadySetTarkov/Utility/User32.cs @@ -5,11 +5,18 @@ namespace ReadySetTarkov.Utility; -public class User32 : IUser32 +internal class User32 : IUser32 { - public nint GetForegroundWindow() => PInvoke.GetForegroundWindow(); + public User32(ISystemParametersInfo systemParametersInfo, IWindowForegrounding windowForegrounding) + { + SystemParametersInfo = systemParametersInfo; + WindowForegrounding = windowForegrounding; + } + + public ISystemParametersInfo SystemParametersInfo { get; } + public IWindowForegrounding WindowForegrounding { get; } - public bool SetForegroundWindow(nint hWnd) => PInvoke.SetForegroundWindow((HWND)hWnd); + public void AttachThreadInput(uint idAttach, uint to, bool attach) => PInvoke.AttachThreadInput(idAttach, to, attach); public bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags) => PInvoke.SetWindowPos((HWND)hWnd, (HWND)hWndInsertAfter, x, y, cx, cy, (SET_WINDOW_POS_FLAGS)uFlags); @@ -38,11 +45,14 @@ public bool ShowWindowAsync(nint hWnd, bool showDefault) public bool IsWindow(nint hWnd) => PInvoke.IsWindow((HWND)hWnd); - public unsafe uint GetWindowThreadProcessId(nint hWnd) + public unsafe (uint threadId, uint processId) GetWindowThreadProcessId(nint hWnd) { uint processId = 0; - _ = PInvoke.GetWindowThreadProcessId((HWND)hWnd, &processId); - - return processId; + var threadId = PInvoke.GetWindowThreadProcessId((HWND)hWnd, &processId); + return (threadId, processId); } + + public bool ShowWindow(nint hWnd, SHOW_WINDOW_CMD nCmdShow) => PInvoke.ShowWindow((HWND)hWnd, nCmdShow); + + public bool IsIconic(nint hWnd) => PInvoke.IsIconic((HWND)hWnd); }