Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to stop sending mouse clicks after the mouse is moved #71

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions auto-clicker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/* Begin PBXBuildFile section */
4C5D699D2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D699C2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift */; };
4C5D699F2A4EED3500E72CEE /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D699E2A4EED3500E72CEE /* NotificationService.swift */; };
5DB2BECC2A54826B008AFE05 /* MouseMoveSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */; };
5DB2BECE2A5482DC008AFE05 /* MouseMoveModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */; };
5DB2BED02A548325008AFE05 /* MouseMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DB2BECF2A548325008AFE05 /* MouseMove.swift */; };
B50022CA2875F5BF00610474 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50022C92875F5BF00610474 /* HelpCommands.swift */; };
B510760927F4A21500BB1CDA /* DateStrings in Frameworks */ = {isa = PBXBuildFile; productRef = B510760827F4A21500BB1CDA /* DateStrings */; };
B510760C27F4A23300BB1CDA /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = B510760B27F4A23300BB1CDA /* KeyboardShortcuts */; };
Expand Down Expand Up @@ -77,6 +80,9 @@
/* Begin PBXFileReference section */
4C5D699C2A4ECDF800E72CEE /* NotificationsSettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsSettingsTabView.swift; sourceTree = "<group>"; };
4C5D699E2A4EED3500E72CEE /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMoveSelector.swift; sourceTree = "<group>"; };
5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMoveModal.swift; sourceTree = "<group>"; };
5DB2BECF2A548325008AFE05 /* MouseMove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseMove.swift; sourceTree = "<group>"; };
B50022C92875F5BF00610474 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = "<group>"; };
B510760E27F4A25400BB1CDA /* WindowStateService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowStateService.swift; sourceTree = "<group>"; };
B510761027F4A33D00BB1CDA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -333,6 +339,8 @@
B5F6A01A27F3A8A6003CD730 /* StatBox.swift */,
B510761E27F4B6F500BB1CDA /* KeyboardShortcutHint.swift */,
B5BCE808287C2F4D00B739AD /* SmallText.swift */,
5DB2BECB2A54826B008AFE05 /* MouseMoveSelector.swift */,
5DB2BECD2A5482DC008AFE05 /* MouseMoveModal.swift */,
);
path = Components;
sourceTree = "<group>";
Expand Down Expand Up @@ -380,6 +388,7 @@
isa = PBXGroup;
children = (
B5E6395527CA76CB008B111A /* Duration.swift */,
5DB2BECF2A548325008AFE05 /* MouseMove.swift */,
B5BCE802287BF59900B739AD /* Colour.swift */,
);
path = Enums;
Expand Down Expand Up @@ -579,6 +588,7 @@
4C5D699F2A4EED3500E72CEE /* NotificationService.swift in Sources */,
B510763227FF7EC800BB1CDA /* InputAwareView.swift in Sources */,
B5B6B46328032D3200C779FD /* PermissionsView.swift in Sources */,
5DB2BED02A548325008AFE05 /* MouseMove.swift in Sources */,
B510762327F4BB5F00BB1CDA /* KeyboardShortcutsSettingsTabView.swift in Sources */,
B510761A27F4A5BD00BB1CDA /* AppDelegate.swift in Sources */,
B5D603F528830A3600655D2C /* SettingsTabItemView.swift in Sources */,
Expand Down Expand Up @@ -607,6 +617,7 @@
B5E92B1427F1087E00A7FC63 /* UnderlinedTextFieldStyle.swift in Sources */,
B510762D27F4BF0C00BB1CDA /* Defaults+Workaround.swift in Sources */,
B510761627F4A43B00BB1CDA /* GeneralSettingsTabView.swift in Sources */,
5DB2BECE2A5482DC008AFE05 /* MouseMoveModal.swift in Sources */,
B510761827F4A58500BB1CDA /* KeyboardShortcuts.swift in Sources */,
B510763527FF811B00BB1CDA /* PressKeyListenerModal.swift in Sources */,
B53027FA264C2748002B8610 /* DelayTimer.swift in Sources */,
Expand All @@ -624,6 +635,7 @@
B5E92B1C27F1BA5E00A7FC63 /* ThemeService.swift in Sources */,
C4345BB52846056000365CF9 /* ProcessInfo+Extensions.swift in Sources */,
B5E6395327CA62EB008B111A /* ThemedButtonStyle.swift in Sources */,
5DB2BECC2A54826B008AFE05 /* MouseMoveSelector.swift in Sources */,
B510762127F4BB4900BB1CDA /* AppearanceSettingsTabView.swift in Sources */,
B5BF094328832A4E008092D9 /* MenuBarView.swift in Sources */,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
"revision" : "0dcedd56994d871f243f3d9c76590bfd9f8aba69",
"version" : "1.5.0"
}
},
{
"identity" : "launchatlogin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin",
"state" : {
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
"version" : "4.2.0"
}
}
],
"version" : 2
Expand Down
2 changes: 1 addition & 1 deletion auto-clicker/Build Assets/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>97ad514</string>
<string>51625fa</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
Expand Down
5 changes: 5 additions & 0 deletions auto-clicker/Constants/FieldConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ let MAX_REPEAT_AMOUNT: Int = 100_000_000

