From 333f555d07d2d3bbdbbc9f3f7c48be6e98b317fb Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 25 Dec 2024 19:20:12 -0800 Subject: [PATCH 01/12] Refactor MainWindow for readability Signed-off-by: Ryan Luu --- FluentAutoClicker/App.xaml.cs | 5 ++--- FluentAutoClicker/MainPage.xaml.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs index 76a7709..34d7141 100644 --- a/FluentAutoClicker/App.xaml.cs +++ b/FluentAutoClicker/App.xaml.cs @@ -27,7 +27,7 @@ namespace FluentAutoClicker; /// public partial class App : Application { - public static Window Window { get; private set; } = null!; + public static MainWindow MainWindow = new(); /// /// Initializes the singleton application object. This is the first line of authored code @@ -44,7 +44,6 @@ public App() /// Details about the launch request and process. protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) { - Window = new MainWindow(); - Window.Activate(); + MainWindow.Activate(); } } \ No newline at end of file diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 62f5a78..96c383b 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -40,7 +40,10 @@ public MainPage() private void MainPage_Loaded(object sender, RoutedEventArgs e) { - WindowMessageHook hook = new(App.Window); + MainWindow window = App.MainWindow; + nint hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window); + + WindowMessageHook hook = new(window); Unloaded += (s, e) => hook.Dispose(); hook.Message += (s, e) => @@ -54,16 +57,15 @@ private void MainPage_Loaded(object sender, RoutedEventArgs e) } }; - nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); int id = 1; // Register the F6 key - if (!RegisterHotKey(hwnd, id, Mod.ModNoRepeat, VirtualKey.F6)) + if (!RegisterHotKey(hWnd, id, Mod.ModNoRepeat, VirtualKey.F6)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } - Unloaded += (s, e) => UnregisterHotKey(hwnd, id); + Unloaded += (s, e) => UnregisterHotKey(hWnd, id); } private void SetControlsEnabled(bool isEnabled) From fec7b9b6e8ad83bd53a775b00ef72cb5185bc9c3 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 25 Dec 2024 23:01:22 -0800 Subject: [PATCH 02/12] Normalize DllImport import ending --- FluentAutoClicker/Helpers/WindowMessageHook.cs | 6 +++--- FluentAutoClicker/MainPage.xaml.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FluentAutoClicker/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs index 5c8b075..a700169 100644 --- a/FluentAutoClicker/Helpers/WindowMessageHook.cs +++ b/FluentAutoClicker/Helpers/WindowMessageHook.cs @@ -80,13 +80,13 @@ protected virtual void Dispose(bool disposing) ~WindowMessageHook() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } - [DllImport("comctl32", SetLastError = true)] + [DllImport("comctl32.dll", SetLastError = true)] private static extern bool SetWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass, uint dwRefData); - [DllImport("comctl32", SetLastError = true)] + [DllImport("comctl32.dll", SetLastError = true)] private static extern nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam); - [DllImport("comctl32", SetLastError = true)] + [DllImport("comctl32.dll", SetLastError = true)] private static extern bool RemoveWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass); private static nint GetHandle(Window window) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 96c383b..0f006a2 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -155,10 +155,10 @@ private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) } // interop code for Windows API hotkey functions - [DllImport("user32", SetLastError = true)] + [DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(nint hWnd, int id, Mod fsModifiers, VirtualKey vk); - [DllImport("user32", SetLastError = true)] + [DllImport("user32.dll", SetLastError = true)] private static extern bool UnregisterHotKey(nint hWnd, int id); [Flags] From c3d37c90a623ac4bcfb07ec7c4bb80f001290332 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sat, 4 Jan 2025 02:15:58 -0800 Subject: [PATCH 03/12] Refactor to use LibraryImport instead of DllImport --- FluentAutoClicker/FluentAutoClicker.csproj | 1 + FluentAutoClicker/Helpers/AutoClicker.cs | 6 ++--- .../Helpers/WindowMessageHook.cs | 16 +++++++------ FluentAutoClicker/MainPage.xaml.cs | 10 ++++---- FluentAutoClicker/Program.cs | 24 ++++++++++--------- 5 files changed, 32 insertions(+), 25 deletions(-) diff --git a/FluentAutoClicker/FluentAutoClicker.csproj b/FluentAutoClicker/FluentAutoClicker.csproj index 0e586ee..35191d6 100644 --- a/FluentAutoClicker/FluentAutoClicker.csproj +++ b/FluentAutoClicker/FluentAutoClicker.csproj @@ -11,6 +11,7 @@ win-$(Platform).pubxml enable enable + true true true DISABLE_XAML_GENERATED_MAIN diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs index 0cc2be2..796143f 100644 --- a/FluentAutoClicker/Helpers/AutoClicker.cs +++ b/FluentAutoClicker/Helpers/AutoClicker.cs @@ -22,10 +22,10 @@ namespace FluentAutoClicker.Helpers; /// /// Helper for creating threads to synthesize mouse input. /// -public static class AutoClicker +public static partial class AutoClicker { - [DllImport("user32.dll", SetLastError = true)] - private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize); + [LibraryImport("user32.dll", SetLastError = true)] + private static partial uint SendInput(uint nInputs, Input[] pInputs, int cbSize); private static Thread? _autoClickerThread; private static bool _isAutoClickerRunning; diff --git a/FluentAutoClicker/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs index a700169..229d813 100644 --- a/FluentAutoClicker/Helpers/WindowMessageHook.cs +++ b/FluentAutoClicker/Helpers/WindowMessageHook.cs @@ -22,7 +22,7 @@ namespace FluentAutoClicker.Helpers; -public class WindowMessageHook : IEquatable, IDisposable +public partial class WindowMessageHook : IEquatable, IDisposable { private delegate nint Subclassproc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData); @@ -80,14 +80,16 @@ protected virtual void Dispose(bool disposing) ~WindowMessageHook() { Dispose(disposing: false); } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } - [DllImport("comctl32.dll", SetLastError = true)] - private static extern bool SetWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass, uint dwRefData); + [LibraryImport("comctl32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass, uint dwRefData); - [DllImport("comctl32.dll", SetLastError = true)] - private static extern nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam); + [LibraryImport("comctl32.dll", SetLastError = true)] + private static partial nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam); - [DllImport("comctl32.dll", SetLastError = true)] - private static extern bool RemoveWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass); + [LibraryImport("comctl32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool RemoveWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass); private static nint GetHandle(Window window) { diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 0f006a2..8a1b174 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -155,11 +155,13 @@ private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) } // interop code for Windows API hotkey functions - [DllImport("user32.dll", SetLastError = true)] - private static extern bool RegisterHotKey(nint hWnd, int id, Mod fsModifiers, VirtualKey vk); + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool RegisterHotKey(nint hWnd, int id, Mod fsModifiers, VirtualKey vk); - [DllImport("user32.dll", SetLastError = true)] - private static extern bool UnregisterHotKey(nint hWnd, int id); + [LibraryImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool UnregisterHotKey(nint hWnd, int id); [Flags] private enum Mod diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs index d535db8..e5e2ec0 100644 --- a/FluentAutoClicker/Program.cs +++ b/FluentAutoClicker/Program.cs @@ -26,7 +26,7 @@ namespace FluentAutoClicker; /// /// Customized Program.cs file to implement single-instancing in a WinUI app with C#. Single-instanced apps only allow one instance of the app running at a time. /// -public class Program +public partial class Program { [STAThread] private static int Main(string[] args) @@ -68,21 +68,23 @@ private static bool DecideRedirection() return isRedirect; } - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr CreateEvent( - IntPtr lpEventAttributes, bool bManualReset, - bool bInitialState, string? lpName); + [LibraryImport("kernel32.dll", EntryPoint = "CreateEventW", StringMarshalling = StringMarshalling.Utf16)] + private static partial IntPtr CreateEvent( + IntPtr lpEventAttributes, [MarshalAs(UnmanagedType.Bool)] bool bManualReset, + [MarshalAs(UnmanagedType.Bool)] bool bInitialState, string? lpName); - [DllImport("kernel32.dll")] - private static extern bool SetEvent(IntPtr hEvent); + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetEvent(IntPtr hEvent); - [DllImport("ole32.dll")] - private static extern uint CoWaitForMultipleObjects( + [LibraryImport("ole32.dll")] + private static partial uint CoWaitForMultipleObjects( uint dwFlags, uint dwMilliseconds, ulong nHandles, IntPtr[] pHandles, out uint dwIndex); - [DllImport("user32.dll")] - private static extern bool SetForegroundWindow(IntPtr hWnd); + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetForegroundWindow(IntPtr hWnd); private static IntPtr _redirectEventHandle = IntPtr.Zero; From befaad91dc8730934fa78dcdb47a8e278dd56416 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sun, 5 Jan 2025 12:44:43 -0800 Subject: [PATCH 04/12] Update fsModifiers type to use VirtualKeyModifiers --- FluentAutoClicker/MainPage.xaml.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 8a1b174..74456e9 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -60,7 +60,7 @@ private void MainPage_Loaded(object sender, RoutedEventArgs e) int id = 1; // Register the F6 key - if (!RegisterHotKey(hWnd, id, Mod.ModNoRepeat, VirtualKey.F6)) + if (!RegisterHotKey(hWnd, id, VirtualKeyModifiers.None, VirtualKey.F6)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } @@ -157,21 +157,12 @@ private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) // interop code for Windows API hotkey functions [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool RegisterHotKey(nint hWnd, int id, Mod fsModifiers, VirtualKey vk); + private static partial bool RegisterHotKey(nint hWnd, int id, VirtualKeyModifiers fsModifiers, VirtualKey vk); [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool UnregisterHotKey(nint hWnd, int id); - [Flags] - private enum Mod - { - ModAlt = 0x1, - ModControl = 0x2, - ModShift = 0x4, - ModWin = 0x8, - ModNoRepeat = 0x4000, - } private void ClickOffsetCheckBox_Unchecked(object sender, RoutedEventArgs e) { ClickOffsetAmount.IsEnabled = false; From 77b5ba169ba9c4427ba85e40553b71c78265099f Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sun, 5 Jan 2025 12:53:08 -0800 Subject: [PATCH 05/12] Add doc comments for hotkeys --- FluentAutoClicker/MainPage.xaml.cs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 74456e9..48b63ad 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -154,11 +154,38 @@ private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) ClickRepeatAmount.IsEnabled = true; } - // interop code for Windows API hotkey functions + /// + /// Define a system-wide hot key. + /// + /// + /// A handle to the window that will receive WM_HOTKEY messages generated by the + /// hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the + /// message queue of the calling thread and must be processed in the message loop. + /// + /// + /// The identifier of the hot key. If the hWnd parameter is NULL, then the hot + /// key is associated with the current thread rather than with a particular + /// window. + /// + /// + /// The keys that must be pressed in combination with the key specified by the + /// uVirtKey parameter in order to generate the WM_HOTKEY message. + /// + /// The virtual-key code of the hot key. [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool RegisterHotKey(nint hWnd, int id, VirtualKeyModifiers fsModifiers, VirtualKey vk); + /// + /// Frees a hot key previously registered by the calling thread. + /// + /// + /// A handle to the window associated with the hot key to be freed. This parameter + /// should be NULL if the hot key is not associated with a window. + /// + /// + /// The identifier of the hot key to be freed. + /// [LibraryImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool UnregisterHotKey(nint hWnd, int id); From dbc914b72266c40383ec8bcb3ef183d765d3ce13 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Mon, 6 Jan 2025 15:00:46 -0800 Subject: [PATCH 06/12] Interop with PInvoke --- FluentAutoClicker/FluentAutoClicker.csproj | 4 + .../Helpers/WindowMessageHook.cs | 150 ------------------ FluentAutoClicker/MainPage.xaml.cs | 87 ++++------ FluentAutoClicker/NativeMethods.txt | 4 + 4 files changed, 36 insertions(+), 209 deletions(-) delete mode 100644 FluentAutoClicker/Helpers/WindowMessageHook.cs create mode 100644 FluentAutoClicker/NativeMethods.txt diff --git a/FluentAutoClicker/FluentAutoClicker.csproj b/FluentAutoClicker/FluentAutoClicker.csproj index 35191d6..7498c9e 100644 --- a/FluentAutoClicker/FluentAutoClicker.csproj +++ b/FluentAutoClicker/FluentAutoClicker.csproj @@ -38,6 +38,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/FluentAutoClicker/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs deleted file mode 100644 index 229d813..0000000 --- a/FluentAutoClicker/Helpers/WindowMessageHook.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (C) 2024 Ryan Luu -// -// This file is part of Fluent Auto Clicker. -// -// Fluent Auto Clicker is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Fluent Auto Clicker is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with Fluent Auto Clicker. If not, see . - -using Microsoft.UI.Xaml; -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Runtime.InteropServices; - -namespace FluentAutoClicker.Helpers; - -public partial class WindowMessageHook : IEquatable, IDisposable -{ - private delegate nint Subclassproc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData); - - private static readonly ConcurrentDictionary Hooks = new(); - private static readonly Subclassproc Proc = SubclassProc; - - public event EventHandler? Message; - private nint _hWnd; - - public WindowMessageHook(Window window) : this(GetHandle(window)) { } - public WindowMessageHook(nint hWnd) - { - if (hWnd == 0) - { - throw new ArgumentException(null, nameof(hWnd)); - } - - _hWnd = hWnd; - _ = Hooks.AddOrUpdate(hWnd, this, (k, o) => - { - if (Equals(o)) - { - return o; - } - - o.Dispose(); - return this; - }); - if (!SetWindowSubclass(hWnd, Proc, 0, 0)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } - } - - protected virtual void OnMessage(object sender, MessageEventArgs e) - { - Message?.Invoke(sender, e); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposing) - { - return; - } - - nint hWnd = Interlocked.Exchange(ref _hWnd, IntPtr.Zero); - if (hWnd != IntPtr.Zero) - { - _ = RemoveWindowSubclass(hWnd, Proc, 0); - _ = Hooks.Remove(hWnd, out _); - } - } - - ~WindowMessageHook() { Dispose(disposing: false); } - public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } - - [LibraryImport("comctl32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool SetWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass, uint dwRefData); - - [LibraryImport("comctl32.dll", SetLastError = true)] - private static partial nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam); - - [LibraryImport("comctl32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool RemoveWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass); - - private static nint GetHandle(Window window) - { - ArgumentNullException.ThrowIfNull(window); - return WinRT.Interop.WindowNative.GetWindowHandle(window); - } - - private static nint SubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData) - { - if (Hooks.TryGetValue(hWnd, out WindowMessageHook? hook)) - { - MessageEventArgs e = new(hWnd, uMsg, wParam, lParam); - hook.OnMessage(hook, e); - if (e.Result.HasValue) - { - return e.Result.Value; - } - } - return DefSubclassProc(hWnd, uMsg, wParam, lParam); - } - - public override int GetHashCode() - { - return _hWnd.GetHashCode(); - } - - public override string? ToString() - { - return _hWnd.ToString(); - } - - public override bool Equals(object? obj) - { - return Equals(obj as WindowMessageHook); - } - - public virtual bool Equals(WindowMessageHook? other) - { - return other != null && _hWnd.Equals(other._hWnd); - } -} - -public class MessageEventArgs : EventArgs -{ - public MessageEventArgs(nint hWnd, uint uMsg, nint wParam, nint lParam) - { - HWnd = hWnd; - Message = uMsg; - WParam = wParam; - LParam = lParam; - } - - public nint HWnd { get; } - public uint Message { get; } - public nint WParam { get; } - public nint LParam { get; } - public virtual nint? Result { get; set; } -} diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 48b63ad..429906b 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -17,13 +17,14 @@ using FluentAutoClicker.Helpers; using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; -using System.ComponentModel; using System.Globalization; using System.Runtime.InteropServices; -using Windows.System; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using Windows.Win32.UI.WindowsAndMessaging; namespace FluentAutoClicker; @@ -38,34 +39,38 @@ public MainPage() Loaded += MainPage_Loaded; } - private void MainPage_Loaded(object sender, RoutedEventArgs e) - { - MainWindow window = App.MainWindow; - nint hWnd = WinRT.Interop.WindowNative.GetWindowHandle(window); + private WNDPROC wndProc = null!; - WindowMessageHook hook = new(window); - Unloaded += (s, e) => hook.Dispose(); + private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam) + { + uint WM_HOTKEY = 0x0312; // HotKey Window Message - hook.Message += (s, e) => + if (Msg == WM_HOTKEY) { - const int wmHotkey = 0x312; - if (e.Message == wmHotkey) + if (StartToggleButton.IsEnabled) { - // Toggle the StartToggleButton when the hotkey is pressed - ToggleButtonAutomationPeer pattern = (ToggleButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(StartToggleButton).GetPattern(PatternInterface.Toggle); - pattern.Toggle(); + StartToggleButton.IsChecked = !StartToggleButton.IsChecked; } - }; + } - int id = 1; + return PInvoke.CallWindowProc(wndProc, hWnd, Msg, wParam, lParam); + } - // Register the F6 key - if (!RegisterHotKey(hWnd, id, VirtualKeyModifiers.None, VirtualKey.F6)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); - } + private void MainPage_Loaded(object sender, RoutedEventArgs e) + { + // Get window handle + MainWindow window = App.MainWindow; + HWND hWnd = new(WinRT.Interop.WindowNative.GetWindowHandle(window)); + + // Register hotkey + int id = 0x0000; + _ = PInvoke.RegisterHotKey(hWnd, id, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6 - Unloaded += (s, e) => UnregisterHotKey(hWnd, id); + // Add hotkey function pointer to window procedure + WNDPROC hotKeyDelegate = HotKeyProc; + nint hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyDelegate); + nint wndPtr = PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyProcPtr); + wndProc = Marshal.GetDelegateForFunctionPointer(wndPtr); } private void SetControlsEnabled(bool isEnabled) @@ -154,42 +159,6 @@ private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) ClickRepeatAmount.IsEnabled = true; } - /// - /// Define a system-wide hot key. - /// - /// - /// A handle to the window that will receive WM_HOTKEY messages generated by the - /// hot key. If this parameter is NULL, WM_HOTKEY messages are posted to the - /// message queue of the calling thread and must be processed in the message loop. - /// - /// - /// The identifier of the hot key. If the hWnd parameter is NULL, then the hot - /// key is associated with the current thread rather than with a particular - /// window. - /// - /// - /// The keys that must be pressed in combination with the key specified by the - /// uVirtKey parameter in order to generate the WM_HOTKEY message. - /// - /// The virtual-key code of the hot key. - [LibraryImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool RegisterHotKey(nint hWnd, int id, VirtualKeyModifiers fsModifiers, VirtualKey vk); - - /// - /// Frees a hot key previously registered by the calling thread. - /// - /// - /// A handle to the window associated with the hot key to be freed. This parameter - /// should be NULL if the hot key is not associated with a window. - /// - /// - /// The identifier of the hot key to be freed. - /// - [LibraryImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static partial bool UnregisterHotKey(nint hWnd, int id); - private void ClickOffsetCheckBox_Unchecked(object sender, RoutedEventArgs e) { ClickOffsetAmount.IsEnabled = false; diff --git a/FluentAutoClicker/NativeMethods.txt b/FluentAutoClicker/NativeMethods.txt new file mode 100644 index 0000000..0ab104e --- /dev/null +++ b/FluentAutoClicker/NativeMethods.txt @@ -0,0 +1,4 @@ +RegisterHotKey +UnregisterHotKey +SetWindowLongPtr +CallWindowProc \ No newline at end of file From 48da0a51379ffdacacdf7822691fbf0012a9f255 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Tue, 7 Jan 2025 15:21:51 -0800 Subject: [PATCH 07/12] Fix crash on resize --- FluentAutoClicker/MainPage.xaml.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 429906b..ac7e832 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -39,7 +39,8 @@ public MainPage() Loaded += MainPage_Loaded; } - private WNDPROC wndProc = null!; + private WNDPROC origHotKeyProc; + private WNDPROC hotKeyProcD; private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam) { @@ -53,7 +54,7 @@ private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam) } } - return PInvoke.CallWindowProc(wndProc, hWnd, Msg, wParam, lParam); + return PInvoke.CallWindowProc(origHotKeyProc, hWnd, Msg, wParam, lParam); } private void MainPage_Loaded(object sender, RoutedEventArgs e) @@ -67,10 +68,10 @@ private void MainPage_Loaded(object sender, RoutedEventArgs e) _ = PInvoke.RegisterHotKey(hWnd, id, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6 // Add hotkey function pointer to window procedure - WNDPROC hotKeyDelegate = HotKeyProc; - nint hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyDelegate); + hotKeyProcD = HotKeyProc; + nint hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD); nint wndPtr = PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyProcPtr); - wndProc = Marshal.GetDelegateForFunctionPointer(wndPtr); + origHotKeyProc = Marshal.GetDelegateForFunctionPointer(wndPtr); } private void SetControlsEnabled(bool isEnabled) From cc7666c0c147b0bfc9a118023d230bec8a90ba8d Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 8 Jan 2025 13:14:01 -0800 Subject: [PATCH 08/12] Refactor for clicking logic --- FluentAutoClicker/Helpers/AutoClicker.cs | 130 +++++++++-------------- FluentAutoClicker/MainPage.xaml.cs | 8 +- FluentAutoClicker/NativeMethods.txt | 3 +- 3 files changed, 58 insertions(+), 83 deletions(-) diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs index 8ce7cdb..e099d13 100644 --- a/FluentAutoClicker/Helpers/AutoClicker.cs +++ b/FluentAutoClicker/Helpers/AutoClicker.cs @@ -16,17 +16,16 @@ // along with Fluent Auto Clicker. If not, see . using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.UI.Input.KeyboardAndMouse; namespace FluentAutoClicker.Helpers; /// /// Helper for creating threads to synthesize mouse input. /// -public static partial class AutoClicker +public static class AutoClicker { - [LibraryImport("user32.dll", SetLastError = true)] - private static partial uint SendInput(uint nInputs, Input[] pInputs, int cbSize); - private static Thread? _autoClickerThread; private static bool _isAutoClickerRunning; @@ -37,114 +36,87 @@ public static partial class AutoClicker /// The number of clicks before stopping the auto clicker thread. /// The mouse button used to click. /// The amount of time in milliseconds to add randomly to the millisecond delay between clicks. - public static void StartAutoClicker(int millisecondsDelay, int clickAmount, int mouseButtonType, int clickDelayOffset) + public static void Start(int millisecondsDelay = 100, int clickAmount = 0, + int mouseButtonType = 0, int clickDelayOffset = 0) { - // TODO: Evaluate whether a thread is necessary for this. + // TODO: Move the parameters to another function to be able to change parameters while the thread is running. _isAutoClickerRunning = true; - _autoClickerThread = new Thread(() => AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); + _autoClickerThread = new Thread(() => + AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); _autoClickerThread.Start(); } /// /// Stops the auto clicker thread. /// - public static void StopAutoClicker() + public static void Stop() { _isAutoClickerRunning = false; - // HACK: Incorrectly stops the thread, but it works for now. _autoClickerThread?.Join(); } - private static async void AutoClickerThread(int clickInterval, int repeatAmount, int mouseButton, int clickOffset) + private static async void AutoClickerThread(int clickInterval, int repeatAmount, int mouseButton, + int clickOffset) { int clickCount = 0; - Random random = new(); + while (_isAutoClickerRunning) { + // Stop if we click more than repeat amount. if (clickCount >= repeatAmount && repeatAmount != 0) { - StopAutoClicker(); + Stop(); break; } - // TODO: Move this to a enum instead of a number - switch (mouseButton) - { - case 0: - MouseEvent(0, 0, (uint)MouseEventF.LeftDown, 0, 0, IntPtr.Zero); - MouseEvent(0, 0, (uint)MouseEventF.LeftUp, 0, 0, IntPtr.Zero); - break; - case 1: - MouseEvent(0, 0, (uint)MouseEventF.MiddleDown, 0, 0, IntPtr.Zero); - MouseEvent(0, 0, (uint)MouseEventF.MiddleUp, 0, 0, IntPtr.Zero); - break; - case 2: - MouseEvent(0, 0, (uint)MouseEventF.RightDown, 0, 0, IntPtr.Zero); - MouseEvent(0, 0, (uint)MouseEventF.RightUp, 0, 0, IntPtr.Zero); - break; - } - - if (repeatAmount > 0) - { - clickCount++; - } + // Click mouse and increment click count + ClickMouse(mouseButton); + clickCount++; - int randomClickOffset = random.Next(0, clickOffset); + // Delay before next click + int randomClickOffset = new Random().Next(0, clickOffset); await Task.Delay(clickInterval + randomClickOffset); } } - private static void MouseEvent(int dx, int dy, uint dwFlags, uint dwData, uint time, nint dwExtraInfo) + /// + /// Clicks the mouse button. + /// + /// The mouse button to click. + private static void ClickMouse(int button) { - Input[] inputs = new Input[2]; - inputs[0] = MouseInput(dx, dy, dwData, dwFlags, time, dwExtraInfo); - inputs[1] = MouseInput(dx, dy, dwData, dwFlags, time, dwExtraInfo); - _ = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf()); + if (button == 0) // Left mouse button + { + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN); + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP); + } + else if (button == 1) // Middle mouse button + { + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEDOWN); + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_MIDDLEUP); + } + else if (button == 2) // Right mouse button + { + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTDOWN); + SendMouseInput(MOUSE_EVENT_FLAGS.MOUSEEVENTF_RIGHTUP); + } } - private static Input MouseInput(int dx, int dy, uint mouseData, uint dwFlags, uint time, nint dwExtraInfo) + /// + /// Sends a mouse input event. + /// + /// The mouse event flags that specify the type of mouse event. + private static void SendMouseInput(MOUSE_EVENT_FLAGS dwFlags) { - return new Input - { - type = 0, - mi = new InputMouse + INPUT[] inputs = + [ + new() { - dx = dx, - dy = dy, - mouseData = mouseData, - dwFlags = dwFlags, - time = time, - dwExtraInfo = dwExtraInfo + type = INPUT_TYPE.INPUT_MOUSE, + Anonymous = new INPUT._Anonymous_e__Union { mi = new MOUSEINPUT { dwFlags = dwFlags } } } - }; - } + ]; - [StructLayout(LayoutKind.Sequential)] - private struct Input - { - public int type; - public InputMouse mi; - } - - [StructLayout(LayoutKind.Sequential)] - private struct InputMouse - { - public int dx; - public int dy; - public uint mouseData; - public uint dwFlags; - public uint time; - public IntPtr dwExtraInfo; - } - - [Flags] - private enum MouseEventF : uint - { - LeftDown = 0x0002, - LeftUp = 0x0004, - RightDown = 0x0008, - RightUp = 0x0010, - MiddleDown = 0x0020, - MiddleUp = 0x0040 + _ = PInvoke.SendInput(inputs, Marshal.SizeOf()); } } \ No newline at end of file diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 82f5b2b..baad720 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -88,7 +88,8 @@ private void SetControlsEnabled(bool isEnabled) ClickRepeatAmount.IsEnabled = ClickRepeatCheckBox.IsChecked == true && isEnabled; // TODO: Change this to use a custom control. See https://github.com/RyanLua/FluentAutoClicker/issues/42 - string brushKey = isEnabled ? "SystemControlForegroundBaseHighBrush" : "SystemControlForegroundBaseMediumLowBrush"; + string brushKey = + isEnabled ? "SystemControlForegroundBaseHighBrush" : "SystemControlForegroundBaseMediumLowBrush"; ClickIntervalTextBlock.Foreground = Application.Current.Resources[brushKey] as Brush; HotkeyTextBlock.Foreground = Application.Current.Resources[brushKey] as Brush; } @@ -100,6 +101,7 @@ private int GetNumberBoxValue(NumberBox numberBox, int defaultValue) value = defaultValue; numberBox.Value = value; } + return value; } @@ -140,13 +142,13 @@ private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e) int repeatAmount = ClickRepeatCheckBox.IsEnabled == true ? Convert.ToInt32(ClickRepeatAmount.Value) : 0; int mouseButton = MouseButtonTypeComboBox.SelectedIndex; int clickOffset = ClickOffsetCheckBox.IsChecked == true ? Convert.ToInt32(ClickOffsetAmount.Value) : 0; - AutoClicker.StartAutoClicker(clickInterval, repeatAmount, mouseButton, clickOffset); + AutoClicker.Start(clickInterval, repeatAmount, mouseButton, clickOffset); } private void StartToggleButton_OnUnchecked(object sender, RoutedEventArgs e) { StartToggleButton.Content = "Start"; - AutoClicker.StopAutoClicker(); + AutoClicker.Stop(); SetControlsEnabled(true); } diff --git a/FluentAutoClicker/NativeMethods.txt b/FluentAutoClicker/NativeMethods.txt index 0ab104e..b198ef1 100644 --- a/FluentAutoClicker/NativeMethods.txt +++ b/FluentAutoClicker/NativeMethods.txt @@ -1,4 +1,5 @@ RegisterHotKey UnregisterHotKey SetWindowLongPtr -CallWindowProc \ No newline at end of file +CallWindowProc +SendInput \ No newline at end of file From e74ddf2b1ca27dba82c404681f1e02a51f7589dc Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 8 Jan 2025 14:08:40 -0800 Subject: [PATCH 09/12] Remove period Signed-off-by: Ryan Luu --- FluentAutoClicker/Helpers/AutoClicker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs index e099d13..c296b4c 100644 --- a/FluentAutoClicker/Helpers/AutoClicker.cs +++ b/FluentAutoClicker/Helpers/AutoClicker.cs @@ -62,7 +62,7 @@ private static async void AutoClickerThread(int clickInterval, int repeatAmount, while (_isAutoClickerRunning) { - // Stop if we click more than repeat amount. + // Stop if we click more than repeat amount if (clickCount >= repeatAmount && repeatAmount != 0) { Stop(); @@ -119,4 +119,4 @@ private static void SendMouseInput(MOUSE_EVENT_FLAGS dwFlags) _ = PInvoke.SendInput(inputs, Marshal.SizeOf()); } -} \ No newline at end of file +} From f71db44463c30e27cf0e05960420c45bfe85b571 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 8 Jan 2025 16:43:16 -0800 Subject: [PATCH 10/12] Fix x84 support --- FluentAutoClicker/MainPage.xaml.cs | 20 ++++++++++++++++++-- FluentAutoClicker/NativeMethods.txt | 1 - 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index baad720..b644861 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -57,6 +57,21 @@ private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam) return PInvoke.CallWindowProc(origHotKeyProc, hWnd, Msg, wParam, lParam); } + [LibraryImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = true)] + private static partial int SetWindowLong_x86(nint hWnd, int nIndex, int dwNewLong); + + [LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)] + private static partial nint SetWindowLongPtr_x64(nint hWnd, int nIndex, nint dwNewLong); + + // CsWin32 doesn't allow specifying the calling convention between x86 and x64 + private static nint SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong) + { + nint hWndInt = hWnd.Value; + return IntPtr.Size == 4 + ? SetWindowLong_x86(hWndInt, nIndex, (int)dwNewLong) + : SetWindowLongPtr_x64(hWndInt, nIndex, dwNewLong); + } + private void MainPage_Loaded(object sender, RoutedEventArgs e) { // Get window handle @@ -68,9 +83,10 @@ private void MainPage_Loaded(object sender, RoutedEventArgs e) _ = PInvoke.RegisterHotKey(hWnd, id, HOT_KEY_MODIFIERS.MOD_NOREPEAT, 0x75); // F6 // Add hotkey function pointer to window procedure + int GWL_WNDPROC = -4; hotKeyProcD = HotKeyProc; - nint hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD); - nint wndPtr = PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWL_WNDPROC, hotKeyProcPtr); + IntPtr hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD); + IntPtr wndPtr = SetWindowLongPtr(hWnd, GWL_WNDPROC, hotKeyProcPtr); origHotKeyProc = Marshal.GetDelegateForFunctionPointer(wndPtr); } diff --git a/FluentAutoClicker/NativeMethods.txt b/FluentAutoClicker/NativeMethods.txt index b198ef1..d0c05aa 100644 --- a/FluentAutoClicker/NativeMethods.txt +++ b/FluentAutoClicker/NativeMethods.txt @@ -1,5 +1,4 @@ RegisterHotKey UnregisterHotKey -SetWindowLongPtr CallWindowProc SendInput \ No newline at end of file From f51bbcf70b4b8741737d3952f71a88e4bca24d1b Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Wed, 8 Jan 2025 18:42:38 -0800 Subject: [PATCH 11/12] Simplify checkbox logic --- FluentAutoClicker/MainPage.xaml | 6 ++---- FluentAutoClicker/MainPage.xaml.cs | 30 +++++++++++------------------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/FluentAutoClicker/MainPage.xaml b/FluentAutoClicker/MainPage.xaml index 1a56715..4cb3a57 100644 --- a/FluentAutoClicker/MainPage.xaml +++ b/FluentAutoClicker/MainPage.xaml @@ -102,8 +102,7 @@ + Click="CheckBox_Click" /> + Click="CheckBox_Click" /> Date: Wed, 8 Jan 2025 19:00:56 -0800 Subject: [PATCH 12/12] Fix diagnostics --- FluentAutoClicker/App.xaml.cs | 2 +- FluentAutoClicker/MainPage.xaml.cs | 4 ++-- FluentAutoClicker/Program.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs index a6258de..8933114 100644 --- a/FluentAutoClicker/App.xaml.cs +++ b/FluentAutoClicker/App.xaml.cs @@ -27,7 +27,7 @@ namespace FluentAutoClicker; /// public partial class App : Application { - public static MainWindow MainWindow = new(); + public static readonly MainWindow MainWindow = new(); /// /// Initializes the singleton application object. This is the first line of authored code diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index b7e7933..e2b290a 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -39,8 +39,8 @@ public MainPage() Loaded += MainPage_Loaded; } - private WNDPROC origHotKeyProc; - private WNDPROC hotKeyProcD; + private WNDPROC? origHotKeyProc; + private WNDPROC? hotKeyProcD; private LRESULT HotKeyProc(HWND hWnd, uint Msg, WPARAM wParam, LPARAM lParam) { diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs index 60cd409..b830b22 100644 --- a/FluentAutoClicker/Program.cs +++ b/FluentAutoClicker/Program.cs @@ -29,7 +29,7 @@ namespace FluentAutoClicker; public partial class Program { [STAThread] - private static int Main(string[] args) + private static int Main() { WinRT.ComWrappersSupport.InitializeComWrappers(); bool isRedirect = DecideRedirection(); @@ -80,7 +80,7 @@ private static partial IntPtr CreateEvent( [LibraryImport("ole32.dll")] private static partial uint CoWaitForMultipleObjects( uint dwFlags, uint dwMilliseconds, ulong nHandles, - IntPtr[] pHandles, out uint dwIndex); + [In, Out] IntPtr[] pHandles, out uint dwIndex); [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)]