Skip to content

Commit 7289b3b

Browse files
committed
Allow user to stop sending mouse clicks after the mouse is moved
1 parent a0a081c commit 7289b3b

File tree

10 files changed

+172
-1
lines changed

10 files changed

+172
-1
lines changed

auto-clicker.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
/* Begin PBXBuildFile section */
1010
4C5D699D2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D699C2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift */; };
1111
4C5D699F2A4EED3500E72CEE /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D699E2A4EED3500E72CEE /* NotificationService.swift */; };
12+
5DB2BECC2A54826B008AFE05 /* MouseMoveSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */; };
13+
5DB2BECE2A5482DC008AFE05 /* MouseMoveModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */; };
14+
5DB2BED02A548325008AFE05 /* MouseMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECF2A548325008AFE05 /* MouseMove.swift */; };
1215
B50022CA2875F5BF00610474 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50022C92875F5BF00610474 /* HelpCommands.swift */; };
1316
B510760927F4A21500BB1CDA /* DateStrings in Frameworks */ = {isa = PBXBuildFile; productRef = B510760827F4A21500BB1CDA /* DateStrings */; };
1417
B510760C27F4A23300BB1CDA /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = B510760B27F4A23300BB1CDA /* KeyboardShortcuts */; };
@@ -77,6 +80,9 @@
7780
/* Begin PBXFileReference section */
7881
4C5D699C2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsSettingsTabView.swift; sourceTree = "<group>"; };
7982
4C5D699E2A4EED3500E72CEE /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
83+
5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMoveSelector.swift; sourceTree = "<group>"; };
84+
5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMoveModal.swift; sourceTree = "<group>"; };
85+
5DB2BECF2A548325008AFE05 /* MouseMove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMove.swift; sourceTree = "<group>"; };
8086
B50022C92875F5BF00610474 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; };
8187
B510760E27F4A25400BB1CDA /* WindowStateService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowStateService.swift; sourceTree = "<group>"; };
8288
B510761027F4A33D00BB1CDA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
@@ -333,6 +339,8 @@
333339
B5F6A01A27F3A8A6003CD730 /* StatBox.swift */,
334340
B510761E27F4B6F500BB1CDA /* KeyboardShortcutHint.swift */,
335341
B5BCE808287C2F4D00B739AD /* SmallText.swift */,
342+
5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */,
343+
5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */,
336344
);
337345
path = Components;
338346
sourceTree = "<group>";
@@ -380,6 +388,7 @@
380388
isa = PBXGroup;
381389
children = (
382390
B5E6395527CA76CB008B111A /* Duration.swift */,
391+
5DB2BECF2A548325008AFE05 /* MouseMove.swift */,
383392
B5BCE802287BF59900B739AD /* Colour.swift */,
384393
);
385394
path = Enums;
@@ -579,6 +588,7 @@
579588
4C5D699F2A4EED3500E72CEE /* NotificationService.swift in Sources */,
580589
B510763227FF7EC800BB1CDA /* InputAwareView.swift in Sources */,
581590
B5B6B46328032D3200C779FD /* PermissionsView.swift in Sources */,
591+
5DB2BED02A548325008AFE05 /* MouseMove.swift in Sources */,
582592
B510762327F4BB5F00BB1CDA /* KeyboardShortcutsSettingsTabView.swift in Sources */,
583593
B510761A27F4A5BD00BB1CDA /* AppDelegate.swift in Sources */,
584594
B5D603F528830A3600655D2C /* SettingsTabItemView.swift in Sources */,
@@ -607,6 +617,7 @@
607617
B5E92B1427F1087E00A7FC63 /* UnderlinedTextFieldStyle.swift in Sources */,
608618
B510762D27F4BF0C00BB1CDA /* Defaults+Workaround.swift in Sources */,
609619
B510761627F4A43B00BB1CDA /* GeneralSettingsTabView.swift in Sources */,
620+
5DB2BECE2A5482DC008AFE05 /* MouseMoveModal.swift in Sources */,
610621
B510761827F4A58500BB1CDA /* KeyboardShortcuts.swift in Sources */,
611622
B510763527FF811B00BB1CDA /* PressKeyListenerModal.swift in Sources */,
612623
B53027FA264C2748002B8610 /* DelayTimer.swift in Sources */,
@@ -624,6 +635,7 @@
624635
B5E92B1C27F1BA5E00A7FC63 /* ThemeService.swift in Sources */,
625636
C4345BB52846056000365CF9 /* ProcessInfo+Extensions.swift in Sources */,
626637
B5E6395327CA62EB008B111A /* ThemedButtonStyle.swift in Sources */,
638+
5DB2BECC2A54826B008AFE05 /* MouseMoveSelector.swift in Sources */,
627639
B510762127F4BB4900BB1CDA /* AppearanceSettingsTabView.swift in Sources */,
628640
B5BF094328832A4E008092D9 /* MenuBarView.swift in Sources */,
629641
);

