diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props index c97a124..9aad919 100644 --- a/Source/Directory.Build.props +++ b/Source/Directory.Build.props @@ -16,8 +16,7 @@ true - - false + true diff --git a/Source/Launchbar.sln b/Source/Launchbar.sln index 2f5b708..569730e 100644 --- a/Source/Launchbar.sln +++ b/Source/Launchbar.sln @@ -1,6 +1,6 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 +# 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Launchbar", "Launchbar\Launchbar.csproj", "{55B26A32-A0F3-4E58-8B0F-D992D199069F}" diff --git a/Source/Launchbar/App.xaml.cs b/Source/Launchbar/App.xaml.cs index 82b5c49..03328ba 100644 --- a/Source/Launchbar/App.xaml.cs +++ b/Source/Launchbar/App.xaml.cs @@ -10,10 +10,7 @@ namespace Launchbar; -/// -/// Interaction logic for App.xaml -/// -public sealed partial class App : Application +public sealed partial class App : Application, IDisposable { #if DEBUG private const string MutexName = @"Global\LaunchbarSingleInstanceMutex(Debug)"; @@ -25,11 +22,7 @@ public sealed partial class App : Application private static SplashScreen? splashScreen; - // ReSharper disable NotAccessedField.Local -#pragma warning disable IDE0052 // Remove unread private members - We need to keep the mutex alive during the lifetime of the app. private readonly Mutex instanceMutex; -#pragma warning restore IDE0052 // Remove unread private members - // ReSharper restore NotAccessedField.Local private WindowBar? barLeft; @@ -109,7 +102,7 @@ public App() setting.PropertyChanged += this.settingsPropertyChanged; - this.contextMenu = this.FindResource("contextMenuTemplate") as ContextMenu ?? throw new InvalidOperationException("Missing 'contextMenuTemplate'."); + this.contextMenu = this.FindResource("ContextMenuTemplate") as ContextMenu ?? throw new InvalidOperationException("Missing 'contextMenuTemplate'."); this.forceContextMenuLayout(); try @@ -286,4 +279,16 @@ public static ContextMenu RequestContextMenu() // Attach to this object. return app.contextMenu; } + + protected override void OnExit(ExitEventArgs e) + { + base.OnExit(e); + + this.instanceMutex.Dispose(); + } + + public void Dispose() + { + this.instanceMutex.Dispose(); + } } \ No newline at end of file diff --git a/Source/Launchbar/Launchbar.csproj b/Source/Launchbar/Launchbar.csproj index e5ea1ba..c4df4bd 100644 --- a/Source/Launchbar/Launchbar.csproj +++ b/Source/Launchbar/Launchbar.csproj @@ -6,13 +6,13 @@ net8.0-windows true WinExe + true Resources\Next.ico - 5.2.0-preview.1 + 5.2.0-preview.2 Launchbar RunIT to the next level. Copyright © Mertsch $([System.DateTime]::UtcNow.Year) - true true @@ -20,10 +20,10 @@ - - + + - + diff --git a/Source/Launchbar/Menu.cs b/Source/Launchbar/Menu.cs index 6435620..c0a65df 100644 --- a/Source/Launchbar/Menu.cs +++ b/Source/Launchbar/Menu.cs @@ -11,14 +11,14 @@ public sealed class Menu : NotifyBase { #region Fields - private MenuEntryCollection entries; + private MenuEntryCollection? entries; #endregion /// /// Gets or sets a list of menu entries. /// - public MenuEntryCollection Entries + public MenuEntryCollection? Entries { get { return this.entries; } set diff --git a/Source/Launchbar/MenuEntryAdvanced.cs b/Source/Launchbar/MenuEntryAdvanced.cs index 1135827..8c1f2ff 100644 --- a/Source/Launchbar/MenuEntryAdvanced.cs +++ b/Source/Launchbar/MenuEntryAdvanced.cs @@ -1,7 +1,6 @@ -using System.Text; +using Launchbar.Win32; using System.Windows.Media; using System.Xml.Serialization; -using Launchbar.Win32; namespace Launchbar; @@ -150,7 +149,7 @@ public void UpdateIcon() { if (p.IsValidFile) // Valid file? { - newIcon = WinHelper.ExtractAssociatedIcon(p.PathAbsolute); + newIcon = WinHelper.ExtractAssociatedIcon(p.PathAbsolute!); // IsValidFile tests the path newType = IconType.Custom; } else if (p.IsValidPath) // Valid dictionary? @@ -181,18 +180,13 @@ public void ChooseIcon() { if (this is Program { IsValidFile: true } p) // When the program path is valid, use that path as default. { - path = p.Path; + path = p.Path!; // IsValidFile tests the path } } - // We need to have a string that is long enough to handle a more complex path than - // the default one (more characters in length). - StringBuilder sb = new StringBuilder(path, 4096); // 4095 + null-char should be enough - - // Methods returns one when pressing OK in the dialog. - if (SafeNativeMethods.PickIconDlg(nint.Zero, sb, (uint)sb.Capacity, ref index) == 1) + if (WinHelper.PickIconDialog(ref path, ref index)) { - this.IconPath = sb.ToString(); //save the information + this.IconPath = path; this.IconIndex = index; } this.UpdateIcon(); diff --git a/Source/Launchbar/Win32/SafeNativeMethods.cs b/Source/Launchbar/Win32/LibraryImport.cs similarity index 95% rename from Source/Launchbar/Win32/SafeNativeMethods.cs rename to Source/Launchbar/Win32/LibraryImport.cs index b751e5f..d65e55a 100644 --- a/Source/Launchbar/Win32/SafeNativeMethods.cs +++ b/Source/Launchbar/Win32/LibraryImport.cs @@ -1,6 +1,4 @@ using System.Runtime.InteropServices; -using System.Security; -using System.Text; // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Global @@ -9,8 +7,7 @@ namespace Launchbar.Win32; -[SuppressUnmanagedCodeSecurity] -internal static partial class SafeNativeMethods +internal static partial class LibraryImport { internal const string User32 = @"user32.dll"; @@ -94,8 +91,8 @@ public static nint SetWindowLongPtr(nint hWnd, int nIndex, nint dwNewLong) /// [in, out] A pointer to an integer that, on entry, specified the index of /// the initial selection. On exit, the integer specifies the index of the icon that was selected. /// Returns 1 if successful; otherwise, 0. - [DllImport(Shell32, EntryPoint = "PickIconDlg", CharSet = CharSet.Unicode)] - public static extern int PickIconDlg(nint hwnd, StringBuilder pszIconPath, uint cchIconPath, ref int piIconIndex); + [LibraryImport(Shell32, EntryPoint = "PickIconDlg", StringMarshalling = StringMarshalling.Utf16)] + public static partial int PickIconDlg(nint hwnd, [In, Out] char[] pszIconPath, uint cchIconPath, ref int piIconIndex); /// /// Destroys an icon and frees any memory the icon occupied. diff --git a/Source/Launchbar/Win32/WinHelper.cs b/Source/Launchbar/Win32/WinHelper.cs index a411516..e57a6dd 100644 --- a/Source/Launchbar/Win32/WinHelper.cs +++ b/Source/Launchbar/Win32/WinHelper.cs @@ -20,11 +20,11 @@ internal static class WinHelper public static BitmapSource? ExtractAssociatedIcon(string path) { int i = 0; - nint hIcon = SafeNativeMethods.ExtractAssociatedIcon(nint.Zero, path, ref i); + nint hIcon = LibraryImport.ExtractAssociatedIcon(nint.Zero, path, ref i); if (hIcon != nint.Zero) { BitmapSource bms = Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, null); - SafeNativeMethods.DestroyIcon(hIcon); + LibraryImport.DestroyIcon(hIcon); return bms; } return null; @@ -39,11 +39,11 @@ internal static class WinHelper /// The extracted icon as . public static BitmapSource? ExtractIcon(string path, int index) { - nint hIcon = SafeNativeMethods.ExtractIcon(nint.Zero, path, (uint)index); + nint hIcon = LibraryImport.ExtractIcon(nint.Zero, path, (uint)index); if (hIcon != nint.Zero) { BitmapSource bms = Imaging.CreateBitmapSourceFromHIcon(hIcon, Int32Rect.Empty, null); - SafeNativeMethods.DestroyIcon(hIcon); + LibraryImport.DestroyIcon(hIcon); return bms; } return null; @@ -55,21 +55,21 @@ internal static class WinHelper /// public static void SendMouseButtonDown(MouseButton button) { - SafeNativeMethods.MOUSEINPUT mi = new SafeNativeMethods.MOUSEINPUT + LibraryImport.MOUSEINPUT mi = new LibraryImport.MOUSEINPUT { dwFlags = button switch { - MouseButton.Left => SafeNativeMethods.MOUSEINPUTFLAGS.MOUSEEVENTF_LEFTDOWN, - MouseButton.Right => SafeNativeMethods.MOUSEINPUTFLAGS.MOUSEEVENTF_RIGHTDOWN, + MouseButton.Left => LibraryImport.MOUSEINPUTFLAGS.MOUSEEVENTF_LEFTDOWN, + MouseButton.Right => LibraryImport.MOUSEINPUTFLAGS.MOUSEEVENTF_RIGHTDOWN, _ => throw new NotSupportedException(), }, }; - SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT mkhInput = new SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT { mi = mi }; + LibraryImport.MOUSEKEYBDHARDWAREINPUT mkhInput = new LibraryImport.MOUSEKEYBDHARDWAREINPUT { mi = mi }; - SafeNativeMethods.INPUT input = new SafeNativeMethods.INPUT { type = SafeNativeMethods.INPUT_TYPE.MOUSE, mkhi = mkhInput }; + LibraryImport.INPUT input = new LibraryImport.INPUT { type = LibraryImport.INPUT_TYPE.MOUSE, mkhi = mkhInput }; - SafeNativeMethods.SendInput(1, ref input, Marshal.SizeOf(input)); + LibraryImport.SendInput(1, ref input, Marshal.SizeOf(input)); } /// @@ -78,21 +78,21 @@ public static void SendMouseButtonDown(MouseButton button) /// public static void SendMouseButtonUp(MouseButton button) { - SafeNativeMethods.MOUSEINPUT mi = new SafeNativeMethods.MOUSEINPUT + LibraryImport.MOUSEINPUT mi = new LibraryImport.MOUSEINPUT { dwFlags = button switch { - MouseButton.Left => SafeNativeMethods.MOUSEINPUTFLAGS.MOUSEEVENTF_LEFTUP, - MouseButton.Right => SafeNativeMethods.MOUSEINPUTFLAGS.MOUSEEVENTF_RIGHTUP, + MouseButton.Left => LibraryImport.MOUSEINPUTFLAGS.MOUSEEVENTF_LEFTUP, + MouseButton.Right => LibraryImport.MOUSEINPUTFLAGS.MOUSEEVENTF_RIGHTUP, _ => throw new NotSupportedException(), }, }; - SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT mkhInput = new SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT { mi = mi }; + LibraryImport.MOUSEKEYBDHARDWAREINPUT mkhInput = new LibraryImport.MOUSEKEYBDHARDWAREINPUT { mi = mi }; - SafeNativeMethods.INPUT input = new SafeNativeMethods.INPUT { type = SafeNativeMethods.INPUT_TYPE.MOUSE, mkhi = mkhInput }; + LibraryImport.INPUT input = new LibraryImport.INPUT { type = LibraryImport.INPUT_TYPE.MOUSE, mkhi = mkhInput }; - SafeNativeMethods.SendInput(1, ref input, Marshal.SizeOf(input)); + LibraryImport.SendInput(1, ref input, Marshal.SizeOf(input)); } /// @@ -101,35 +101,50 @@ public static void SendMouseButtonUp(MouseButton button) /// public static void SendMouseMoveRelative(int x, int y) { - SafeNativeMethods.MOUSEINPUT mi = new SafeNativeMethods.MOUSEINPUT + LibraryImport.MOUSEINPUT mi = new LibraryImport.MOUSEINPUT { - dwFlags = SafeNativeMethods.MOUSEINPUTFLAGS.MOUSEEVENTF_MOVE, + dwFlags = LibraryImport.MOUSEINPUTFLAGS.MOUSEEVENTF_MOVE, dx = x, dy = y, }; - SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT mkhInput = new SafeNativeMethods.MOUSEKEYBDHARDWAREINPUT + LibraryImport.MOUSEKEYBDHARDWAREINPUT mkhInput = new LibraryImport.MOUSEKEYBDHARDWAREINPUT { mi = mi, }; - SafeNativeMethods.INPUT input = new SafeNativeMethods.INPUT + LibraryImport.INPUT input = new LibraryImport.INPUT { - type = SafeNativeMethods.INPUT_TYPE.MOUSE, + type = LibraryImport.INPUT_TYPE.MOUSE, mkhi = mkhInput, }; - SafeNativeMethods.SendInput(1, ref input, Marshal.SizeOf(input)); + LibraryImport.SendInput(1, ref input, Marshal.SizeOf(input)); } public static void SetAsToolWindow(this Window window) { nint handle = new WindowInteropHelper(window).EnsureHandle(); - nint oldFlags = SafeNativeMethods.GetWindowLongPtr(handle, GWL.GWL_EXSTYLE); + nint oldFlags = LibraryImport.GetWindowLongPtr(handle, GWL.GWL_EXSTYLE); if (oldFlags != nint.Zero) { nint newFlags = new nint(oldFlags.ToInt64() | ExtendedWindowStyles.WS_EX_TOOLWINDOW); - SafeNativeMethods.SetWindowLongPtr(handle, GWL.GWL_EXSTYLE, newFlags); + LibraryImport.SetWindowLongPtr(handle, GWL.GWL_EXSTYLE, newFlags); } } + + public static bool PickIconDialog(ref string path, ref int index) + { + // We need to have a string that is long enough to handle a more complex path than the default one (more characters in length). + char[] pathBuffer = new char[32 * 1024]; // https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation + path.CopyTo(pathBuffer); + + // Method returns one when pressing OK in the dialog. + if (LibraryImport.PickIconDlg(nint.Zero, pathBuffer, (uint)pathBuffer.Length, ref index) == 1) + { + path = new string(pathBuffer, 0, Array.IndexOf(pathBuffer, '\0')); // Extract string from null terminated string. + return true; + } + return false; + } } \ No newline at end of file diff --git a/Source/Launchbar/WindowSettings.xaml.cs b/Source/Launchbar/WindowSettings.xaml.cs index b36fcbd..107cf61 100644 --- a/Source/Launchbar/WindowSettings.xaml.cs +++ b/Source/Launchbar/WindowSettings.xaml.cs @@ -243,7 +243,7 @@ private void addMenuEntry(MenuEntry newEntry) } else { - Settings.Default.Menu.Entries.Add(newEntry); + Settings.Default.Menu.Entries?.Add(newEntry); } }