diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
index 76695a4e3..b738b9c88 100644
--- a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
+++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
+using Windows.Win32;
namespace Flow.Launcher.Infrastructure
{
@@ -54,10 +54,6 @@ namespace Flow.Launcher.Infrastructure
return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First;
}
- [DllImport("user32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
-
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
///
@@ -70,9 +66,9 @@ namespace Flow.Launcher.Infrastructure
var index = 0;
var numRemaining = hWnds.Count;
- EnumWindows((wnd, _) =>
+ PInvoke.EnumWindows((wnd, _) =>
{
- var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.ToInt32());
+ var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.Value);
if (searchIndex != -1)
{
z[searchIndex] = index;
diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
index ec35ef9d6..1475252ca 100644
--- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
+++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj
@@ -35,6 +35,10 @@
false
+
+
+
+
@@ -56,6 +60,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
index f847ab189..75af843b9 100644
--- a/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
+++ b/Flow.Launcher.Infrastructure/Hotkey/GlobalHotkey.cs
@@ -1,6 +1,11 @@
-using System;
+using System;
+using System.Diagnostics;
using System.Runtime.InteropServices;
using Flow.Launcher.Plugin;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Input.KeyboardAndMouse;
+using Windows.Win32.UI.WindowsAndMessaging;
namespace Flow.Launcher.Infrastructure.Hotkey
{
@@ -10,44 +15,41 @@ namespace Flow.Launcher.Infrastructure.Hotkey
///
public unsafe class GlobalHotkey : IDisposable
{
- private static readonly IntPtr hookId;
-
-
-
- public delegate bool KeyboardCallback(KeyEvent keyEvent, int vkCode, SpecialKeyState state);
- internal static Func hookedKeyboardCallback;
+ private static readonly UnhookWindowsHookExSafeHandle hookId;
- //Modifier key constants
- private const int VK_SHIFT = 0x10;
- private const int VK_CONTROL = 0x11;
- private const int VK_ALT = 0x12;
- private const int VK_WIN = 91;
+ public delegate bool KeyboardCallback(int keyEvent, int vkCode, SpecialKeyState state);
+ internal static Func hookedKeyboardCallback;
static GlobalHotkey()
{
// Set the hook
- hookId = InterceptKeys.SetHook(& LowLevelKeyboardProc);
+ using Process curProcess = Process.GetCurrentProcess();
+ using ProcessModule curModule = curProcess.MainModule;
+ hookId = PInvoke.SetWindowsHookEx(
+ WINDOWS_HOOK_ID.WH_KEYBOARD_LL,
+ LowLevelKeyboardProc,
+ PInvoke.GetModuleHandle(curModule.ModuleName), 0);
}
public static SpecialKeyState CheckModifiers()
{
SpecialKeyState state = new SpecialKeyState();
- if ((InterceptKeys.GetKeyState(VK_SHIFT) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_SHIFT) & 0x8000) != 0)
{
//SHIFT is pressed
state.ShiftPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_CONTROL) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_CONTROL) & 0x8000) != 0)
{
//CONTROL is pressed
state.CtrlPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_ALT) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_MENU) & 0x8000) != 0)
{
//ALT is pressed
state.AltPressed = true;
}
- if ((InterceptKeys.GetKeyState(VK_WIN) & 0x8000) != 0)
+ if ((PInvoke.GetKeyState((int)VIRTUAL_KEY.VK_LWIN) & 0x8000) != 0)
{
//WIN is pressed
state.WinPressed = true;
@@ -56,33 +58,33 @@ namespace Flow.Launcher.Infrastructure.Hotkey
return state;
}
- [UnmanagedCallersOnly]
- private static IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam)
+ private static LRESULT LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
bool continues = true;
if (nCode >= 0)
{
- if (wParam.ToUInt32() == (int)KeyEvent.WM_KEYDOWN ||
- wParam.ToUInt32() == (int)KeyEvent.WM_KEYUP ||
- wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYDOWN ||
- wParam.ToUInt32() == (int)KeyEvent.WM_SYSKEYUP)
+ var wParamValue = (int)wParam.Value;
+ if (wParamValue == (int)KeyEvent.WM_KEYDOWN ||
+ wParamValue == (int)KeyEvent.WM_KEYUP ||
+ wParamValue == (int)KeyEvent.WM_SYSKEYDOWN ||
+ wParamValue == (int)KeyEvent.WM_SYSKEYUP)
{
if (hookedKeyboardCallback != null)
- continues = hookedKeyboardCallback((KeyEvent)wParam.ToUInt32(), Marshal.ReadInt32(lParam), CheckModifiers());
+ continues = hookedKeyboardCallback((KeyEvent)wParamValue, Marshal.ReadInt32(lParam), CheckModifiers());
}
}
if (continues)
{
- return InterceptKeys.CallNextHookEx(hookId, nCode, wParam, lParam);
+ return PInvoke.CallNextHookEx(hookId, nCode, wParam, lParam);
}
- return (IntPtr)(-1);
+ return new LRESULT(-1);
}
public void Dispose()
{
- InterceptKeys.UnhookWindowsHookEx(hookId);
+ PInvoke.UnhookWindowsHookEx(new HHOOK(hookId.DangerousGetHandle()));
}
~GlobalHotkey()
@@ -90,4 +92,4 @@ namespace Flow.Launcher.Infrastructure.Hotkey
Dispose();
}
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs b/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs
deleted file mode 100644
index d33bac34c..000000000
--- a/Flow.Launcher.Infrastructure/Hotkey/InterceptKeys.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Flow.Launcher.Infrastructure.Hotkey
-{
- internal static unsafe class InterceptKeys
- {
- public delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);
-
- private const int WH_KEYBOARD_LL = 13;
-
- public static IntPtr SetHook(delegate* unmanaged proc)
- {
- using (Process curProcess = Process.GetCurrentProcess())
- using (ProcessModule curModule = curProcess.MainModule)
- {
- return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
- }
- }
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr SetWindowsHookEx(int idHook, delegate* unmanaged lpfn, IntPtr hMod, uint dwThreadId);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool UnhookWindowsHookEx(IntPtr hhk);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- public static extern IntPtr GetModuleHandle(string lpModuleName);
-
- [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
- public static extern short GetKeyState(int keyCode);
- }
-}
\ No newline at end of file
diff --git a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
index 15e306883..95bb25837 100644
--- a/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
+++ b/Flow.Launcher.Infrastructure/Hotkey/KeyEvent.cs
@@ -1,3 +1,5 @@
+using Windows.Win32;
+
namespace Flow.Launcher.Infrastructure.Hotkey
{
public enum KeyEvent
@@ -5,21 +7,21 @@ namespace Flow.Launcher.Infrastructure.Hotkey
///
/// Key down
///
- WM_KEYDOWN = 256,
+ WM_KEYDOWN = (int)PInvoke.WM_KEYDOWN,
///
/// Key up
///
- WM_KEYUP = 257,
+ WM_KEYUP = (int)PInvoke.WM_KEYUP,
///
/// System key up
///
- WM_SYSKEYUP = 261,
+ WM_SYSKEYUP = (int)PInvoke.WM_SYSKEYUP,
///
/// System key down
///
- WM_SYSKEYDOWN = 260
+ WM_SYSKEYDOWN = (int)PInvoke.WM_SYSKEYDOWN
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
index 247238bb6..49d6da8c2 100644
--- a/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
+++ b/Flow.Launcher.Infrastructure/Image/ThumbnailReader.cs
@@ -1,12 +1,19 @@
using System;
using System.Runtime.InteropServices;
using System.IO;
+using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
-using System.Windows;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.UI.Shell;
+using Windows.Win32.Graphics.Gdi;
namespace Flow.Launcher.Infrastructure.Image
{
+ ///
+ /// Subclass of
+ ///
[Flags]
public enum ThumbnailOptions
{
@@ -22,91 +29,13 @@ namespace Flow.Launcher.Infrastructure.Image
{
// Based on https://stackoverflow.com/questions/21751747/extract-thumbnail-for-any-file-in-windows
- private const string IShellItem2Guid = "7E9FB0D3-919F-4307-AB2E-9B1860310C93";
-
- [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- internal static extern int SHCreateItemFromParsingName(
- [MarshalAs(UnmanagedType.LPWStr)] string path,
- IntPtr pbc,
- ref Guid riid,
- [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
-
- [DllImport("gdi32.dll")]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool DeleteObject(IntPtr hObject);
-
- [ComImport]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
- internal interface IShellItem
- {
- void BindToHandler(IntPtr pbc,
- [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
- [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
- out IntPtr ppv);
-
- void GetParent(out IShellItem ppsi);
- void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);
- void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
- void Compare(IShellItem psi, uint hint, out int piOrder);
- };
-
- internal enum SIGDN : uint
- {
- NORMALDISPLAY = 0,
- PARENTRELATIVEPARSING = 0x80018001,
- PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
- DESKTOPABSOLUTEPARSING = 0x80028000,
- PARENTRELATIVEEDITING = 0x80031001,
- DESKTOPABSOLUTEEDITING = 0x8004c000,
- FILESYSPATH = 0x80058000,
- URL = 0x80068000
- }
-
- internal enum HResult
- {
- Ok = 0x0000,
- False = 0x0001,
- InvalidArguments = unchecked((int)0x80070057),
- OutOfMemory = unchecked((int)0x8007000E),
- NoInterface = unchecked((int)0x80004002),
- Fail = unchecked((int)0x80004005),
- ExtractionFailed = unchecked((int)0x8004B200),
- ElementNotFound = unchecked((int)0x80070490),
- TypeElementNotFound = unchecked((int)0x8002802B),
- NoObject = unchecked((int)0x800401E5),
- Win32ErrorCanceled = 1223,
- Canceled = unchecked((int)0x800704C7),
- ResourceInUse = unchecked((int)0x800700AA),
- AccessDenied = unchecked((int)0x80030005)
- }
-
- [ComImportAttribute()]
- [GuidAttribute("bcc18b79-ba16-442f-80c4-8a59c30c463b")]
- [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
- internal interface IShellItemImageFactory
- {
- [PreserveSig]
- HResult GetImage(
- [In, MarshalAs(UnmanagedType.Struct)] NativeSize size,
- [In] ThumbnailOptions flags,
- [Out] out IntPtr phbm);
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct NativeSize
- {
- private int width;
- private int height;
-
- public int Width { set { width = value; } }
- public int Height { set { height = value; } }
- };
+ private static readonly Guid GUID_IShellItem = typeof(IShellItem).GUID;
+ private static readonly HRESULT S_ExtractionFailed = (HRESULT)0x8004B200;
public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options)
{
- IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
+ HBITMAP hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options);
try
{
@@ -115,39 +44,52 @@ namespace Flow.Launcher.Infrastructure.Image
finally
{
// delete HBitmap to avoid memory leaks
- DeleteObject(hBitmap);
+ PInvoke.DeleteObject(hBitmap);
}
}
-
- private static IntPtr GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
- {
- IShellItem nativeShellItem;
- Guid shellItem2Guid = new Guid(IShellItem2Guid);
- int retCode = SHCreateItemFromParsingName(fileName, IntPtr.Zero, ref shellItem2Guid, out nativeShellItem);
- if (retCode != 0)
+ private static unsafe HBITMAP GetHBitmap(string fileName, int width, int height, ThumbnailOptions options)
+ {
+ var retCode = PInvoke.SHCreateItemFromParsingName(
+ fileName,
+ null,
+ GUID_IShellItem,
+ out var nativeShellItem);
+
+ if (retCode != HRESULT.S_OK)
throw Marshal.GetExceptionForHR(retCode);
- NativeSize nativeSize = new NativeSize
+ if (nativeShellItem is not IShellItemImageFactory imageFactory)
{
- Width = width,
- Height = height
- };
-
- IntPtr hBitmap;
- HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap);
-
- // if extracting image thumbnail and failed, extract shell icon
- if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed)
- {
- hr = ((IShellItemImageFactory) nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap);
+ Marshal.ReleaseComObject(nativeShellItem);
+ throw new InvalidOperationException("Failed to get IShellItemImageFactory");
}
- Marshal.ReleaseComObject(nativeShellItem);
+ SIZE size = new SIZE
+ {
+ cx = width,
+ cy = height
+ };
- if (hr == HResult.Ok) return hBitmap;
+ HBITMAP hBitmap = default;
+ try
+ {
+ try
+ {
+ imageFactory.GetImage(size, (SIIGBF)options, &hBitmap);
+ }
+ catch (COMException ex) when (ex.HResult == S_ExtractionFailed && options == ThumbnailOptions.ThumbnailOnly)
+ {
+ // Fallback to IconOnly if ThumbnailOnly fails
+ imageFactory.GetImage(size, (SIIGBF)ThumbnailOptions.IconOnly, &hBitmap);
+ }
+ }
+ finally
+ {
+ Marshal.ReleaseComObject(nativeShellItem);
+ }
- throw new COMException($"Error while extracting thumbnail for {fileName}", Marshal.GetExceptionForHR((int)hr));
+ return hBitmap;
}
}
-}
\ No newline at end of file
+}
diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt
new file mode 100644
index 000000000..a083f79aa
--- /dev/null
+++ b/Flow.Launcher.Infrastructure/NativeMethods.txt
@@ -0,0 +1,20 @@
+SHCreateItemFromParsingName
+DeleteObject
+IShellItem
+IShellItemImageFactory
+ExtractionFailed
+S_OK
+
+SetWindowsHookEx
+UnhookWindowsHookEx
+CallNextHookEx
+GetModuleHandle
+GetKeyState
+VIRTUAL_KEY
+
+WM_KEYDOWN
+WM_KEYUP
+WM_SYSKEYDOWN
+WM_SYSKEYUP
+
+EnumWindows
\ No newline at end of file