-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMainWindow.xaml.cs
More file actions
337 lines (283 loc) · 13.8 KB
/
Copy pathMainWindow.xaml.cs
File metadata and controls
337 lines (283 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using Auto_Clicker.Models;
using Auto_Clicker.Services;
using Auto_Clicker.Native;
namespace Auto_Clicker
{
public partial class MainWindow : Window, IDisposable
{
private readonly AutoClickerService _clickerService = new AutoClickerService();
private const int HOTKEY_ID = 9000;
private uint _currentHotkeyVK = 0x75; // F6
private string _lastHotkeyName = "F6";
public MainWindow()
{
InitializeComponent();
LoadWindowsList();
}
// --- CORE LOGIC BINDINGS ---
private static int ParseIntOrDefault(TextBox box) => box != null && int.TryParse(box.Text, out int value) ? value : 0;
private void LoadWindowsList()
{
if (cmbTargetWindow != null)
{
var windows = WindowService.GetOpenWindows();
cmbTargetWindow.ItemsSource = windows;
}
}
private void BtnRefreshWindows_Click(object sender, RoutedEventArgs e)
{
LoadWindowsList();
}
private async void BtnStart_Click(object sender, RoutedEventArgs e)
{
if (_clickerService.IsRunning) return;
// Calculate total interval from hours/minutes/seconds/milliseconds fields
int hours = ParseIntOrDefault(txtHours);
int mins = ParseIntOrDefault(txtMins);
int secs = ParseIntOrDefault(txtSecs);
int ms = ParseIntOrDefault(txtMillisecs);
// long math avoids silent int32 overflow/wraparound on very large hour values
long totalIntervalLong = (hours * 3600000L) + (mins * 60000L) + (secs * 1000L) + ms;
int totalIntervalMs = (int)Math.Min(Math.Max(totalIntervalLong, 0), int.MaxValue);
// Fallback to 100ms if all fields are empty/zero
if (totalIntervalMs <= 0) totalIntervalMs = 100;
// Target coordinates
int posX = ParseIntOrDefault(txtPosX);
int posY = ParseIntOrDefault(txtPosY);
// No explicit "fixed position" checkbox before; now driven directly by the location radio buttons
bool useFixedPos = rbPickLocation?.IsChecked ?? false;
var selectedWindow = cmbTargetWindow?.SelectedItem as WindowInfo;
IntPtr targetHandle = selectedWindow?.Handle ?? IntPtr.Zero;
// Without a target window, "background" clicks would fall back to real global
// clicks at the cursor location, hitting whatever window has focus - refuse instead.
if ((chkRunInBackground?.IsChecked ?? false) && targetHandle == IntPtr.Zero)
{
ShowInfo("No Target Window Selected", "Run in Background requires a target window. Please select one from the list (or click Refresh if it's empty) before starting.");
return;
}
UpdateUIState(true);
// Start the clicking service
int randomRangeValue = ParseIntOrDefault(txtRandomPercent);
string selectedButton = (cmbMouseButton?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "Left";
string selectedClickType = (cmbClickType?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "Single";
// "Repeat N times" vs "Repeat until stopped" (0 = until stopped)
bool repeatFixedTimes = rbRepeatTimes?.IsChecked ?? false;
int repeatCount = repeatFixedTimes ? Math.Max(ParseIntOrDefault(txtRepeatTimes), 1) : 0;
await _clickerService.StartClickingAsync(
intervalMs: totalIntervalMs,
useRandomizer: chkRandomize?.IsChecked ?? false,
randomPercent: randomRangeValue,
useFixedLocation: useFixedPos,
x: posX,
y: posY,
button: selectedButton,
clickType: selectedClickType,
runInBackground: chkRunInBackground?.IsChecked ?? false,
targetHandle: targetHandle,
repeatCount: repeatCount
);
UpdateUIState(false);
}
private void BtnStop_Click(object sender, RoutedEventArgs e)
{
_clickerService.StopClicking();
UpdateUIState(false);
}
private void UpdateUIState(bool isRunning)
{
if (btnStart != null) btnStart.IsEnabled = !isRunning;
if (btnStop != null) btnStop.IsEnabled = isRunning;
}
// --- UI EVENT HANDLERS ---
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left) this.DragMove();
}
private void BtnClose_Click(object sender, RoutedEventArgs e) => this.Close();
private void BtnMinimize_Click(object sender, RoutedEventArgs e) => this.WindowState = WindowState.Minimized;
private void ShowInfo(string title, string message)
{
var info = new InfoWindow(title, message) { Owner = this };
info.ShowDialog();
}
private void BtnInfo_Click(object sender, RoutedEventArgs e)
{
string infoText =
"This feature adds human-like unpredictability to your automated clicks by dynamically altering the delay between each click.\n\n" +
"How it Works:\n" +
"The system calculates a time variance based on the percentage you input. (Note: The base interval must be strictly greater than 20ms).\n\n" +
"The Calculation:\n" +
" • Variance = Base Interval × (Percentage ÷ 100)\n" +
" • Minimum Delay = Base Interval - Variance\n" +
" • Maximum Delay = Base Interval + Variance\n\n" +
"Example:\n" +
"If your base interval is 1000ms with a 20% range, the actual delay for each click will be randomly generated between 800ms and 1200ms.";
ShowInfo("Information", infoText);
}
private void BtnInfoHotkey_Click(object sender, RoutedEventArgs e) =>
ShowInfo("Hotkey Info", "Press the assigned hotkey to Start/Stop the clicker globally.");
private void BtnInfoBackground_Click(object sender, RoutedEventArgs e)
{
string infoText =
"This feature allows the auto-clicker to send simulated clicks to an application even when it is not actively focused or is placed behind other windows.\n\n" +
"Recommendation:\n" +
"For absolute precision and flawless performance, it is highly recommended to combine this feature with the 'Pick Location' method.\n\n" +
"• Why?\n" +
"Using 'Pick Location' ensures that the background clicks are injected directly into the exact coordinates of your targeted application, completely preventing accidental clicks on unintended areas.";
ShowInfo("Information", infoText);
}
private void NumericTextBox_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (sender is TextBox txt && int.TryParse(txt.Text, out int value))
{
value += e.Delta > 0 ? 1 : -1;
if (value < 0) value = 0; // Prevent negative values
txt.Text = value.ToString();
}
}
// Pick-location overlay: click anywhere on screen to capture coordinates
private void BtnPickLocation_Click(object sender, RoutedEventArgs e)
{
bool wasTopmost = this.Topmost;
// SetCurrentValue (not a direct assignment) so the Topmost <-> chkTopMost binding survives
this.SetCurrentValue(TopmostProperty, false);
this.Hide();
Window overlay = new Window
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)), // Nearly transparent, click-through overlay
Topmost = true,
Cursor = Cursors.Cross,
ShowInTaskbar = false,
Left = SystemParameters.VirtualScreenLeft,
Top = SystemParameters.VirtualScreenTop,
Width = SystemParameters.VirtualScreenWidth,
Height = SystemParameters.VirtualScreenHeight
};
// Layered/transparent windows often fail to show a custom Cursor over near-transparent
// pixels, so force it at the application level for the duration of the overlay.
Mouse.OverrideCursor = Cursors.Cross;
overlay.PreviewMouseDown += (s, ev) =>
{
if (ev.LeftButton == MouseButtonState.Pressed)
{
NativeMethods.GetCursorPos(out var point);
if (txtPosX != null) txtPosX.Text = point.X.ToString();
if (txtPosY != null) txtPosY.Text = point.Y.ToString();
overlay.Close();
}
};
overlay.PreviewKeyDown += (s, ev) =>
{
if (ev.Key == Key.Escape) overlay.Close();
};
overlay.ShowDialog();
Mouse.OverrideCursor = null;
this.Show();
this.SetCurrentValue(TopmostProperty, wasTopmost);
this.Activate();
}
// --- HOTKEY BINDINGS ---
// TextBox clicked: enter hotkey capture mode
private void TxtHotkey_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox txt)
{
txt.Text = "press any key...";
txt.Foreground = new SolidColorBrush(Colors.Red);
txt.Focus();
e.Handled = true; // Suppress default text-selection behavior
}
}
// User clicked away without pressing a key: restore previous state
private void TxtHotkey_LostFocus(object sender, RoutedEventArgs e)
{
if (sender is TextBox txt)
{
if (txt.Text == "press any key...")
{
txt.Text = _lastHotkeyName;
}
txt.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2563EB"));
}
}
private void TxtHotkey_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
Key key = (e.Key == Key.System ? e.SystemKey : e.Key);
if (sender is TextBox txt)
{
// Cancel capture and release focus
if (key == Key.Escape || key == Key.Tab || key == Key.Enter || key == Key.LWin || key == Key.RWin)
{
Keyboard.ClearFocus();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(txt), null);
return;
}
_currentHotkeyVK = (uint)KeyInterop.VirtualKeyFromKey(key);
string keyName = key.ToString();
_lastHotkeyName = keyName;
txt.Text = keyName;
txt.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#2563EB"));
// Re-register hotkey
IntPtr handle = new WindowInteropHelper(this).Handle;
NativeMethods.UnregisterHotKey(handle, HOTKEY_ID);
bool registered = NativeMethods.RegisterHotKey(handle, HOTKEY_ID, 0, _currentHotkeyVK);
if (!registered)
ShowInfo("Hotkey Registration Failed", $"'{keyName}' could not be registered as a global hotkey. It may already be in use by another application. You can still use the on-screen Start/Stop buttons.");
if (btnStart != null) btnStart.Content = $"START ({keyName})";
if (btnStop != null) btnStop.Content = $"STOP ({keyName})";
// Release keyboard and logical focus
Keyboard.ClearFocus();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(txt), null);
}
}
// --- WINDOW LIFECYCLE & GLOBAL HOOK ---
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
IntPtr handle = new WindowInteropHelper(this).Handle;
HwndSource source = HwndSource.FromHwnd(handle);
source.AddHook(HwndHook);
// Register the initial hotkey
bool registered = NativeMethods.RegisterHotKey(handle, HOTKEY_ID, 0, _currentHotkeyVK);
if (!registered)
ShowInfo("Hotkey Registration Failed", "Could not register the default global hotkey. It may already be in use by another application. You can still use the on-screen Start/Stop buttons, or assign a different hotkey.");
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_HOTKEY = 0x0312;
if (msg == WM_HOTKEY && wParam.ToInt32() == HOTKEY_ID)
{
if (_clickerService.IsRunning)
{
BtnStop_Click(null, null);
}
else
{
BtnStart_Click(null, null);
}
handled = true;
}
return IntPtr.Zero;
}
public void Dispose()
{
IntPtr handle = new WindowInteropHelper(this).Handle;
NativeMethods.UnregisterHotKey(handle, HOTKEY_ID);
_clickerService?.Dispose();
}
protected override void OnClosed(EventArgs e)
{
Dispose();
base.OnClosed(e);
}
}
}