let DEFAULT_REPEAT_AMOUNT: Int = 100

// MARK: - Mouse movement detection

let MIN_MOUSE_THRESHOLD: Int = 0
let MAX_MOUSE_THRESHOLD: Int = 1000

// MARK: - Start Delay

let MIN_START_DELAY: Int = 0
Expand Down
51 changes: 51 additions & 0 deletions auto-clicker/Enums/MouseMove.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// MouseMove.swift
// auto-clicker
//
// Created by Tomasz Pędraszewski on 04/07/2023.
//

import Foundation
import SwiftUI

enum MouseMove: String, CustomStringConvertible, CaseIterable, Identifiable, Codable {
case enabled = "mousemove_enabled"
case disabled = "mousemove_disabled"

var id: String {
self.rawValue
}

var description: String {
self.rawValue
}

var localised: LocalizedStringKey {
LocalizedStringKey(self.description)
}

var textView: some View {
switch self {
case .enabled, .disabled:
return Text(self.description)
}
}

func buttonView(action: @escaping () -> Void) -> some View {
switch self {
case .enabled, .disabled:
return Button(action: action) {
Text(self.localised, comment: "Mouse move option buttons")
}
}
}

func asBoolean() -> Bool {
switch self {
case .enabled:
return true
case .disabled:
return false
}
}
}
7 changes: 7 additions & 0 deletions auto-clicker/Localisation/en-GB.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"main_window_second" = "second";
"main_window_seconds" = "seconds";
"main_window_before_starting" = " before starting";
"main_window_stop_mousemove" = "And";
"main_window_stop_mousemove_end" = "when the mouse moves";
"main_window_stop_mousemove_pixel" = "pixels";
"main_window_start_btn" = "START";
"main_window_stop_btn" = "STOP";
"main_window_stat_box_next_press_at" = "NEXT PRESS AT";
Expand All @@ -27,6 +30,10 @@
"duration_hours" = "Hours(s)";
"duration_modal_cancel_button" = "Cancel";

"mousemove_enabled" = "stop";
"mousemove_disabled" = "continue";
"mousemove_modal_cancel_button" = "Cancel";

"key_listener_modal_press_prompt" = "Press your desired input...";
"key_listener_modal_dismiss_key_prompt" = "Hold the escape key when done.";
"key_listener_modal_dismiss_key_override" = "To use the escape key itself, press it twice.";
Expand Down
4 changes: 4 additions & 0 deletions auto-clicker/Models/FormState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ struct FormState: Codable, Defaults.Serializable {
var pressAmount: Int
var startDelay: Int
var repeatAmount: Int
var stopOnMouseMove: MouseMove
var mouseDeltaThreshold: Int
}

