Skip to content
Merged
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
5 changes: 4 additions & 1 deletion Sources/Switch2KitApp/Output/MouseController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ final class MouseController: @unchecked Sendable {
defer { units[serial] = unit }
let dx = Self.wrapDiff(state.mouseX, unit.lastX), dy = Self.wrapDiff(state.mouseY, unit.lastY)
unit.lastX = state.mouseX; unit.lastY = state.mouseY
guard unit.primed else { unit.primed = true; return false }
guard state.liftDistance != 0 && state.liftDistance < 1000 && state.surfaceQuality < 4000 else {
// Lost tracking breaks the counter baseline as well as fractional motion.
// Reacquisition may jump/wrap counters; its first usable report is not a delta.
unit.primed = false
unit.residualX = 0; unit.residualY = 0
return false
}
guard unit.primed else { unit.primed = true; return false }
let scale = 0.35 * configuration.mouseSensitivity
let x = Double(dx) * scale + unit.residualX, y = Double(dy) * scale + unit.residualY
let moveX = x.rounded(.towardZero), moveY = y.rounded(.towardZero)
Expand Down
42 changes: 42 additions & 0 deletions tests/mouse/MouseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,48 @@ final class CGEvent {
state.mouseX -= 1; precondition(!input())
}
precondition(!mouse.handle(serial: "pro", model: .proController2, state: state, configuration: config))
surfaceReacquisition()
print("PASS accepted pointer motion, noise, lift/quality, wrap, disabled/denied, edge clamping and event failure")
}
static func surfaceReacquisition() {
for model in [Switch2.Model.joyCon2Left, .joyCon2Right] {
let mouse = MouseController()
mouse.updateContext(permission: true, screens: [])
var config = ControllerConfiguration(); config.mouseEnabled = true
var state = ControllerState()
func input() -> Bool {
mouse.handle(serial: "surface-test", model: model, state: state, configuration: config)
}
CGEvent.cursor = CGPoint(x: 100, y: 100)
state.mouseX = 1000; state.mouseY = 2000
// An initial no-surface report must not prime a usable counter baseline.
precondition(!input())
state.liftDistance = 10; state.mouseX = 40000; state.mouseY = 100
precondition(!input() && CGEvent.cursor == CGPoint(x: 100, y: 100),
"First contact after lifted reports only establishes the optical baseline")
state.mouseX += 10
precondition(input() && CGEvent.cursor.x == 103)
for invalid in 0..<3 {
let before = CGEvent.cursor
state.liftDistance = invalid == 0 ? 0 : invalid == 1 ? 1000 : 10
state.surfaceQuality = invalid == 2 ? 4000 : 0
state.mouseX &+= 30000; state.mouseY &+= 30000
precondition(!input() && CGEvent.cursor == before)
// Counters may jump, including wrapping, while tracking was unusable.
state.liftDistance = 10; state.surfaceQuality = 0
state.mouseX = UInt16.max; state.mouseY = 500
precondition(!input() && CGEvent.cursor == before,
"Reacquisition cannot turn an untracked interval into pointer movement")
state.mouseX = 3
precondition(input() && CGEvent.cursor.x == before.x + 1,
"A following tracked report retains modulo counter movement")
// No fractional residue from before loss or the discarded interval survives.
state.mouseX += 1
precondition(!input())
}
mouse.reset()
}
print("PASS both Joy-Con halves re-prime after lift, out-of-range surface and low quality")
}

}
Loading