From 56070613d913f051a8772facfe62e85f74b0cfe7 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sat, 30 Nov 2024 16:14:36 -0800 Subject: [PATCH 1/5] Use minimal.vsconfig Signed-off-by: Ryan Luu --- .vsconfig | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/.vsconfig b/.vsconfig index 0f44999..2389b29 100644 --- a/.vsconfig +++ b/.vsconfig @@ -3,32 +3,6 @@ "components": [ "Microsoft.VisualStudio.Component.CoreEditor", "Microsoft.VisualStudio.Workload.CoreEditor", - "Microsoft.Net.Component.4.8.SDK", - "Microsoft.Net.Component.4.7.2.TargetingPack", - "Microsoft.Net.ComponentGroup.DevelopmentPrerequisites", - "Microsoft.VisualStudio.Component.Roslyn.Compiler", - "Microsoft.Component.MSBuild", - "Microsoft.VisualStudio.Component.Roslyn.LanguageServices", - "Microsoft.VisualStudio.Component.TextTemplating", - "Microsoft.VisualStudio.Component.NuGet", - "Microsoft.VisualStudio.Component.SQL.CLR", - "Microsoft.Component.ClickOnce", - "Microsoft.VisualStudio.Component.ManagedDesktop.Core", - "Microsoft.NetCore.Component.Runtime.9.0", - "Microsoft.NetCore.Component.Runtime.8.0", - "Microsoft.NetCore.Component.SDK", - "Microsoft.VisualStudio.Component.AppInsights.Tools", - "Microsoft.VisualStudio.Component.DiagnosticTools", - "Microsoft.NetCore.Component.Runtime.6.0", - "Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs", - "Microsoft.ComponentGroup.Blend", - "Microsoft.VisualStudio.Component.Windows10SDK.19041", - "Microsoft.Component.NetFX.Native", - "Microsoft.VisualStudio.Component.Graphics", - "Microsoft.VisualStudio.Component.VC.Tools.ARM64EC", - "Microsoft.VisualStudio.Component.WindowsAppSdkSupport.CSharp", - "Microsoft.VisualStudio.ComponentGroup.WindowsAppDevelopment.Prerequisites", - "Microsoft.VisualStudio.ComponentGroup.UWP.NetCoreAndStandard", "Microsoft.VisualStudio.Workload.Universal" ], "extensions": [] From 23bb9d7de2ac78919c1c2660a72e28ccfbaae2f2 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sat, 30 Nov 2024 18:14:12 -0800 Subject: [PATCH 2/5] Cleanup code --- FluentAutoClicker/App.xaml.cs | 2 +- FluentAutoClicker/Helpers/AutoClicker.cs | 218 +++++++++--------- .../Helpers/ResourceExtensions.cs | 27 +-- .../Helpers/WindowMessageHook.cs | 178 ++++++++------ FluentAutoClicker/MainPage.xaml.cs | 45 ++-- FluentAutoClicker/MainWindow.xaml.cs | 29 +-- FluentAutoClicker/Program.cs | 14 +- 7 files changed, 269 insertions(+), 244 deletions(-) diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs index 98e2a88..2cae0bb 100644 --- a/FluentAutoClicker/App.xaml.cs +++ b/FluentAutoClicker/App.xaml.cs @@ -33,7 +33,7 @@ public partial class App : Application /// public App() { - this.InitializeComponent(); + InitializeComponent(); } /// diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs index 6e49abf..0af699e 100644 --- a/FluentAutoClicker/Helpers/AutoClicker.cs +++ b/FluentAutoClicker/Helpers/AutoClicker.cs @@ -20,131 +20,135 @@ using System.Threading; using System.Threading.Tasks; -namespace FluentAutoClicker.Helpers; - -/// -/// Helper for creating threads to synthesize mouse input. -/// -public static class AutoClicker +namespace FluentAutoClicker.Helpers { - [DllImport("user32.dll", SetLastError = true)] - private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize); - - private static Thread _autoClickerThread; - private static bool IsAutoClickerRunning; - /// - /// Starts the auto clicker thread. + /// Helper for creating threads to synthesize mouse input. /// - /// The number of milliseconds to wait before clicks. - /// 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 class AutoClicker { - // TODO: Evaluate whether a thread is necessary for this. - IsAutoClickerRunning = true; - _autoClickerThread = new Thread(() => AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); - _autoClickerThread.Start(); - } + [DllImport("user32.dll", SetLastError = true)] + private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize); - /// - /// Stops the auto clicker thread. - /// - public static void StopAutoClicker() - { - IsAutoClickerRunning = false; - // HACK: Incorrectly stops the thread, but it works for now. - _autoClickerThread?.Join(); - } + private static Thread _autoClickerThread; + private static bool IsAutoClickerRunning; - private static async void AutoClickerThread(int ClickInterval, int RepeatAmount, int MouseButton, int ClickOffset) - { - var clickCount = 0; - var random = new Random(); - while (IsAutoClickerRunning) + /// + /// Starts the auto clicker thread. + /// + /// The number of milliseconds to wait before clicks. + /// 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) { - if (clickCount >= RepeatAmount && RepeatAmount != 0) - { - StopAutoClicker(); - break; - } + // TODO: Evaluate whether a thread is necessary for this. + IsAutoClickerRunning = true; + _autoClickerThread = new Thread(() => AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); + _autoClickerThread.Start(); + } - // TODO: Move this to a enum instead of a number - switch (MouseButton) + /// + /// Stops the auto clicker thread. + /// + public static void StopAutoClicker() + { + 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) + { + int clickCount = 0; + Random random = new(); + while (IsAutoClickerRunning) { - case 0: - MouseEvent(0, 0, (uint)MouseEventF.LeftDown, 0, 0, IntPtr.Zero); - MouseEvent(0, 0, (uint)MouseEventF.LeftUp, 0, 0, IntPtr.Zero); + if (clickCount >= RepeatAmount && RepeatAmount != 0) + { + StopAutoClicker(); 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++; + // 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; + } - int randomClickOffset = random.Next(0, ClickOffset); - await Task.Delay(ClickInterval + randomClickOffset); + if (RepeatAmount > 0) + { + clickCount++; + } + + int randomClickOffset = 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) - { - var 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()); - } + private static void MouseEvent(int dx, int dy, uint dwFlags, uint dwData, uint time, nint dwExtraInfo) + { + 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()); + } - private static Input MouseInput(int dx, int dy, uint mouseData, uint dwFlags, uint time, nint dwExtraInfo) - { - return new Input + private static Input MouseInput(int dx, int dy, uint mouseData, uint dwFlags, uint time, nint dwExtraInfo) { - type = 0, - mi = new InputMouse + return new Input { - dx = dx, - dy = dy, - mouseData = mouseData, - dwFlags = dwFlags, - time = time, - dwExtraInfo = dwExtraInfo - } - }; - } + type = 0, + mi = new InputMouse + { + dx = dx, + dy = dy, + mouseData = mouseData, + dwFlags = dwFlags, + time = time, + dwExtraInfo = dwExtraInfo + } + }; + } - [StructLayout(LayoutKind.Sequential)] - private struct Input - { - public int type; - public InputMouse mi; - } + [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; - } + [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 + [Flags] + private enum MouseEventF : uint + { + LeftDown = 0x0002, + LeftUp = 0x0004, + RightDown = 0x0008, + RightUp = 0x0010, + MiddleDown = 0x0020, + MiddleUp = 0x0040 + } } -} +} \ No newline at end of file diff --git a/FluentAutoClicker/Helpers/ResourceExtensions.cs b/FluentAutoClicker/Helpers/ResourceExtensions.cs index 0a0b6cb..774c185 100644 --- a/FluentAutoClicker/Helpers/ResourceExtensions.cs +++ b/FluentAutoClicker/Helpers/ResourceExtensions.cs @@ -17,22 +17,23 @@ using Microsoft.Windows.ApplicationModel.Resources; -namespace FluentAutoClicker.Helpers; - -/// -/// Helper for getting localized strings from resources. -/// -public static class ResourceExtensions +namespace FluentAutoClicker.Helpers { - private static readonly ResourceLoader ResourceLoader = new(); - /// - /// Gets the localized string for the resource key. + /// Helper for getting localized strings from resources. /// - /// The resource key for the returned localized string. - /// The localized string for the specified resource key. - public static string GetLocalized(this string resourceKey) + public static class ResourceExtensions { - return ResourceLoader.GetString(resourceKey); + private static readonly ResourceLoader ResourceLoader = new(); + + /// + /// Gets the localized string for the resource key. + /// + /// The resource key for the returned localized string. + /// The localized string for the specified resource key. + public static string GetLocalized(this string resourceKey) + { + return ResourceLoader.GetString(resourceKey); + } } } \ No newline at end of file diff --git a/FluentAutoClicker/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs index ff68e0b..9f7fa2d 100644 --- a/FluentAutoClicker/Helpers/WindowMessageHook.cs +++ b/FluentAutoClicker/Helpers/WindowMessageHook.cs @@ -23,96 +23,130 @@ using System.Runtime.InteropServices; using System.Threading; -namespace FluentAutoClicker.Helpers; - -public class WindowMessageHook : IEquatable, IDisposable +namespace FluentAutoClicker.Helpers { - private delegate nint SUBCLASSPROC(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData); + 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; + private static readonly ConcurrentDictionary _hooks = new(); + private static readonly SUBCLASSPROC _proc = SubclassProc; - public event EventHandler Message; - private nint _hWnd; + 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)); + 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()); + } + } - _hWnd = hWnd; - _hooks.AddOrUpdate(hWnd, this, (k, o) => + protected virtual void OnMessage(object sender, MessageEventArgs e) { - if (Equals(o)) return o; - o.Dispose(); - return this; - }); - if (!SetWindowSubclass(hWnd, _proc, 0, 0)) - throw new Win32Exception(Marshal.GetLastWin32Error()); - } + Message?.Invoke(sender, e); + } - protected virtual void OnMessage(object sender, MessageEventArgs e) => Message?.Invoke(sender, e); - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; - var hWnd = Interlocked.Exchange(ref _hWnd, IntPtr.Zero); - if (hWnd != IntPtr.Zero) + protected virtual void Dispose(bool disposing) { - RemoveWindowSubclass(hWnd, _proc, 0); - _hooks.Remove(hWnd, out _); + 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); } + ~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 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 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); + [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 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 var hook)) + private static nint SubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData) { - var e = new MessageEventArgs(hWnd, uMsg, wParam, lParam); - hook.OnMessage(hook, e); - if (e.Result.HasValue) - return e.Result.Value; + 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(); } - return DefSubclassProc(hWnd, uMsg, wParam, lParam); - } - public override int GetHashCode() => _hWnd.GetHashCode(); - public override string ToString() => _hWnd.ToString(); - public override bool Equals(object obj) => Equals(obj as WindowMessageHook); - public virtual bool Equals(WindowMessageHook other) => other != null && _hWnd.Equals(other._hWnd); -} + public override string ToString() + { + return _hWnd.ToString(); + } -public class MessageEventArgs : EventArgs -{ - public MessageEventArgs(nint hWnd, uint uMsg, nint wParam, nint lParam) - { - HWnd = hWnd; - Message = uMsg; - WParam = wParam; - LParam = lParam; + 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 nint HWnd { get; } - public uint Message { get; } - public nint WParam { get; } - public nint LParam { get; } - public virtual nint? Result { get; set; } -} + 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; } + } +} \ No newline at end of file diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 4f158b1..9c1ca85 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -42,7 +42,7 @@ public MainPage() private void MainPage_Loaded(object sender, RoutedEventArgs e) { - var hook = new WindowMessageHook(App.Window); + WindowMessageHook hook = new(App.Window); Unloaded += (s, e) => hook.Dispose(); // unhook on close hook.Message += (s, e) => { @@ -50,16 +50,18 @@ private void MainPage_Loaded(object sender, RoutedEventArgs e) if (e.Message == WM_HOTKEY) { // click on the button using UI Automation - var pattern = (ToggleButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(StartToggleButton).GetPattern(PatternInterface.Toggle); + ToggleButtonAutomationPeer pattern = (ToggleButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(StartToggleButton).GetPattern(PatternInterface.Toggle); pattern.Toggle(); } }; // register CTRL + B as a global hotkey - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); - var id = 1; // some arbitrary hotkey identifier + nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); + int id = 1; // some arbitrary hotkey identifier if (!RegisterHotKey(hwnd, id, MOD.MOD_NOREPEAT, VirtualKey.F6)) + { throw new Win32Exception(Marshal.GetLastWin32Error()); + } Unloaded += (s, e) => UnregisterHotKey(hwnd, id); // unregister hotkey on window close } @@ -100,31 +102,31 @@ private void SetControlsEnabled(bool isEnabled) private int GetIntervalMilliseconds() { - if (!Int32.TryParse(NumberBoxHours.Value.ToString(CultureInfo.InvariantCulture), out var hours)) + if (!int.TryParse(NumberBoxHours.Value.ToString(CultureInfo.InvariantCulture), out int hours)) { hours = 0; NumberBoxHours.Value = hours; } - if (!Int32.TryParse(NumberBoxMinutes.Value.ToString(CultureInfo.InvariantCulture), out var minutes)) + if (!int.TryParse(NumberBoxMinutes.Value.ToString(CultureInfo.InvariantCulture), out int minutes)) { minutes = 0; NumberBoxMinutes.Value = minutes; } - if (!Int32.TryParse(NumberBoxSeconds.Value.ToString(CultureInfo.InvariantCulture), out var seconds)) + if (!int.TryParse(NumberBoxSeconds.Value.ToString(CultureInfo.InvariantCulture), out int seconds)) { seconds = 0; NumberBoxSeconds.Value = seconds; } - if (!Int32.TryParse(NumberBoxMilliseconds.Value.ToString(CultureInfo.InvariantCulture), out var milliseconds)) + if (!int.TryParse(NumberBoxMilliseconds.Value.ToString(CultureInfo.InvariantCulture), out int milliseconds)) { milliseconds = 100; NumberBoxMilliseconds.Value = milliseconds; } - var totalTimeInMilliseconds = ((hours * 60 + minutes) * 60 + seconds) * 1000 + milliseconds; + int totalTimeInMilliseconds = (((((hours * 60) + minutes) * 60) + seconds) * 1000) + milliseconds; if (totalTimeInMilliseconds == 0) { @@ -141,7 +143,7 @@ private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e) SetControlsEnabled(false); // 3-second countdown - for (var i = 3; i > 0; i--) + for (int i = 3; i > 0; i--) { StartToggleButton.Content = i.ToString(); await Task.Delay(1000); @@ -151,26 +153,9 @@ private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e) StartToggleButton.Content = "Stop"; int clickInterval = GetIntervalMilliseconds(); - int repeatAmount = 0; - if (ClickRepeatCheckBox.IsEnabled == true) - { - repeatAmount = Convert.ToInt32(ClickRepeatAmount.Value); - } - else - { - repeatAmount = 0; - } + int repeatAmount = ClickRepeatCheckBox.IsEnabled == true ? Convert.ToInt32(ClickRepeatAmount.Value) : 0; int mouseButton = MouseButtonTypeComboBox.SelectedIndex; - int clickOffset = 0; - if (ClickOffsetCheckBox.IsChecked == true) - { - clickOffset = Convert.ToInt32(ClickOffsetAmount.Value); - } - else - { - clickOffset = 0; - } - + int clickOffset = ClickOffsetCheckBox.IsChecked == true ? Convert.ToInt32(ClickOffsetAmount.Value) : 0; AutoClicker.StartAutoClicker(clickInterval, repeatAmount, mouseButton, clickOffset); } @@ -217,4 +202,4 @@ private void ClickOffsetCheckBox_Checked(object sender, RoutedEventArgs e) ClickOffsetAmount.IsEnabled = true; } } -} +} \ No newline at end of file diff --git a/FluentAutoClicker/MainWindow.xaml.cs b/FluentAutoClicker/MainWindow.xaml.cs index 2115f53..e83f599 100644 --- a/FluentAutoClicker/MainWindow.xaml.cs +++ b/FluentAutoClicker/MainWindow.xaml.cs @@ -20,23 +20,24 @@ // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. -namespace FluentAutoClicker; - -/// -/// An window that displays a page's contents. -/// -public sealed partial class MainWindow +namespace FluentAutoClicker { - public MainWindow() + /// + /// An window that displays a page's contents. + /// + public sealed partial class MainWindow { - InitializeComponent(); + public MainWindow() + { + InitializeComponent(); - // Set up window - Title = "AppDisplayName".GetLocalized(); - AppWindow.SetIcon("Assets/WindowIcon.ico"); + // Set up window + Title = "AppDisplayName".GetLocalized(); + AppWindow.SetIcon("Assets/WindowIcon.ico"); - // Set up window title bar - ExtendsContentIntoTitleBar = true; - AppTitleBar.Title = "AppDisplayName".GetLocalized(); + // Set up window title bar + ExtendsContentIntoTitleBar = true; + AppTitleBar.Title = "AppDisplayName".GetLocalized(); + } } } \ No newline at end of file diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs index 911475f..2c50b74 100644 --- a/FluentAutoClicker/Program.cs +++ b/FluentAutoClicker/Program.cs @@ -32,7 +32,7 @@ namespace FluentAutoClicker public class Program { [STAThread] - static int Main(string[] args) + private static int Main(string[] args) { WinRT.ComWrappersSupport.InitializeComWrappers(); bool isRedirect = DecideRedirection(); @@ -41,7 +41,7 @@ static int Main(string[] args) { Application.Start((p) => { - var context = new DispatcherQueueSynchronizationContext( + DispatcherQueueSynchronizationContext context = new( DispatcherQueue.GetForCurrentThread()); SynchronizationContext.SetSynchronizationContext(context); _ = new App(); @@ -85,7 +85,7 @@ private static extern uint CoWaitForMultipleObjects( IntPtr[] pHandles, out uint dwIndex); [DllImport("user32.dll")] - static extern bool SetForegroundWindow(IntPtr hWnd); + private static extern bool SetForegroundWindow(IntPtr hWnd); private static IntPtr redirectEventHandle = IntPtr.Zero; @@ -95,10 +95,10 @@ public static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance) { redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null); - Task.Run(() => + _ = Task.Run(() => { keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); - SetEvent(redirectEventHandle); + _ = SetEvent(redirectEventHandle); }); uint CWMO_DEFAULT = 0; @@ -109,12 +109,12 @@ public static void RedirectActivationTo(AppActivationArguments args, // Bring the window to the foreground Process process = Process.GetProcessById((int)keyInstance.ProcessId); - SetForegroundWindow(process.MainWindowHandle); + _ = SetForegroundWindow(process.MainWindowHandle); } private static void OnActivated(object sender, AppActivationArguments args) { - ExtendedActivationKind kind = args.Kind; + _ = args.Kind; } } } \ No newline at end of file From 7ae2674b18d1432550fa6a116ba2e03722584c83 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sat, 30 Nov 2024 18:22:45 -0800 Subject: [PATCH 3/5] Switch namespace declarations to be file scoped --- .editorconfig | 2 +- FluentAutoClicker/App.xaml.cs | 43 ++- FluentAutoClicker/Helpers/AutoClicker.cs | 219 +++++++------- .../Helpers/ResourceExtensions.cs | 27 +- .../Helpers/WindowMessageHook.cs | 185 ++++++------ FluentAutoClicker/MainPage.xaml.cs | 279 +++++++++--------- FluentAutoClicker/MainWindow.xaml.cs | 29 +- FluentAutoClicker/Program.cs | 149 +++++----- 8 files changed, 463 insertions(+), 470 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5680309..1f8dd0a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -124,7 +124,7 @@ csharp_style_prefer_readonly_struct_member = true csharp_prefer_braces = true csharp_prefer_simple_using_statement = true csharp_prefer_system_threading_lock = true -csharp_style_namespace_declarations = block_scoped +csharp_style_namespace_declarations = file_scoped csharp_style_prefer_method_group_conversion = true csharp_style_prefer_primary_constructors = true csharp_style_prefer_top_level_statements = true diff --git a/FluentAutoClicker/App.xaml.cs b/FluentAutoClicker/App.xaml.cs index 2cae0bb..ab9d74e 100644 --- a/FluentAutoClicker/App.xaml.cs +++ b/FluentAutoClicker/App.xaml.cs @@ -20,32 +20,31 @@ // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. -namespace FluentAutoClicker +namespace FluentAutoClicker; + +/// +/// Provides application-specific behavior to supplement the default Application class. +/// +public partial class App : Application { /// - /// Provides application-specific behavior to supplement the default Application class. + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). /// - public partial class App : Application + public App() { - /// - /// Initializes the singleton application object. This is the first line of authored code - /// executed, and as such is the logical equivalent of main() or WinMain(). - /// - public App() - { - InitializeComponent(); - } - - /// - /// Invoked when the application is launched. - /// - /// Details about the launch request and process. - protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) - { - Window = new MainWindow(); - Window.Activate(); - } + InitializeComponent(); + } - public static Window Window { get; private set; } + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + Window = new MainWindow(); + Window.Activate(); } + + public static Window Window { get; private set; } } \ No newline at end of file diff --git a/FluentAutoClicker/Helpers/AutoClicker.cs b/FluentAutoClicker/Helpers/AutoClicker.cs index 0af699e..bbe65da 100644 --- a/FluentAutoClicker/Helpers/AutoClicker.cs +++ b/FluentAutoClicker/Helpers/AutoClicker.cs @@ -20,135 +20,134 @@ using System.Threading; using System.Threading.Tasks; -namespace FluentAutoClicker.Helpers +namespace FluentAutoClicker.Helpers; + +/// +/// Helper for creating threads to synthesize mouse input. +/// +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; + /// - /// Helper for creating threads to synthesize mouse input. + /// Starts the auto clicker thread. /// - public static class AutoClicker + /// The number of milliseconds to wait before clicks. + /// 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) { - [DllImport("user32.dll", SetLastError = true)] - private static extern uint SendInput(uint nInputs, Input[] pInputs, int cbSize); - - private static Thread _autoClickerThread; - private static bool IsAutoClickerRunning; + // TODO: Evaluate whether a thread is necessary for this. + IsAutoClickerRunning = true; + _autoClickerThread = new Thread(() => AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); + _autoClickerThread.Start(); + } - /// - /// Starts the auto clicker thread. - /// - /// The number of milliseconds to wait before clicks. - /// 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) - { - // TODO: Evaluate whether a thread is necessary for this. - IsAutoClickerRunning = true; - _autoClickerThread = new Thread(() => AutoClickerThread(millisecondsDelay, clickAmount, mouseButtonType, clickDelayOffset)); - _autoClickerThread.Start(); - } + /// + /// Stops the auto clicker thread. + /// + public static void StopAutoClicker() + { + IsAutoClickerRunning = false; + // HACK: Incorrectly stops the thread, but it works for now. + _autoClickerThread?.Join(); + } - /// - /// Stops the auto clicker thread. - /// - public static void StopAutoClicker() + private static async void AutoClickerThread(int ClickInterval, int RepeatAmount, int MouseButton, int ClickOffset) + { + int clickCount = 0; + Random random = new(); + while (IsAutoClickerRunning) { - IsAutoClickerRunning = false; - // HACK: Incorrectly stops the thread, but it works for now. - _autoClickerThread?.Join(); - } + if (clickCount >= RepeatAmount && RepeatAmount != 0) + { + StopAutoClicker(); + break; + } - private static async void AutoClickerThread(int ClickInterval, int RepeatAmount, int MouseButton, int ClickOffset) - { - int clickCount = 0; - Random random = new(); - while (IsAutoClickerRunning) + // TODO: Move this to a enum instead of a number + switch (MouseButton) { - if (clickCount >= RepeatAmount && RepeatAmount != 0) - { - StopAutoClicker(); + case 0: + MouseEvent(0, 0, (uint)MouseEventF.LeftDown, 0, 0, IntPtr.Zero); + MouseEvent(0, 0, (uint)MouseEventF.LeftUp, 0, 0, IntPtr.Zero); 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++; - } + 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; + } - int randomClickOffset = random.Next(0, ClickOffset); - await Task.Delay(ClickInterval + randomClickOffset); + if (RepeatAmount > 0) + { + clickCount++; } - } - private static void MouseEvent(int dx, int dy, uint dwFlags, uint dwData, uint time, nint dwExtraInfo) - { - 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()); + int randomClickOffset = 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) + { + 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()); + } - private static Input MouseInput(int dx, int dy, uint mouseData, uint dwFlags, uint time, nint dwExtraInfo) + private static Input MouseInput(int dx, int dy, uint mouseData, uint dwFlags, uint time, nint dwExtraInfo) + { + return new Input { - return new Input + type = 0, + mi = new InputMouse { - type = 0, - mi = new InputMouse - { - dx = dx, - dy = dy, - mouseData = mouseData, - dwFlags = dwFlags, - time = time, - dwExtraInfo = dwExtraInfo - } - }; - } + dx = dx, + dy = dy, + mouseData = mouseData, + dwFlags = dwFlags, + time = time, + dwExtraInfo = dwExtraInfo + } + }; + } - [StructLayout(LayoutKind.Sequential)] - private struct Input - { - public int type; - public InputMouse mi; - } + [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; - } + [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 - } + [Flags] + private enum MouseEventF : uint + { + LeftDown = 0x0002, + LeftUp = 0x0004, + RightDown = 0x0008, + RightUp = 0x0010, + MiddleDown = 0x0020, + MiddleUp = 0x0040 } } \ No newline at end of file diff --git a/FluentAutoClicker/Helpers/ResourceExtensions.cs b/FluentAutoClicker/Helpers/ResourceExtensions.cs index 774c185..0a0b6cb 100644 --- a/FluentAutoClicker/Helpers/ResourceExtensions.cs +++ b/FluentAutoClicker/Helpers/ResourceExtensions.cs @@ -17,23 +17,22 @@ using Microsoft.Windows.ApplicationModel.Resources; -namespace FluentAutoClicker.Helpers +namespace FluentAutoClicker.Helpers; + +/// +/// Helper for getting localized strings from resources. +/// +public static class ResourceExtensions { + private static readonly ResourceLoader ResourceLoader = new(); + /// - /// Helper for getting localized strings from resources. + /// Gets the localized string for the resource key. /// - public static class ResourceExtensions + /// The resource key for the returned localized string. + /// The localized string for the specified resource key. + public static string GetLocalized(this string resourceKey) { - private static readonly ResourceLoader ResourceLoader = new(); - - /// - /// Gets the localized string for the resource key. - /// - /// The resource key for the returned localized string. - /// The localized string for the specified resource key. - public static string GetLocalized(this string resourceKey) - { - return ResourceLoader.GetString(resourceKey); - } + return ResourceLoader.GetString(resourceKey); } } \ No newline at end of file diff --git a/FluentAutoClicker/Helpers/WindowMessageHook.cs b/FluentAutoClicker/Helpers/WindowMessageHook.cs index 9f7fa2d..378f223 100644 --- a/FluentAutoClicker/Helpers/WindowMessageHook.cs +++ b/FluentAutoClicker/Helpers/WindowMessageHook.cs @@ -23,130 +23,129 @@ using System.Runtime.InteropServices; using System.Threading; -namespace FluentAutoClicker.Helpers +namespace FluentAutoClicker.Helpers; + +public class WindowMessageHook : IEquatable, IDisposable { - public class WindowMessageHook : IEquatable, IDisposable - { - private delegate nint SUBCLASSPROC(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData); + 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; + private static readonly ConcurrentDictionary _hooks = new(); + private static readonly SUBCLASSPROC _proc = SubclassProc; - public event EventHandler Message; - private nint _hWnd; + public event EventHandler Message; + private nint _hWnd; - public WindowMessageHook(Window window) : this(GetHandle(window)) { } - public WindowMessageHook(nint hWnd) + public WindowMessageHook(Window window) : this(GetHandle(window)) { } + public WindowMessageHook(nint hWnd) + { + if (hWnd == 0) { - if (hWnd == 0) - { - throw new ArgumentException(null, nameof(hWnd)); - } + 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)) + _hWnd = hWnd; + _ = _hooks.AddOrUpdate(hWnd, this, (k, o) => + { + if (Equals(o)) { - throw new Win32Exception(Marshal.GetLastWin32Error()); + return o; } - } - protected virtual void OnMessage(object sender, MessageEventArgs e) + o.Dispose(); + return this; + }); + if (!SetWindowSubclass(hWnd, _proc, 0, 0)) { - Message?.Invoke(sender, e); + throw new Win32Exception(Marshal.GetLastWin32Error()); } + } + + protected virtual void OnMessage(object sender, MessageEventArgs e) + { + Message?.Invoke(sender, e); + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!disposing) { - if (!disposing) - { - return; - } + return; + } - nint hWnd = Interlocked.Exchange(ref _hWnd, IntPtr.Zero); - if (hWnd != IntPtr.Zero) - { - _ = RemoveWindowSubclass(hWnd, _proc, 0); - _ = _hooks.Remove(hWnd, out _); - } + 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); } + ~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 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 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); + [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 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) + private static nint SubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData) + { + if (_hooks.TryGetValue(hWnd, out WindowMessageHook hook)) { - if (_hooks.TryGetValue(hWnd, out WindowMessageHook hook)) + MessageEventArgs e = new(hWnd, uMsg, wParam, lParam); + hook.OnMessage(hook, e); + if (e.Result.HasValue) { - MessageEventArgs e = new(hWnd, uMsg, wParam, lParam); - hook.OnMessage(hook, e); - if (e.Result.HasValue) - { - return e.Result.Value; - } + return e.Result.Value; } - return DefSubclassProc(hWnd, uMsg, wParam, lParam); - } - - public override int GetHashCode() - { - return _hWnd.GetHashCode(); } + return DefSubclassProc(hWnd, uMsg, wParam, lParam); + } - public override string ToString() - { - return _hWnd.ToString(); - } + public override int GetHashCode() + { + return _hWnd.GetHashCode(); + } - public override bool Equals(object obj) - { - return Equals(obj as WindowMessageHook); - } + public override string ToString() + { + return _hWnd.ToString(); + } - public virtual bool Equals(WindowMessageHook other) - { - return other != null && _hWnd.Equals(other._hWnd); - } + public override bool Equals(object obj) + { + return Equals(obj as WindowMessageHook); } - public class MessageEventArgs : EventArgs + public virtual bool Equals(WindowMessageHook other) { - public MessageEventArgs(nint hWnd, uint uMsg, nint wParam, nint lParam) - { - HWnd = hWnd; - Message = uMsg; - WParam = wParam; - LParam = lParam; - } + return other != null && _hWnd.Equals(other._hWnd); + } +} - public nint HWnd { get; } - public uint Message { get; } - public nint WParam { get; } - public nint LParam { get; } - public virtual nint? Result { get; set; } +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; } } \ No newline at end of file diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index 9c1ca85..b06e04d 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -27,179 +27,178 @@ using System.Threading.Tasks; using Windows.System; -namespace FluentAutoClicker +namespace FluentAutoClicker; + +/// +/// The main page containing all controls displayed on the main window. +/// +public sealed partial class MainPage : Page { - /// - /// The main page containing all controls displayed on the main window. - /// - public sealed partial class MainPage : Page + public MainPage() { - public MainPage() - { - InitializeComponent(); - Loaded += MainPage_Loaded; - } + InitializeComponent(); + Loaded += MainPage_Loaded; + } - private void MainPage_Loaded(object sender, RoutedEventArgs e) + private void MainPage_Loaded(object sender, RoutedEventArgs e) + { + WindowMessageHook hook = new(App.Window); + Unloaded += (s, e) => hook.Dispose(); // unhook on close + hook.Message += (s, e) => { - WindowMessageHook hook = new(App.Window); - Unloaded += (s, e) => hook.Dispose(); // unhook on close - hook.Message += (s, e) => + const int WM_HOTKEY = 0x312; + if (e.Message == WM_HOTKEY) { - const int WM_HOTKEY = 0x312; - if (e.Message == WM_HOTKEY) - { - // click on the button using UI Automation - ToggleButtonAutomationPeer pattern = (ToggleButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(StartToggleButton).GetPattern(PatternInterface.Toggle); - pattern.Toggle(); - } - }; - - // register CTRL + B as a global hotkey - nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); - int id = 1; // some arbitrary hotkey identifier - if (!RegisterHotKey(hwnd, id, MOD.MOD_NOREPEAT, VirtualKey.F6)) - { - throw new Win32Exception(Marshal.GetLastWin32Error()); + // click on the button using UI Automation + ToggleButtonAutomationPeer pattern = (ToggleButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(StartToggleButton).GetPattern(PatternInterface.Toggle); + pattern.Toggle(); } + }; - Unloaded += (s, e) => UnregisterHotKey(hwnd, id); // unregister hotkey on window close - } - - private void SetControlsEnabled(bool isEnabled) + // register CTRL + B as a global hotkey + nint hwnd = WinRT.Interop.WindowNative.GetWindowHandle(App.Window); + int id = 1; // some arbitrary hotkey identifier + if (!RegisterHotKey(hwnd, id, MOD.MOD_NOREPEAT, VirtualKey.F6)) { - NumberBoxHours.IsEnabled = isEnabled; - NumberBoxMinutes.IsEnabled = isEnabled; - NumberBoxSeconds.IsEnabled = isEnabled; - NumberBoxMilliseconds.IsEnabled = isEnabled; - MouseButtonTypeComboBox.IsEnabled = isEnabled; - ClickRepeatCheckBox.IsEnabled = isEnabled; - ClickOffsetCheckBox.IsEnabled = isEnabled; - //HotkeyButton.IsEnabled = isEnabled; - - if (ClickOffsetCheckBox.IsChecked == true) - { - ClickOffsetAmount.IsEnabled = isEnabled; - } + throw new Win32Exception(Marshal.GetLastWin32Error()); + } - if (ClickRepeatCheckBox.IsChecked == true) - { - ClickRepeatAmount.IsEnabled = isEnabled; - } + Unloaded += (s, e) => UnregisterHotKey(hwnd, id); // unregister hotkey on window close + } - // Gray out text if disabled - if (!isEnabled) - { - ClickIntervalTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseMediumLowBrush"] as Brush; - HotkeyTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseMediumLowBrush"] as Brush; - } - else - { - ClickIntervalTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseHighBrush"] as Brush; - HotkeyTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseHighBrush"] as Brush; - } + private void SetControlsEnabled(bool isEnabled) + { + NumberBoxHours.IsEnabled = isEnabled; + NumberBoxMinutes.IsEnabled = isEnabled; + NumberBoxSeconds.IsEnabled = isEnabled; + NumberBoxMilliseconds.IsEnabled = isEnabled; + MouseButtonTypeComboBox.IsEnabled = isEnabled; + ClickRepeatCheckBox.IsEnabled = isEnabled; + ClickOffsetCheckBox.IsEnabled = isEnabled; + //HotkeyButton.IsEnabled = isEnabled; + + if (ClickOffsetCheckBox.IsChecked == true) + { + ClickOffsetAmount.IsEnabled = isEnabled; } - private int GetIntervalMilliseconds() + if (ClickRepeatCheckBox.IsChecked == true) { - if (!int.TryParse(NumberBoxHours.Value.ToString(CultureInfo.InvariantCulture), out int hours)) - { - hours = 0; - NumberBoxHours.Value = hours; - } - - if (!int.TryParse(NumberBoxMinutes.Value.ToString(CultureInfo.InvariantCulture), out int minutes)) - { - minutes = 0; - NumberBoxMinutes.Value = minutes; - } - - if (!int.TryParse(NumberBoxSeconds.Value.ToString(CultureInfo.InvariantCulture), out int seconds)) - { - seconds = 0; - NumberBoxSeconds.Value = seconds; - } - - if (!int.TryParse(NumberBoxMilliseconds.Value.ToString(CultureInfo.InvariantCulture), out int milliseconds)) - { - milliseconds = 100; - NumberBoxMilliseconds.Value = milliseconds; - } - - int totalTimeInMilliseconds = (((((hours * 60) + minutes) * 60) + seconds) * 1000) + milliseconds; - - if (totalTimeInMilliseconds == 0) - { - totalTimeInMilliseconds = 1; - NumberBoxMilliseconds.Value = 1; - } - - return totalTimeInMilliseconds; + ClickRepeatAmount.IsEnabled = isEnabled; } - private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e) + // Gray out text if disabled + if (!isEnabled) { - StartToggleButton.IsEnabled = false; - SetControlsEnabled(false); - - // 3-second countdown - for (int i = 3; i > 0; i--) - { - StartToggleButton.Content = i.ToString(); - await Task.Delay(1000); - } - - StartToggleButton.IsEnabled = true; - StartToggleButton.Content = "Stop"; - - int clickInterval = GetIntervalMilliseconds(); - 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); + ClickIntervalTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseMediumLowBrush"] as Brush; + HotkeyTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseMediumLowBrush"] as Brush; } - - private void StartToggleButton_OnUnchecked(object sender, RoutedEventArgs e) + else { - StartToggleButton.Content = "Start"; - AutoClicker.StopAutoClicker(); - SetControlsEnabled(true); + ClickIntervalTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseHighBrush"] as Brush; + HotkeyTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseHighBrush"] as Brush; } + } - private void ClickRepeatCheckBox_Unchecked(object sender, RoutedEventArgs e) + private int GetIntervalMilliseconds() + { + if (!int.TryParse(NumberBoxHours.Value.ToString(CultureInfo.InvariantCulture), out int hours)) { - ClickRepeatAmount.IsEnabled = false; + hours = 0; + NumberBoxHours.Value = hours; } - private void ClickRepeatCheckBox_Checked(object sender, RoutedEventArgs e) + if (!int.TryParse(NumberBoxMinutes.Value.ToString(CultureInfo.InvariantCulture), out int minutes)) { - ClickRepeatAmount.IsEnabled = true; + minutes = 0; + NumberBoxMinutes.Value = minutes; } - // 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); + if (!int.TryParse(NumberBoxSeconds.Value.ToString(CultureInfo.InvariantCulture), out int seconds)) + { + seconds = 0; + NumberBoxSeconds.Value = seconds; + } - [Flags] - private enum MOD + if (!int.TryParse(NumberBoxMilliseconds.Value.ToString(CultureInfo.InvariantCulture), out int milliseconds)) { - MOD_ALT = 0x1, - MOD_CONTROL = 0x2, - MOD_SHIFT = 0x4, - MOD_WIN = 0x8, - MOD_NOREPEAT = 0x4000, + milliseconds = 100; + NumberBoxMilliseconds.Value = milliseconds; } - private void ClickOffsetCheckBox_Unchecked(object sender, RoutedEventArgs e) + + int totalTimeInMilliseconds = (((((hours * 60) + minutes) * 60) + seconds) * 1000) + milliseconds; + + if (totalTimeInMilliseconds == 0) { - ClickOffsetAmount.IsEnabled = false; + totalTimeInMilliseconds = 1; + NumberBoxMilliseconds.Value = 1; } - private void ClickOffsetCheckBox_Checked(object sender, RoutedEventArgs e) + return totalTimeInMilliseconds; + } + + private async void StartToggleButton_OnChecked(object sender, RoutedEventArgs e) + { + StartToggleButton.IsEnabled = false; + SetControlsEnabled(false); + + // 3-second countdown + for (int i = 3; i > 0; i--) { - ClickOffsetAmount.IsEnabled = true; + StartToggleButton.Content = i.ToString(); + await Task.Delay(1000); } + + StartToggleButton.IsEnabled = true; + StartToggleButton.Content = "Stop"; + + int clickInterval = GetIntervalMilliseconds(); + 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); + } + + private void StartToggleButton_OnUnchecked(object sender, RoutedEventArgs e) + { + StartToggleButton.Content = "Start"; + AutoClicker.StopAutoClicker(); + 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 + { + MOD_ALT = 0x1, + MOD_CONTROL = 0x2, + MOD_SHIFT = 0x4, + MOD_WIN = 0x8, + MOD_NOREPEAT = 0x4000, + } + private void ClickOffsetCheckBox_Unchecked(object sender, RoutedEventArgs e) + { + ClickOffsetAmount.IsEnabled = false; + } + + private void ClickOffsetCheckBox_Checked(object sender, RoutedEventArgs e) + { + ClickOffsetAmount.IsEnabled = true; } } \ No newline at end of file diff --git a/FluentAutoClicker/MainWindow.xaml.cs b/FluentAutoClicker/MainWindow.xaml.cs index e83f599..2115f53 100644 --- a/FluentAutoClicker/MainWindow.xaml.cs +++ b/FluentAutoClicker/MainWindow.xaml.cs @@ -20,24 +20,23 @@ // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. -namespace FluentAutoClicker +namespace FluentAutoClicker; + +/// +/// An window that displays a page's contents. +/// +public sealed partial class MainWindow { - /// - /// An window that displays a page's contents. - /// - public sealed partial class MainWindow + public MainWindow() { - public MainWindow() - { - InitializeComponent(); + InitializeComponent(); - // Set up window - Title = "AppDisplayName".GetLocalized(); - AppWindow.SetIcon("Assets/WindowIcon.ico"); + // Set up window + Title = "AppDisplayName".GetLocalized(); + AppWindow.SetIcon("Assets/WindowIcon.ico"); - // Set up window title bar - ExtendsContentIntoTitleBar = true; - AppTitleBar.Title = "AppDisplayName".GetLocalized(); - } + // Set up window title bar + ExtendsContentIntoTitleBar = true; + AppTitleBar.Title = "AppDisplayName".GetLocalized(); } } \ No newline at end of file diff --git a/FluentAutoClicker/Program.cs b/FluentAutoClicker/Program.cs index 2c50b74..855855d 100644 --- a/FluentAutoClicker/Program.cs +++ b/FluentAutoClicker/Program.cs @@ -24,97 +24,96 @@ using System.Threading; using System.Threading.Tasks; -namespace FluentAutoClicker +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 { - /// - /// 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 + [STAThread] + private static int Main(string[] args) { - [STAThread] - private static int Main(string[] args) - { - WinRT.ComWrappersSupport.InitializeComWrappers(); - bool isRedirect = DecideRedirection(); + WinRT.ComWrappersSupport.InitializeComWrappers(); + bool isRedirect = DecideRedirection(); - if (!isRedirect) + if (!isRedirect) + { + Application.Start((p) => { - Application.Start((p) => - { - DispatcherQueueSynchronizationContext context = new( - DispatcherQueue.GetForCurrentThread()); - SynchronizationContext.SetSynchronizationContext(context); - _ = new App(); - }); - } - - return 0; + DispatcherQueueSynchronizationContext context = new( + DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); } - private static bool DecideRedirection() - { - bool isRedirect = false; - AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs(); - ExtendedActivationKind kind = args.Kind; - AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp"); + return 0; + } - if (keyInstance.IsCurrent) - { - keyInstance.Activated += OnActivated; - } - else - { - isRedirect = true; - RedirectActivationTo(args, keyInstance); - } + private static bool DecideRedirection() + { + bool isRedirect = false; + AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs(); + ExtendedActivationKind kind = args.Kind; + AppInstance keyInstance = AppInstance.FindOrRegisterForKey("MySingleInstanceApp"); - return isRedirect; + if (keyInstance.IsCurrent) + { + keyInstance.Activated += OnActivated; + } + else + { + isRedirect = true; + RedirectActivationTo(args, keyInstance); } - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr CreateEvent( - IntPtr lpEventAttributes, bool bManualReset, - bool bInitialState, string lpName); - - [DllImport("kernel32.dll")] - private static extern bool SetEvent(IntPtr hEvent); - - [DllImport("ole32.dll")] - private static extern uint CoWaitForMultipleObjects( - uint dwFlags, uint dwMilliseconds, ulong nHandles, - IntPtr[] pHandles, out uint dwIndex); + return isRedirect; + } - [DllImport("user32.dll")] - private static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr CreateEvent( + IntPtr lpEventAttributes, bool bManualReset, + bool bInitialState, string lpName); - private static IntPtr redirectEventHandle = IntPtr.Zero; + [DllImport("kernel32.dll")] + private static extern bool SetEvent(IntPtr hEvent); - // Do the redirection on another thread, and use a non-blocking - // wait method to wait for the redirection to complete. - public static void RedirectActivationTo(AppActivationArguments args, - AppInstance keyInstance) - { - redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null); - _ = Task.Run(() => - { - keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); - _ = SetEvent(redirectEventHandle); - }); + [DllImport("ole32.dll")] + private static extern uint CoWaitForMultipleObjects( + uint dwFlags, uint dwMilliseconds, ulong nHandles, + IntPtr[] pHandles, out uint dwIndex); - uint CWMO_DEFAULT = 0; - uint INFINITE = 0xFFFFFFFF; - _ = CoWaitForMultipleObjects( - CWMO_DEFAULT, INFINITE, 1, - [redirectEventHandle], out uint handleIndex); + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); - // Bring the window to the foreground - Process process = Process.GetProcessById((int)keyInstance.ProcessId); - _ = SetForegroundWindow(process.MainWindowHandle); - } + private static IntPtr redirectEventHandle = IntPtr.Zero; - private static void OnActivated(object sender, AppActivationArguments args) + // Do the redirection on another thread, and use a non-blocking + // wait method to wait for the redirection to complete. + public static void RedirectActivationTo(AppActivationArguments args, + AppInstance keyInstance) + { + redirectEventHandle = CreateEvent(IntPtr.Zero, true, false, null); + _ = Task.Run(() => { - _ = args.Kind; - } + keyInstance.RedirectActivationToAsync(args).AsTask().Wait(); + _ = SetEvent(redirectEventHandle); + }); + + uint CWMO_DEFAULT = 0; + uint INFINITE = 0xFFFFFFFF; + _ = CoWaitForMultipleObjects( + CWMO_DEFAULT, INFINITE, 1, + [redirectEventHandle], out uint handleIndex); + + // Bring the window to the foreground + Process process = Process.GetProcessById((int)keyInstance.ProcessId); + _ = SetForegroundWindow(process.MainWindowHandle); + } + + private static void OnActivated(object sender, AppActivationArguments args) + { + _ = args.Kind; } } \ No newline at end of file From cc7f1fc5b682638a65ccad4115d2e20d2ab29faf Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sat, 7 Dec 2024 21:38:22 -0800 Subject: [PATCH 4/5] Move DefineConstants Signed-off-by: Ryan Luu --- FluentAutoClicker/FluentAutoClicker.csproj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/FluentAutoClicker/FluentAutoClicker.csproj b/FluentAutoClicker/FluentAutoClicker.csproj index 0031957..73e219d 100644 --- a/FluentAutoClicker/FluentAutoClicker.csproj +++ b/FluentAutoClicker/FluentAutoClicker.csproj @@ -10,6 +10,7 @@ win-$(Platform).pubxml true true + DISABLE_XAML_GENERATED_MAIN Ryan Luu Copyright (C) 2024 Ryan Luu @@ -50,11 +51,6 @@ False - - - DISABLE_XAML_GENERATED_MAIN - - False @@ -86,4 +82,4 @@ True True - \ No newline at end of file + From 52466a6f88e45ae299a09bdab865e64c2c925007 Mon Sep 17 00:00:00 2001 From: Ryan Luu Date: Sun, 8 Dec 2024 02:22:08 -0800 Subject: [PATCH 5/5] Add bug note --- FluentAutoClicker/MainPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FluentAutoClicker/MainPage.xaml.cs b/FluentAutoClicker/MainPage.xaml.cs index b06e04d..b39943b 100644 --- a/FluentAutoClicker/MainPage.xaml.cs +++ b/FluentAutoClicker/MainPage.xaml.cs @@ -87,7 +87,7 @@ private void SetControlsEnabled(bool isEnabled) ClickRepeatAmount.IsEnabled = isEnabled; } - // Gray out text if disabled + // TODO: Change this to use a custom control. See https://github.com/RyanLua/FluentAutoClicker/issues/42 if (!isEnabled) { ClickIntervalTextBlock.Foreground = Application.Current.Resources["SystemControlForegroundBaseMediumLowBrush"] as Brush;