diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizer.swift index ee0c0c0..b2b331f 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizer.swift @@ -18,5 +18,5 @@ protocol PointerVisualizer: AnyObject { var isPresentationActive: Bool { get set } /// Handles a captured mouse event and updates the visualizer if needed. - func noteMouseEvent(_ mouseEvent: MouseEvent) + func display(_ mouseEvent: MouseEvent) } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizersManager.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizersManager.swift index f8b2b16..70ca589 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizersManager.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/Core/PointerVisualizersManager.swift @@ -29,10 +29,10 @@ final class PointerVisualizersManager { icon = PointerIconVisualizer(settings: pointerIconSettings) } - func noteMouseEvent(_ mouseEvent: MouseEvent) { + func display(_ mouseEvent: MouseEvent) { guard self.isPresentationActive else { return } for visualizer in all where visualizer.isEnabled { - visualizer.noteMouseEvent(mouseEvent) + visualizer.display(mouseEvent) } } } diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizer.swift index da1d3d5..0763403 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizer.swift @@ -82,7 +82,7 @@ extension PointerIconVisualizer { // MARK: - PointerVisualizer extension PointerIconVisualizer: PointerVisualizer { - func noteMouseEvent(_ mouseEvent: MouseEvent) { + func display(_ mouseEvent: MouseEvent) { guard self.isEnabled else { return } self.pointerContentView.handle(mouseEvent: mouseEvent) self.syncPresentation() diff --git a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizer.swift b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizer.swift index 506a7b8..ac63428 100644 --- a/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizer.swift +++ b/Apps/Keyty/Sources/Keyty/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizer.swift @@ -48,7 +48,7 @@ public final class PointerRingVisualizer { // MARK: - PointerVisualizer extension PointerRingVisualizer: PointerVisualizer { - public func noteMouseEvent(_ mouseEvent: MouseEvent) { + public func display(_ mouseEvent: MouseEvent) { guard self.isEnabled else { return } if mouseEvent.type == .scrollWheel { return } self.visualizerWindow?.update(with: mouseEvent) diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/CaptureController/CaptureController.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/CaptureController/CaptureController.swift index 183c3cc..08c5387 100644 --- a/Apps/Keyty/Sources/Keyty/Platform/Capture/CaptureController/CaptureController.swift +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/CaptureController/CaptureController.swift @@ -32,7 +32,9 @@ final class CaptureController { self.pointerVisualizersManager = pointerVisualizersManager self.keyboardVisualizer = keyboardVisualizer self.permissionsService = permissionsService - self.eventTap.delegate = self + self.eventTap.onOutput = { [weak self] output in + self?.handle(output) + } self.eventProcessor.onItemProduced = { [keyboardVisualizer] item in keyboardVisualizer.display(item) } @@ -166,9 +168,35 @@ private extension CaptureController { } } -// MARK: - EventTapDelegate -extension CaptureController: EventTapDelegate { - func eventTap(_ tap: EventTap, didChangeState state: EventTap.State) { +// MARK: - Event Tap Output +private extension CaptureController { + func handle(_ output: EventTap.Output) { + switch output { + case .stateChanged(let state): + self.handleTapStateChange(state) + + case .keystroke(let keystroke): + guard self.isCapturing else { return } + self.eventProcessor.processKeystroke(keystroke) + + case .modifierFlags(let flags): + guard self.isCapturing else { return } + self.eventProcessor.processFlagsChanged(flags) + + case .mediaKey(let mediaKey): + guard self.isCapturing, mediaKey.isRecognized else { return } + self.eventProcessor.processMediaKey(mediaKey) + + case .mouse(let mouseEvent): + guard self.isCapturing else { return } + Task { @MainActor [pointerVisualizersManager = self.pointerVisualizersManager] in + pointerVisualizersManager.display(mouseEvent) + } + self.eventProcessor.processMouseEvent(mouseEvent) + } + } + + func handleTapStateChange(_ state: EventTap.State) { self.updateTapState(from: state) switch state { @@ -182,27 +210,4 @@ extension CaptureController: EventTapDelegate { self.handleTapFailure() } } - - func eventTap(_ tap: EventTap, noteKeystroke keystroke: StandardKeyEvent) { - guard self.isCapturing else { return } - self.eventProcessor.processKeystroke(keystroke) - } - - func eventTap(_ tap: EventTap, noteFlagsChanged flags: NSEvent.ModifierFlags) { - guard self.isCapturing else { return } - self.eventProcessor.processFlagsChanged(flags) - } - - func eventTap(_ tap: EventTap, noteMediaKey mediaKey: MediaKeyEvent) { - guard self.isCapturing, mediaKey.isRecognized else { return } - self.eventProcessor.processMediaKey(mediaKey) - } - - func eventTap(_ tap: EventTap, noteMouseEvent mouseEvent: MouseEvent) { - guard self.isCapturing else { return } - Task { @MainActor [pointerVisualizersManager = self.pointerVisualizersManager] in - pointerVisualizersManager.noteMouseEvent(mouseEvent) - } - self.eventProcessor.processMouseEvent(mouseEvent) - } } diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Error.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Error.swift index 87df9f7..5f30e6a 100644 --- a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Error.swift +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Error.swift @@ -10,16 +10,11 @@ import Cocoa extension EventTap { enum Error: LocalizedError, Equatable { - case keyTapCreationFailed - case mouseAndFlagsTapCreationFailed + /// A tap could not be created. + case creationFailed(Kind) var errorDescription: String? { - switch self { - case .keyTapCreationFailed: - return L10n.EventTap.keyTapCreationFailed - case .mouseAndFlagsTapCreationFailed: - return L10n.EventTap.mouseAndFlagsTapCreationFailed - } + L10n.EventTap.creationFailed } } } diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+InstalledTap.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+InstalledTap.swift new file mode 100644 index 0000000..2f1d7b3 --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+InstalledTap.swift @@ -0,0 +1,17 @@ +// +// EventTap+InstalledTap.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Cocoa + +extension EventTap { + /// A mach port and its run loop source, kept together so they are torn down together. + struct InstalledTap { + let machPort: CFMachPort + let runLoopSource: CFRunLoopSource + } +} diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Kind.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Kind.swift new file mode 100644 index 0000000..f332eab --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Kind.swift @@ -0,0 +1,34 @@ +// +// EventTap+Kind.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Cocoa + +extension EventTap { + enum Kind: CaseIterable { + case key + case mouseAndFlags + } +} + +extension EventTap.Kind { + var eventsOfInterest: CGEventMask { + switch self { + case .key: + return Self.mask(.keyDown, .keyUp, .systemDefined) + case .mouseAndFlags: + return Self.mask(.leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp, + .leftMouseDragged, .rightMouseDragged, + .otherMouseDown, .otherMouseUp, .otherMouseDragged, + .scrollWheel, .flagsChanged) + } + } + + private static func mask(_ types: CGEventType...) -> CGEventMask { + types.reduce(0) { $0 | CGEventMask(1 << $1.rawValue) } + } +} diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Output.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Output.swift new file mode 100644 index 0000000..11678ec --- /dev/null +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+Output.swift @@ -0,0 +1,23 @@ +// +// EventTap+Output.swift +// Keyty +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension EventTap { + /// Everything an installed tap reports to its owner. + /// + /// Captured input and lifecycle changes share one channel so that a consumer + /// handles them in a single ordered switch rather than across several callbacks. + enum Output { + case keystroke(StandardKeyEvent) + case mouse(MouseEvent) + case mediaKey(MediaKeyEvent) + case modifierFlags(NSEvent.ModifierFlags) + case stateChanged(EventTap.State) + } +} diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+State.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+State.swift index 1e6ee49..6da41c8 100644 --- a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+State.swift +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+State.swift @@ -8,9 +8,16 @@ extension EventTap { enum State: Equatable { + /// No taps are installed, either before the first `install()` or after `remove()`. case idle + + /// Both taps are installed and the system is delivering events. case installed + + /// Still installed, but the system turned a tap off; it is re-enabled automatically. case temporarilyDisabled(EventTap.DisableReason) + + /// A tap could not be created, so nothing is installed. case failed(EventTap.Error) } } diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+TapKind.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+TapKind.swift deleted file mode 100644 index b4c98f2..0000000 --- a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap+TapKind.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// EventTap+TapKind.swift -// Keyty -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -extension EventTap { - enum TapKind { - case key - case mouseAndFlags - } -} diff --git a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap.swift b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap.swift index a38cbe5..3eacd97 100644 --- a/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap.swift +++ b/Apps/Keyty/Sources/Keyty/Platform/Capture/EventTap/EventTap.swift @@ -9,19 +9,15 @@ import Cocoa import IOKit.hidsystem -protocol EventTapDelegate: AnyObject { - func eventTap(_ tap: EventTap, noteKeystroke keystroke: StandardKeyEvent) - func eventTap(_ tap: EventTap, noteMouseEvent mouseEvent: MouseEvent) - func eventTap(_ tap: EventTap, noteFlagsChanged flags: NSEvent.ModifierFlags) - func eventTap(_ tap: EventTap, noteMediaKey mediaKey: MediaKeyEvent) - func eventTap(_ tap: EventTap, didChangeState state: EventTap.State) -} - final class EventTap { + /// Receives every captured event and lifecycle change. Called on the run loop + /// the tap was installed on, never on a background queue. + var onOutput: ((Output) -> Void)? + private(set) var state: EventTap.State = .idle { didSet { guard oldValue != self.state else { return } - self.delegate?.eventTap(self, didChangeState: self.state) + self.onOutput?(.stateChanged(self.state)) } } @@ -32,92 +28,117 @@ final class EventTap { return false } - private var keyEventTap: CFMachPort? - private var mouseAndFlagsEventTap: CFMachPort? - - private var keyEventTapSource: CFRunLoopSource? - private var mouseAndFlagsEventTapSource: CFRunLoopSource? - - weak var delegate: EventTapDelegate? + fileprivate var installedTaps: [Kind: InstalledTap] = [:] deinit { if self.isInstalled { self.remove() } } +} - // MARK: - Public - +// MARK: - Public API +extension EventTap { func install() throws(EventTap.Error) { guard !self.isInstalled else { return } - // We have to try to tap the keydown event independently because CGEventTapCreate will - // succeed if it can install the event tap for the flags changed event, which apparently - // doesn't require universal access to be enabled. Thus, the call would succeed but - // Keyty would be, um, useless. - self.keyEventTap = CGEvent.tapCreate( - tap: .cgSessionEventTap, - place: .headInsertEventTap, - options: .listenOnly, - eventsOfInterest: cgEventMask(.keyDown, .keyUp, .systemDefined), - callback: keyTapCallback, - userInfo: Unmanaged.passUnretained(self).toOpaque() - ) - guard self.keyEventTap != nil else { - self.state = .failed(.keyTapCreationFailed) - throw EventTap.Error.keyTapCreationFailed + for kind in Kind.allCases { + guard let installedTap = self.makeTap(kind) else { + self.resetInstalledResources() + self.state = .failed(.creationFailed(kind)) + throw .creationFailed(kind) + } + self.installedTaps[kind] = installedTap } - self.mouseAndFlagsEventTap = CGEvent.tapCreate( + self.state = .installed + } + + func remove() { + guard self.state != .idle else { return } + self.resetInstalledResources() + self.state = .idle + } +} + +// MARK: - Tap Resources +private extension EventTap { + func makeTap(_ kind: Kind) -> InstalledTap? { + guard let machPort = CGEvent.tapCreate( tap: .cgSessionEventTap, place: .headInsertEventTap, options: .listenOnly, - eventsOfInterest: cgEventMask(.leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp, - .leftMouseDragged, .rightMouseDragged, - .flagsChanged, - .otherMouseDown, .otherMouseUp, .otherMouseDragged, - .scrollWheel), - callback: mouseFlagsTapCallback, + eventsOfInterest: kind.eventsOfInterest, + callback: tapCallback(for: kind), userInfo: Unmanaged.passUnretained(self).toOpaque() - ) - guard self.mouseAndFlagsEventTap != nil else { - self.resetInstalledResources() - self.state = .failed(.mouseAndFlagsTapCreationFailed) - throw EventTap.Error.mouseAndFlagsTapCreationFailed + ) else { + return nil } - self.keyEventTapSource = CFMachPortCreateRunLoopSource(nil, self.keyEventTap, 0) - self.mouseAndFlagsEventTapSource = CFMachPortCreateRunLoopSource(nil, self.mouseAndFlagsEventTap, 0) + guard let runLoopSource = CFMachPortCreateRunLoopSource(nil, machPort, 0) else { + CFMachPortInvalidate(machPort) + return nil + } - let runLoop = CFRunLoopGetCurrent() - CFRunLoopAddSource(runLoop, self.keyEventTapSource, .commonModes) - CFRunLoopAddSource(runLoop, self.mouseAndFlagsEventTapSource, .commonModes) + CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) + return InstalledTap(machPort: machPort, runLoopSource: runLoopSource) + } - self.state = .installed + func resetInstalledResources() { + for installedTap in self.installedTaps.values { + CFRunLoopSourceInvalidate(installedTap.runLoopSource) + } + self.installedTaps.removeAll() } - func remove() { - guard self.state != .idle else { return } - self.resetInstalledResources() - self.state = .idle + func reenableTap(_ kind: Kind) { + guard let installedTap = self.installedTaps[kind] else { return } + CGEvent.tapEnable(tap: installedTap.machPort, enable: true) + self.state = .installed } +} - // MARK: - Fileprivate (called from C callbacks) +// MARK: - Event Routing +extension EventTap { + /// Single entry point for both taps. `kind` matters only for disable notifications, + /// which report the same event type whichever tap the system turned off. + fileprivate func handleTapCallback(type: CGEventType, event: CGEvent, kind: Kind) { + switch type { + case .tapDisabledByTimeout, .tapDisabledByUserInput: + self.handleTapDisabled(type, kind: kind) + case .keyDown, .keyUp: + self.handleKeyEvent(event) + case .systemDefined: + self.handleSystemDefined(event) + case .flagsChanged: + self.handleFlagsChanged(event) + case .leftMouseDown, .leftMouseUp, .rightMouseDown, .rightMouseUp, + .leftMouseDragged, .rightMouseDragged, + .otherMouseDown, .otherMouseUp, .otherMouseDragged, + .scrollWheel: + self.handleMouseEvent(event) + default: + break + } + } +} - fileprivate func handleKeyEvent(_ cgEvent: CGEvent) { +// MARK: - Event Handling +private extension EventTap { + func handleKeyEvent(_ cgEvent: CGEvent) { guard let nsEvent = NSEvent(cgEvent: cgEvent) else { return } - self.delegate?.eventTap(self, noteKeystroke: StandardKeyEvent(nsEvent: nsEvent)) + self.onOutput?(.keystroke(StandardKeyEvent(nsEvent: nsEvent))) } - fileprivate func handleFlagsChanged(_ cgEvent: CGEvent) { + func handleFlagsChanged(_ cgEvent: CGEvent) { let flags = NSEvent.ModifierFlags(cgEventFlags: cgEvent.flags) - self.delegate?.eventTap(self, noteFlagsChanged: flags) + self.onOutput?(.modifierFlags(flags)) } - fileprivate func handleMouseEvent(_ cgEvent: CGEvent) { + func handleMouseEvent(_ cgEvent: CGEvent) { guard let nsEvent = NSEvent(cgEvent: cgEvent) else { return } - self.delegate?.eventTap(self, noteMouseEvent: MouseEvent(nsEvent: nsEvent, cgEvent: cgEvent)) + self.onOutput?(.mouse(MouseEvent(nsEvent: nsEvent, cgEvent: cgEvent))) } - fileprivate func handleSystemDefined(_ cgEvent: CGEvent) { + func handleSystemDefined(_ cgEvent: CGEvent) { // Media keys arrive as system-defined events with the aux-control-buttons subtype. // Other system-defined subtypes (screen changes, etc.) are ignored, // and reading `data1` is only safe once the subtype is confirmed. @@ -128,11 +149,11 @@ final class EventTap { else { return } - - self.delegate?.eventTap(self, noteMediaKey: MediaKeyEvent(nsEvent: nsEvent)) + + self.onOutput?(.mediaKey(MediaKeyEvent(nsEvent: nsEvent))) } - fileprivate func handleTapDisabled(_ type: CGEventType, kind: EventTap.TapKind) { + func handleTapDisabled(_ type: CGEventType, kind: Kind) { let reason: EventTap.DisableReason switch type { case .tapDisabledByTimeout: @@ -145,44 +166,20 @@ final class EventTap { self.state = .temporarilyDisabled(reason) self.reenableTap(kind) } - - private func reenableTap(_ kind: EventTap.TapKind) { - let machPort: CFMachPort? - switch kind { - case .key: - machPort = self.keyEventTap - case .mouseAndFlags: - machPort = self.mouseAndFlagsEventTap - } - - guard let machPort else { return } - CGEvent.tapEnable(tap: machPort, enable: true) - self.state = .installed - } - - private func resetInstalledResources() { - if let keyEventTapSource = self.keyEventTapSource { - CFRunLoopSourceInvalidate(keyEventTapSource) - } - if let mouseAndFlagsEventTapSource = self.mouseAndFlagsEventTapSource { - CFRunLoopSourceInvalidate(mouseAndFlagsEventTapSource) - } - - self.keyEventTapSource = nil - self.mouseAndFlagsEventTapSource = nil - self.keyEventTap = nil - self.mouseAndFlagsEventTap = nil - } -} - -// MARK: - CGEventMask helper - -private func cgEventMask(_ types: CGEventType...) -> CGEventMask { - types.reduce(0) { $0 | CGEventMask(1 << $1.rawValue) } } // MARK: - C event tap callbacks -// Must be file-scope functions (no captures) to satisfy @convention(c) requirement. +// Must be file-scope functions (no captures) to satisfy the @convention(c) requirement, +// so each tap gets a trampoline that supplies its own `Kind`. + +private func tapCallback(for kind: EventTap.Kind) -> CGEventTapCallBack { + switch kind { + case .key: + return keyTapCallback + case .mouseAndFlags: + return mouseFlagsTapCallback + } +} private func keyTapCallback( proxy: CGEventTapProxy, @@ -190,17 +187,7 @@ private func keyTapCallback( event: CGEvent, refcon: UnsafeMutableRawPointer? ) -> Unmanaged? { - if let refcon { - let tap = Unmanaged.fromOpaque(refcon).takeUnretainedValue() - if type == .tapDisabledByTimeout || type == .tapDisabledByUserInput { - tap.handleTapDisabled(type, kind: .key) - } else if [.keyDown, .keyUp].contains(type) { - tap.handleKeyEvent(event) - } else if type == .systemDefined { - tap.handleSystemDefined(event) - } - } - return .passUnretained(event) + dispatchTapCallback(type: type, event: event, refcon: refcon, kind: .key) } private func mouseFlagsTapCallback( @@ -208,22 +195,19 @@ private func mouseFlagsTapCallback( type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer? +) -> Unmanaged? { + dispatchTapCallback(type: type, event: event, refcon: refcon, kind: .mouseAndFlags) +} + +private func dispatchTapCallback( + type: CGEventType, + event: CGEvent, + refcon: UnsafeMutableRawPointer?, + kind: EventTap.Kind ) -> Unmanaged? { if let refcon { let tap = Unmanaged.fromOpaque(refcon).takeUnretainedValue() - switch type { - case .tapDisabledByTimeout, .tapDisabledByUserInput: - tap.handleTapDisabled(type, kind: .mouseAndFlags) - case .leftMouseDown, .rightMouseDown, .leftMouseUp, .rightMouseUp, - .leftMouseDragged, .rightMouseDragged, - .otherMouseDown, .otherMouseUp, .otherMouseDragged, - .scrollWheel: - tap.handleMouseEvent(event) - case .flagsChanged: - tap.handleFlagsChanged(event) - default: - break - } + tap.handleTapCallback(type: type, event: event, kind: kind) } return .passUnretained(event) } diff --git a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings index 08c02a4..3765583 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/de.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Abbrechen"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Die Tastaturerfassung konnte nicht gestartet werden. Die Berechtigung für Bedienungshilfen ist erforderlich."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Die Erfassung von Maus und Sondertasten konnte nicht gestartet werden."; +"event_tap.creation_failed" = "Die Eingabeerfassung konnte nicht gestartet werden. Die Berechtigung für Bedienungshilfen ist erforderlich."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings index 8a83ba6..cf353cd 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/en.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Cancel"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Could not create key event tap. Accessibility permission is required."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Could not create mouse and modifiers event tap."; +"event_tap.creation_failed" = "Could not create event tap. Accessibility permission is required."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings index d2caf16..a3788cb 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/es.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Cancelar"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "No se ha podido iniciar la captura del teclado. Se necesita el permiso de Accesibilidad."; -"event_tap.mouse_and_flags_tap_creation_failed" = "No se ha podido iniciar la captura del ratón y las teclas modificadoras."; +"event_tap.creation_failed" = "No se ha podido iniciar la captura de entradas. Se necesita el permiso de Accesibilidad."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings index b540471..851ec7d 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/fr.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Annuler"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Impossible de démarrer la détection du clavier. L’autorisation d’accessibilité est requise."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Impossible de démarrer la détection de la souris et des touches de modification."; +"event_tap.creation_failed" = "Impossible de démarrer la détection des entrées. L’autorisation d’accessibilité est requise."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings index 38933c2..7f89e32 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/ja.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "キャンセル"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "キーボード入力の検出を開始できませんでした。アクセシビリティへのアクセス権が必要です。"; -"event_tap.mouse_and_flags_tap_creation_failed" = "マウスと修飾キーの入力検出を開始できませんでした。"; +"event_tap.creation_failed" = "入力の検出を開始できませんでした。アクセシビリティへのアクセス権が必要です。"; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings index d6c8863..7cc6ae8 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/nl.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Annuleren"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Kon geen toetsgebeurtenis-tap maken. Toegankelijkheidstoestemming is vereist."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Kon geen muis- en modificatietoetsen-tap maken."; +"event_tap.creation_failed" = "Kon geen invoergebeurtenis-tap maken. Toegankelijkheidstoestemming is vereist."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings index a947d98..aede4fb 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/pl.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Anuluj"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Nie udało się utworzyć przechwytywania zdarzeń klawiatury. Wymagane jest uprawnienie Dostępność."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Nie udało się utworzyć przechwytywania zdarzeń myszy i modyfikatorów."; +"event_tap.creation_failed" = "Nie udało się utworzyć przechwytywania zdarzeń. Wymagane jest uprawnienie Dostępność."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings index 897e2ba..aacf857 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/uk.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "Скасувати"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "Не вдалося почати відстеження клавіатури. Потрібен дозвіл «Доступність»."; -"event_tap.mouse_and_flags_tap_creation_failed" = "Не вдалося почати відстеження миші та клавіш-модифікаторів."; +"event_tap.creation_failed" = "Не вдалося почати відстеження введення. Потрібен дозвіл «Доступність»."; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings index e38bb54..d190d20 100644 --- a/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings +++ b/Apps/Keyty/Sources/Keyty/Resources/zh-Hans.lproj/Localizable.strings @@ -73,8 +73,7 @@ "general.reset_all_settings_cancel_button" = "取消"; /* Event capture */ -"event_tap.key_tap_creation_failed" = "无法开始监测键盘输入。需要“辅助功能”权限。"; -"event_tap.mouse_and_flags_tap_creation_failed" = "无法开始监测鼠标和修饰键输入。"; +"event_tap.creation_failed" = "无法开始监测输入。需要“辅助功能”权限。"; /* Info settings pane */ "info.github_label" = "GitHub"; diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizerTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizerTests.swift index 11e9973..b7caef6 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizerTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerRing/PointerRingVisualizerTests.swift @@ -40,7 +40,7 @@ final class PointerRingVisualizerTests: XCTestCase { let cgEvent = CGEvent(scrollWheelEvent2Source: nil, units: .pixel, wheelCount: 1, wheel1: 10, wheel2: 0, wheel3: 0)! let nsEvent = NSEvent(cgEvent: cgEvent)! - XCTAssertNoThrow(self.visualizer.noteMouseEvent(MouseEvent(nsEvent: nsEvent))) + XCTAssertNoThrow(self.visualizer.display(MouseEvent(nsEvent: nsEvent))) } func testRegistersDefaultColor() {