Skip to content

Commit 37067b4

Browse files
authored
Merge pull request #34 from NakaokaRei/feature/issue-14-double-triple-click
feat: Add doubleClick() and tripleClick() functions
2 parents e08f78f + d7ad6b2 commit 37067b4

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

Sample/Sample/Features/Mouse/MouseControlView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ struct MouseControlView: View {
3232
Button("Left Click") {
3333
viewModel.leftClick()
3434
}
35+
36+
Button("Double Click") {
37+
viewModel.doubleClick()
38+
}
39+
40+
Button("Triple Click") {
41+
viewModel.tripleClick()
42+
}
3543
}
3644
}
3745
}

Sample/Sample/Features/Mouse/MouseControlViewModel.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,12 @@ class MouseControlViewModel: ObservableObject {
2323
func leftClick() {
2424
SwiftAutoGUI.leftClick()
2525
}
26+
27+
func doubleClick() {
28+
SwiftAutoGUI.doubleClick()
29+
}
30+
31+
func tripleClick() {
32+
SwiftAutoGUI.tripleClick()
33+
}
2634
}

Sources/SwiftAutoGUI/SwiftAutoGUI.swift

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ import AppKit
4444
/// - ``locateCenterOnScreen(_:grayscale:confidence:region:)``
4545
/// - ``locateAllOnScreen(_:grayscale:confidence:region:)``
4646
public class SwiftAutoGUI {
47+
48+
/// Represents mouse buttons that can be clicked.
49+
public enum MouseButton {
50+
case left
51+
case right
52+
53+
/// Maps to the corresponding CGMouseButton value
54+
var cgMouseButton: CGMouseButton {
55+
switch self {
56+
case .left:
57+
return .left
58+
case .right:
59+
return .right
60+
}
61+
}
62+
}
4763

4864
// MARK: Key Event
4965

@@ -513,6 +529,174 @@ public class SwiftAutoGUI {
513529
)
514530
scrollEvent?.post(tap: .cghidEventTap)
515531
}
532+
533+
/// Performs a double-click with the specified mouse button at the current cursor position.
534+
///
535+
/// This method simulates a double-click operation, commonly used to open files,
536+
/// select words in text, or activate items.
537+
///
538+
/// - Parameter button: The mouse button to click (default: .left)
539+
///
540+
/// - Note: The timing between clicks is handled automatically to match system double-click speed.
541+
///
542+
/// ## Example
543+
///
544+
/// ```swift
545+
/// // Double-click at current position
546+
/// SwiftAutoGUI.doubleClick()
547+
///
548+
/// // Double-click with right button
549+
/// SwiftAutoGUI.doubleClick(button: .right)
550+
/// ```
551+
public static func doubleClick(button: MouseButton = .left) {
552+
var mouseLoc = NSEvent.mouseLocation
553+
mouseLoc.y = NSHeight(NSScreen.screens[0].frame) - mouseLoc.y
554+
doubleClick(at: mouseLoc, button: button)
555+
}
556+
557+
/// Performs a double-click with the specified mouse button at a given position.
558+
///
559+
/// This method simulates a double-click operation at the specified coordinates,
560+
/// commonly used to open files, select words in text, or activate items.
561+
///
562+
/// - Parameters:
563+
/// - at: The position to click in CGWindow coordinates (origin at top-left)
564+
/// - button: The mouse button to click (default: .left)
565+
///
566+
/// - Note: The timing between clicks is handled automatically to match system double-click speed.
567+
///
568+
/// ## Example
569+
///
570+
/// ```swift
571+
/// // Double-click at specific position
572+
/// SwiftAutoGUI.doubleClick(at: CGPoint(x: 100, y: 200))
573+
///
574+
/// // Double-click at button location
575+
/// let buttonPos = CGPoint(x: 150, y: 300)
576+
/// SwiftAutoGUI.doubleClick(at: buttonPos)
577+
///
578+
/// // Select a word in text editor
579+
/// SwiftAutoGUI.move(to: wordPosition)
580+
/// SwiftAutoGUI.doubleClick()
581+
/// ```
582+
public static func doubleClick(at position: CGPoint, button: MouseButton = .left) {
583+
let source = CGEventSource(stateID: .hidSystemState)
584+
585+
// Create mouse down and up events with click count set to 2
586+
let mouseDownType: CGEventType = button == .left ? .leftMouseDown : .rightMouseDown
587+
let mouseUpType: CGEventType = button == .left ? .leftMouseUp : .rightMouseUp
588+
589+
// First click
590+
let firstDown = CGEvent(mouseEventSource: source, mouseType: mouseDownType,
591+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
592+
firstDown?.post(tap: .cghidEventTap)
593+
594+
let firstUp = CGEvent(mouseEventSource: source, mouseType: mouseUpType,
595+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
596+
firstUp?.post(tap: .cghidEventTap)
597+
598+
// Second click with click count = 2
599+
let secondDown = CGEvent(mouseEventSource: source, mouseType: mouseDownType,
600+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
601+
secondDown?.setIntegerValueField(.mouseEventClickState, value: 2)
602+
secondDown?.post(tap: .cghidEventTap)
603+
604+
let secondUp = CGEvent(mouseEventSource: source, mouseType: mouseUpType,
605+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
606+
secondUp?.setIntegerValueField(.mouseEventClickState, value: 2)
607+
secondUp?.post(tap: .cghidEventTap)
608+
609+
Thread.sleep(forTimeInterval: 0.01)
610+
}
611+
612+
/// Performs a triple-click with the specified mouse button at the current cursor position.
613+
///
614+
/// This method simulates a triple-click operation, commonly used to select entire
615+
/// lines or paragraphs of text.
616+
///
617+
/// - Parameter button: The mouse button to click (default: .left)
618+
///
619+
/// - Note: The timing between clicks is handled automatically to match system settings.
620+
///
621+
/// ## Example
622+
///
623+
/// ```swift
624+
/// // Triple-click at current position
625+
/// SwiftAutoGUI.tripleClick()
626+
///
627+
/// // Triple-click with right button
628+
/// SwiftAutoGUI.tripleClick(button: .right)
629+
/// ```
630+
public static func tripleClick(button: MouseButton = .left) {
631+
var mouseLoc = NSEvent.mouseLocation
632+
mouseLoc.y = NSHeight(NSScreen.screens[0].frame) - mouseLoc.y
633+
tripleClick(at: mouseLoc, button: button)
634+
}
635+
636+
/// Performs a triple-click with the specified mouse button at a given position.
637+
///
638+
/// This method simulates a triple-click operation at the specified coordinates,
639+
/// commonly used to select entire lines or paragraphs of text.
640+
///
641+
/// - Parameters:
642+
/// - at: The position to click in CGWindow coordinates (origin at top-left)
643+
/// - button: The mouse button to click (default: .left)
644+
///
645+
/// - Note: The timing between clicks is handled automatically to match system settings.
646+
///
647+
/// ## Example
648+
///
649+
/// ```swift
650+
/// // Triple-click at specific position
651+
/// SwiftAutoGUI.tripleClick(at: CGPoint(x: 150, y: 300))
652+
///
653+
/// // Select entire paragraph
654+
/// let paragraphPos = CGPoint(x: 200, y: 400)
655+
/// SwiftAutoGUI.tripleClick(at: paragraphPos)
656+
///
657+
/// // Triple-click with right button
658+
/// SwiftAutoGUI.tripleClick(at: CGPoint(x: 100, y: 200), button: .right)
659+
/// ```
660+
public static func tripleClick(at position: CGPoint, button: MouseButton = .left) {
661+
let source = CGEventSource(stateID: .hidSystemState)
662+
663+
// Create mouse down and up events
664+
let mouseDownType: CGEventType = button == .left ? .leftMouseDown : .rightMouseDown
665+
let mouseUpType: CGEventType = button == .left ? .leftMouseUp : .rightMouseUp
666+
667+
// First click
668+
let firstDown = CGEvent(mouseEventSource: source, mouseType: mouseDownType,
669+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
670+
firstDown?.post(tap: .cghidEventTap)
671+
672+
let firstUp = CGEvent(mouseEventSource: source, mouseType: mouseUpType,
673+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
674+
firstUp?.post(tap: .cghidEventTap)
675+
676+
// Second click with click count = 2
677+
let secondDown = CGEvent(mouseEventSource: source, mouseType: mouseDownType,
678+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
679+
secondDown?.setIntegerValueField(.mouseEventClickState, value: 2)
680+
secondDown?.post(tap: .cghidEventTap)
681+
682+
let secondUp = CGEvent(mouseEventSource: source, mouseType: mouseUpType,
683+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
684+
secondUp?.setIntegerValueField(.mouseEventClickState, value: 2)
685+
secondUp?.post(tap: .cghidEventTap)
686+
687+
// Third click with click count = 3
688+
let thirdDown = CGEvent(mouseEventSource: source, mouseType: mouseDownType,
689+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
690+
thirdDown?.setIntegerValueField(.mouseEventClickState, value: 3)
691+
thirdDown?.post(tap: .cghidEventTap)
692+
693+
let thirdUp = CGEvent(mouseEventSource: source, mouseType: mouseUpType,
694+
mouseCursorPosition: position, mouseButton: button.cgMouseButton)
695+
thirdUp?.setIntegerValueField(.mouseEventClickState, value: 3)
696+
thirdUp?.post(tap: .cghidEventTap)
697+
698+
Thread.sleep(forTimeInterval: 0.01)
699+
}
516700

517701
private static func leftClickDown(position: CGPoint) {
518702
let source = CGEventSource(stateID: CGEventSourceStateID.hidSystemState)

0 commit comments

Comments
 (0)