extension FormState {
Expand All @@ -24,5 +26,7 @@ extension FormState {
self.pressAmount = DEFAULT_PRESS_AMOUNT
self.startDelay = DEFAULT_START_DELAY
self.repeatAmount = DEFAULT_REPEAT_AMOUNT
self.stopOnMouseMove = MouseMove.disabled
self.mouseDeltaThreshold = MIN_MOUSE_THRESHOLD
}
}
35 changes: 35 additions & 0 deletions auto-clicker/Observable Objects/AutoClickSimulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ final class AutoClickSimulator: ObservableObject {
private var timer: Timer?
private var mouseLocation: NSPoint { NSEvent.mouseLocation }
private var activity: Cancellable?

private var monitorObject: Any? = nil
private var initialMousePosition: NSPoint? = nil
private var mouseDeltaThreshold: CGFloat = 0.0

func start() {
self.isAutoClicking = true
Expand Down Expand Up @@ -61,6 +65,12 @@ final class AutoClickSimulator: ObservableObject {
userInfo: nil,
repeats: true)

if (Defaults[.autoClickerState].stopOnMouseMove.asBoolean()) {
self.initialMousePosition = nil
self.mouseDeltaThreshold = CGFloat(Defaults[.autoClickerState].mouseDeltaThreshold)
startMouseMonitoring()
}

if Defaults[.notifyOnStart] {
NotificationService.scheduleNotification(title: "Started", date: self.nextClickAt)
}
Expand All @@ -72,6 +82,11 @@ final class AutoClickSimulator: ObservableObject {

func stop() {
self.isAutoClicking = false

if let monitorObject = self.monitorObject {
NSEvent.removeMonitor(monitorObject)
self.monitorObject = nil
}

if let startMenuItem = MenuBarService.startMenuItem {
startMenuItem.isEnabled = true
Expand Down Expand Up @@ -105,6 +120,26 @@ final class AutoClickSimulator: ObservableObject {
self.stop()
}
}

private func startMouseMonitoring() {
self.monitorObject = NSEvent.addGlobalMonitorForEvents(matching: [.mouseMoved]) { [weak self] event in
self?.mouseMoved(event)
}
}

private func mouseMoved(_ event: NSEvent) {
let position = event.locationInWindow
if let initialPosition = self.initialMousePosition {
let deltaX = position.x - initialPosition.x
let deltaY = position.y - initialPosition.y
let distance = sqrt(deltaX * deltaX + deltaY * deltaY)
if (distance > mouseDeltaThreshold) {
self.stop()
}
} else {
self.initialMousePosition = position
}
}

private let mouseDownEventMap: [NSEvent.EventType: CGEventType] = [
.leftMouseDown: .leftMouseDown,
Expand Down
42 changes: 42 additions & 0 deletions auto-clicker/Views/Main/Components/MouseMoveModal.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// MouseMoveModal.swift
// auto-clicker
//
// Created by Tomasz Pędraszewski on 04/07/2023.
//

import SwiftUI
import Defaults

struct MouseMoveModal: View {
@Environment(\.presentationMode) private var presentationMode

@Default(.appearanceSelectedTheme) private var activeTheme

@Binding var selected: MouseMove

var body: some View {
VStack {
ForEach(MouseMove.allCases) { unit in
unit.buttonView(action: {
self.presentationMode.wrappedValue.dismiss()

self.selected = unit
})
.buttonStyle(ModalButtonStyle())
}

// 'init(_:role:action:)' is only available in macOS 12.0 or newer
// so cannot use a .destructive theme
Button("mousemove_modal_cancel_button") {
self.presentationMode.wrappedValue.dismiss()
}
.buttonStyle(ModalButtonStyle(isDestructive: true))
}
.frame(width: 200, height: 220)
.padding(.vertical, 14)
.padding(.horizontal, 5)
.background(self.activeTheme.backgroundColour)
.ignoresSafeArea()
}
}
24 changes: 24 additions & 0 deletions auto-clicker/Views/Main/Components/MouseMoveSelector.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// MouseMoveSelector.swift
// auto-clicker
//
// Created by Tomasz Pędraszewski on 04/07/2023.
//

import SwiftUI

struct MouseMoveSelector: View {
@State private var showingMouseMoveModal = false

@Binding var selectedMouseMove: MouseMove

var body: some View {
Button(self.selectedMouseMove.localised) {
self.showingMouseMoveModal = true
}
.buttonStyle(UnderlinedButtonStyle())
.sheet(isPresented: self.$showingMouseMoveModal, content: {
MouseMoveModal(selected: self.$selectedMouseMove)
})
}
}
17 changes: 17 additions & 0 deletions auto-clicker/Views/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,23 @@ struct MainView: View {

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")
}

ActionStageLine {
Text("main_window_stop_mousemove", comment: "Main window 'Stop mouse move'")

MouseMoveSelector(selectedMouseMove: self.$formState.stopOnMouseMove)
.disabled(self.hasStarted)

Text("main_window_stop_mousemove_end", comment: "Main window 'Stop mouse move end'")

DynamicWidthNumberField(text: "",
min: MIN_MOUSE_THRESHOLD,
max: MAX_MOUSE_THRESHOLD,
number: self.$formState.mouseDeltaThreshold)
.disabled(self.hasStarted)

Text("main_window_stop_mousemove_pixel", comment: "Main window 'Stop mouse move pixel'")
}
}
.padding(.top, 20)
.padding(.leading, 20)
Expand Down