auto-clicker.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

auto-clicker/Build Assets/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</dict>
3131
</array>
3232
<key>CFBundleVersion</key>
33-
<string>97ad514</string>
33+
<string>ef40c75</string>
3434
<key>LSApplicationCategoryType</key>
3535
<string>public.app-category.utilities</string>
3636
<key>LSMinimumSystemVersion</key>

auto-clicker/Enums/MouseMove.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// MouseMove.swift
3+
// auto-clicker
4+
//
5+
// Created by Tomasz Pędraszewski on 04/07/2023.
6+
//
7+
8+
import Foundation
9+
import SwiftUI
10+
11+
enum MouseMove: String, CustomStringConvertible, CaseIterable, Identifiable, Codable {
12+
case enabled = "mousemove_enabled"
13+
case disabled = "mousemove_disabled"
14+
15+
var id: String {
16+
self.rawValue
17+
}
18+
19+
var description: String {
20+
self.rawValue
21+
}
22+
23+
var localised: LocalizedStringKey {
24+
LocalizedStringKey(self.description)
25+
}
26+
27+
var textView: some View {
28+
switch self {
29+
case .enabled, .disabled:
30+
return Text(self.description)
31+
}
32+
}
33+
34+
func buttonView(action: @escaping () -> Void) -> some View {
35+
switch self {
36+
case .enabled, .disabled:
37+
return Button(action: action) {
38+
Text(self.localised, comment: "Mouse move option buttons")
39+
}
40+
}
41+
}
42+
43+
func asBoolean() -> Bool {
44+
switch self {
45+
case .enabled:
46+
return true
47+
case .disabled:
48+
return false
49+
}
50+
}
51+
}

auto-clicker/Localisation/en-GB.lproj/Localizable.strings

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
"main_window_second" = "second";
1616
"main_window_seconds" = "seconds";
1717
"main_window_before_starting" = " before starting";
18+
"main_window_stop_mousemove" = "And";
19+
"main_window_stop_mousemove_end" = "when the mouse is moved";
1820
"main_window_start_btn" = "START";
1921
"main_window_stop_btn" = "STOP";
2022
"main_window_stat_box_next_press_at" = "NEXT PRESS AT";
@@ -27,6 +29,10 @@
2729
"duration_hours" = "Hours(s)";
2830
"duration_modal_cancel_button" = "Cancel";
2931

32+
"mousemove_enabled" = "stop";
33+
"mousemove_disabled" = "continue";
34+
"mousemove_modal_cancel_button" = "Cancel";
35+
3036
"key_listener_modal_press_prompt" = "Press your desired input...";
3137
"key_listener_modal_dismiss_key_prompt" = "Hold the escape key when done.";
3238
"key_listener_modal_dismiss_key_override" = "To use the escape key itself, press it twice.";

auto-clicker/Models/FormState.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct FormState: Codable, Defaults.Serializable {
1414
var pressAmount: Int
1515
var startDelay: Int
1616
var repeatAmount: Int
17+
var stopOnMouseMove: MouseMove
1718
}
1819

1920
extension FormState {
@@ -24,5 +25,6 @@ extension FormState {
2425
self.pressAmount = DEFAULT_PRESS_AMOUNT
2526
self.startDelay = DEFAULT_START_DELAY
2627
self.repeatAmount = DEFAULT_REPEAT_AMOUNT
28+
self.stopOnMouseMove = MouseMove.disabled
2729
}
2830
}

