diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs
index bbc2c99..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 Window Window { get; private set; } = null!;
+ public static readonly 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/FluentAutoClicker.csproj b/FluentAutoClicker/FluentAutoClicker.csproj
index e515e3a..710ab2e 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
@@ -37,6 +38,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs
index cebaa32..c296b4c 100644
--- a/FluentAutoClicker/Helpers/AutoClicker.cs
+++ b/FluentAutoClicker/Helpers/AutoClicker.cs
@@ -16,6 +16,8 @@
// along with Fluent Auto Clicker. If not, see .
using System.Runtime.InteropServices;
+using Windows.Win32;
+using Windows.Win32.UI.Input.KeyboardAndMouse;
namespace FluentAutoClicker.Helpers;
@@ -24,9 +26,6 @@ namespace FluentAutoClicker.Helpers;
///
public static class AutoClicker
{
- [DllImport("user32.dll", SetLastError = true)]
- private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize);
-
private static Thread? _autoClickerThread;
private static bool _isAutoClickerRunning;
@@ -37,114 +36,87 @@ public static 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/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs
deleted file mode 100644
index ba20471..0000000
--- a/FluentAutoClicker/Helpers/WindowMessageHook.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (C) 2025 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 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); }
-
- [DllImport("comctl32", SetLastError = true)]
- private static extern bool SetWindowSubclass(nint hWnd, Subclassproc pfnSubclass, uint uIdSubclass, uint dwRefData);
-
- [DllImport("comctl32", SetLastError = true)]
- private static extern nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam);
-
- [DllImport("comctl32", SetLastError = true)]
- private static extern 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 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" />
hook.Dispose();
+ 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;
}
- };
+ }
+
+ return PInvoke.CallWindowProc(origHotKeyProc, hWnd, Msg, wParam, lParam);
+ }
- nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window);
- int id = 1;
+ [LibraryImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = true)]
+ private static partial int SetWindowLong_x86(nint hWnd, int nIndex, int dwNewLong);
- // Register the F6 key
- if (!RegisterHotKey(hwnd, id, Mod.ModNoRepeat, VirtualKey.F6))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
+ [LibraryImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)]
+ private static partial nint SetWindowLongPtr_x64(nint hWnd, int nIndex, nint dwNewLong);
- Unloaded += (s, e) => UnregisterHotKey(hwnd, id);
+ // 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
+ 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
+
+ // Add hotkey function pointer to window procedure
+ int GWL_WNDPROC = -4;
+ hotKeyProcD = HotKeyProc;
+ IntPtr hotKeyProcPtr = Marshal.GetFunctionPointerForDelegate(hotKeyProcD);
+ IntPtr wndPtr = SetWindowLongPtr(hWnd, GWL_WNDPROC, hotKeyProcPtr);
+ origHotKeyProc = Marshal.GetDelegateForFunctionPointer(wndPtr);
}
private void SetControlsEnabled(bool isEnabled)
@@ -80,18 +104,20 @@ 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;
}
- private int GetNumberBoxValue(NumberBox numberBox, int defaultValue)
+ private static int GetNumberBoxValue(NumberBox numberBox, int defaultValue)
{
if (!int.TryParse(numberBox.Value.ToString(CultureInfo.InvariantCulture), out int value))
{
value = defaultValue;
numberBox.Value = value;
}
+
return value;
}
@@ -132,49 +158,25 @@ 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);
}
- private void ClickRepeatCheckBox_Unchecked(object sender, RoutedEventArgs e)
- {
- ClickRepeatAmount.IsEnabled = false;
- }
-
- private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e)
- {
- ClickRepeatAmount.IsEnabled = true;
- }
-
- // interop code for Windows API hotkey functions
- [DllImport("user32", SetLastError = true)]
- private static extern bool RegisterHotKey(nint hWnd, int id, Mod fsModifiers, VirtualKey vk);
-
- [DllImport("user32", SetLastError = true)]
- private static extern 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)
+ private void CheckBox_Click(object sender, RoutedEventArgs e)
{
- ClickOffsetAmount.IsEnabled = false;
- }
-
- private void ClickOffsetCheckBox_Checked(object sender, RoutedEventArgs e)
- {
- ClickOffsetAmount.IsEnabled = true;
+ if (sender.Equals(ClickRepeatCheckBox))
+ {
+ ClickRepeatAmount.IsEnabled = ClickRepeatCheckBox.IsChecked == true;
+ }
+ else if (sender.Equals(ClickOffsetCheckBox))
+ {
+ ClickOffsetAmount.IsEnabled = ClickOffsetCheckBox.IsChecked == true;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/FluentAutoClicker/NativeMethods.txt b/FluentAutoClicker/NativeMethods.txt
new file mode 100644
index 0000000..d0c05aa
--- /dev/null
+++ b/FluentAutoClicker/NativeMethods.txt
@@ -0,0 +1,4 @@
+RegisterHotKey
+UnregisterHotKey
+CallWindowProc
+SendInput
\ No newline at end of file
diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs
index b561ef7..b830b22 100644
--- a/FluentAutoClicker/Program.cs
+++ b/FluentAutoClicker/Program.cs
@@ -26,10 +26,10 @@ 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)
+ private static int Main()
{
WinRT.ComWrappersSupport.InitializeComWrappers();
bool isRedirect = DecideRedirection();
@@ -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);
+ [In, Out] 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;