Files
punktfunk/clients/apple/Tests/PunktfunkKitTests/TouchMouseTests.swift
T
enricobuehler d707ee4d4e
android / android (push) Has been cancelled
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
feat(apple,android): three-way touch input — trackpad cursor (default), direct pointer, real multi-touch passthrough
The two touch clients had exactly complementary gaps: iOS forwarded fingers
ONLY as raw wire touches (no way to drive the host cursor from the touch
screen), Android had the two mouse modes but no passthrough. Both now share
one three-way "Touch input" setting: Trackpad (default) / Direct pointer /
Touch passthrough.

iOS/iPadOS: Input/TouchMouse.swift ports the Android gesture engine 1:1
(same px-based acceleration curve; tap=click, two-finger tap=right-click,
two-finger drag=scroll, tap-then-drag=held drag, three-finger tap=stats
HUD via the shared hudEnabled default); direct-pointer mode maps through
the aspect-fit letterbox; the previous always-on behavior lives on as the
passthrough option. The mode latches per gesture (a Settings change never
splits one gesture across models), touchesCancelled releases held state
without synthesizing a click, and session stop flushes a mid-drag button.
Settings picker on iPhone + iPad next to the iPad-only pointer-capture
toggle. Deliberate default change: trackpad, not passthrough.

Android: new nativeSendTouch JNI shim → wire TouchDown/Move/Up (the host
already injects real touch on every backend — libei touchscreen, wlroots,
KWin fake-input, SendInput); streamTouchPassthrough forwards every finger
with stable ids and lifts still-held contacts on teardown; the trackpadMode
Boolean becomes the TouchMode enum (old pref migrated on load, never
rewritten) with a Settings dropdown.

Verified: macOS swift build + full suite (incl. new TouchMouseTests), iOS
Simulator Swift compile, cargo check/fmt/clippy on the native crate, Kotlin
app+kit compile + unit tests. On-glass feel of the iOS ballistics and
Android passthrough against a touch-aware app still pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 00:02:12 +02:00

43 lines
2.0 KiB
Swift

#if os(iOS)
import XCTest
@testable import PunktfunkKit
/// Pins the touch-mouse tuning contract (ported 1:1 from the Android client's TouchInput.kt
/// so the two touch clients feel identical) and the mode parsing. The gesture state machine
/// itself needs UITouch instances and is validated on-glass.
final class TouchMouseTests: XCTestCase {
func testModeParsingDefaultsToTrackpad() {
XCTAssertEqual(TouchInputMode(rawValue: "trackpad"), .trackpad)
XCTAssertEqual(TouchInputMode(rawValue: "pointer"), .pointer)
XCTAssertEqual(TouchInputMode(rawValue: "touch"), .touch)
// Unknown/unset values must fall back to trackpad never crash or go touch-silent.
XCTAssertNil(TouchInputMode(rawValue: "bogus"))
}
func testAccelerationCurve() {
// At or below the speed floor: no acceleration slow drags stay precise.
XCTAssertEqual(TouchMouse.Tuning.accel(forSpeed: 0), 1)
XCTAssertEqual(TouchMouse.Tuning.accel(forSpeed: TouchMouse.Tuning.accelSpeedFloor), 1)
// Above the floor the gain ramps...
let mid = TouchMouse.Tuning.accel(forSpeed: 1.0)
XCTAssertGreaterThan(mid, 1)
XCTAssertLessThan(mid, TouchMouse.Tuning.accelMax)
// ...and a flick is capped so it can't fling the cursor uncontrollably.
XCTAssertEqual(TouchMouse.Tuning.accel(forSpeed: 100), TouchMouse.Tuning.accelMax)
// Monotonic in between.
XCTAssertLessThanOrEqual(
TouchMouse.Tuning.accel(forSpeed: 0.5), TouchMouse.Tuning.accel(forSpeed: 1.5))
}
func testTuningRelations() {
// The tap-drag window must be long enough to hit but short enough not to turn every
// second tap into a drag.
XCTAssertGreaterThan(TouchMouse.Tuning.tapDragWindow, 0.1)
XCTAssertLessThan(TouchMouse.Tuning.tapDragWindow, 0.5)
// A wheel notch per ~10 pt of two-finger pan (the indirect-trackpad path's feel).
XCTAssertGreaterThan(TouchMouse.Tuning.scrollNotchPt, 0)
}
}
#endif