auto-clicker/Observable Objects/AutoClickSimulator.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ final class AutoClickSimulator: ObservableObject {
3131
private var timer: Timer?
3232
private var mouseLocation: NSPoint { NSEvent.mouseLocation }
3333
private var activity: Cancellable?
34+
35+
private var monitorObject: Any? = nil
3436

3537
func start() {
3638
self.isAutoClicking = true
@@ -61,6 +63,10 @@ final class AutoClickSimulator: ObservableObject {
6163
userInfo: nil,
6264
repeats: true)
6365

66+
if (Defaults[.autoClickerState].stopOnMouseMove.asBoolean()) {
67+
startMouseMonitoring()
68+
}
69+
6470
if Defaults[.notifyOnStart] {
6571
NotificationService.scheduleNotification(title: "Started", date: self.nextClickAt)
6672
}
@@ -72,6 +78,10 @@ final class AutoClickSimulator: ObservableObject {
7278

7379
func stop() {
7480
self.isAutoClicking = false
81+
82+
if let monitorObject = self.monitorObject {
83+
NSEvent.removeMonitor(monitorObject)
84+
}
7585

7686
if let startMenuItem = MenuBarService.startMenuItem {
7787
startMenuItem.isEnabled = true
@@ -105,6 +115,12 @@ final class AutoClickSimulator: ObservableObject {
105115
self.stop()
106116
}
107117
}
118+
119+
private func startMouseMonitoring() {
120+
self.monitorObject = NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { [weak self] event in
121+
self?.stop()
122+
}
123+
}
108124

109125
private let mouseDownEventMap: [NSEvent.EventType: CGEventType] = [
110126
.leftMouseDown: .leftMouseDown,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// MouseMoveModal.swift
3+
// auto-clicker
4+
//
5+
// Created by Tomasz Pędraszewski on 04/07/2023.
6+
//
7+
8+
import SwiftUI
9+
import Defaults
10+
11+
struct MouseMoveModal: View {
12+
@Environment(\.presentationMode) private var presentationMode
13+
14+
@Default(.appearanceSelectedTheme) private var activeTheme
15+
16+
@Binding var selected: MouseMove
17+
18+
var body: some View {
19+
VStack {
20+
ForEach(MouseMove.allCases) { unit in
21+
unit.buttonView(action: {
22+
self.presentationMode.wrappedValue.dismiss()
23+
24+
self.selected = unit
25+
})
26+
.buttonStyle(ModalButtonStyle())
27+
}
28+
29+
// 'init(_:role:action:)' is only available in macOS 12.0 or newer
30+
// so cannot use a .destructive theme
31+
Button("mousemove_modal_cancel_button") {
32+
self.presentationMode.wrappedValue.dismiss()
33+
}
34+
.buttonStyle(ModalButtonStyle(isDestructive: true))
35+
}
36+
.frame(width: 200, height: 220)
37+
.padding(.vertical, 14)
38+
.padding(.horizontal, 5)
39+
.background(self.activeTheme.backgroundColour)
40+
.ignoresSafeArea()
41+
}
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// MouseMoveSelector.swift
3+
// auto-clicker
4+
//
5+
// Created by Tomasz Pędraszewski on 04/07/2023.
6+
//
7+
8+
import SwiftUI
9+
10+
struct MouseMoveSelector: View {
11+
@State private var showingMouseMoveModal = false
12+
13+
@Binding var selectedMouseMove: MouseMove
14+
15+
var body: some View {
16+
Button(self.selectedMouseMove.localised) {
17+
self.showingMouseMoveModal = true
18+
}
19+
.buttonStyle(UnderlinedButtonStyle())
20+
.sheet(isPresented: self.$showingMouseMoveModal, content: {
21+
MouseMoveModal(selected: self.$selectedMouseMove)
22+
})
23+
}
24+
}

auto-clicker/Views/Main/MainView.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ struct MainView: View {
130130

131131
Text(self.formState.startDelay == 1 ? "main_window_second" : "main_window_seconds", comment: "Main window 'second(s)'") + Text("main_window_before_starting", comment: "Main window 'before starting'") + Text("main_window_full_stop", comment: "Main window full stop")
132132
}
133+
134+
ActionStageLine {
135+
Text("main_window_stop_mousemove", comment: "Main window 'Stop mouse move'")
136+
137+
MouseMoveSelector(selectedMouseMove: self.$formState.stopOnMouseMove)
138+
.disabled(self.hasStarted)
139+
140+
Text("main_window_stop_mousemove_end", comment: "Main window 'Stop mouse move end'")
141+
}
133142
}
134143
.padding(.top, 20)
135144
.padding(.leading, 20)

0 commit comments

Comments
 (0)