summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-07 14:04:28 -0300
committerGabriel Schneider <[email protected]>2026-09-07 14:10:43 -0300
commitbba412a3b2c83b4c6935baeaac979ead46c0a5dc (patch)
tree00d3bede8004ff0daa7025004d7a5c4bb926ff6d /src
parent60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (diff)
parent7545cdbf47559d4e996c7d3fc9ae8968a3342292 (diff)
downloadpardes-bba412a3b2c83b4c6935baeaac979ead46c0a5dc.tar.gz
pardes-bba412a3b2c83b4c6935baeaac979ead46c0a5dc.zip
macos: merge local trackpad gestures with current upstream
Diffstat (limited to 'src')
-rw-r--r--src/9p_quic.zig4
-rw-r--r--src/CHANGELOG.md18
-rw-r--r--src/macos/Sources/PardesView.swift52
3 files changed, 49 insertions, 25 deletions
diff --git a/src/9p_quic.zig b/src/9p_quic.zig
index 7898adb0..3f12d8bc 100644
--- a/src/9p_quic.zig
+++ b/src/9p_quic.zig
@@ -441,8 +441,10 @@ test "QUIC backpressure retries a moved prefix while new replies are appended" {
try std.testing.expectError(error.InvalidWrite, pair.client.write(bytes[0..1]));
var buffer: [8192]u8 = undefined;
var received: usize = 0;
+ // A pending SSL write may already have delivered a prefix before it
+ // reports completion. Leave that prefix for the retry check below.
while (received < total) {
- if (try pair.server.?.read(&buffer)) |n| {
+ if (try pair.server.?.read(buffer[0..@min(buffer.len, total - received)])) |n| {
try std.testing.expect(n > 0);
try std.testing.expect(std.mem.allEqual(u8, buffer[0..n], 0x5a));
received += n;
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 25067a17..db75ca6b 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -75,6 +75,24 @@
is one is still `BadArgs`: that is a permission problem, not a typo, and the
two want different answers.
+- The macOS trackpad's two verbs trade places: a two-finger click is Exec and
+ a deep press is Look. Exec is what a hand spends a session doing, so it takes
+ the gesture that costs nothing, and the verb that goes somewhere is worth one
+ deliberate push past the click. Three fingers stay Exec, and that is the
+ hardware rather than a gesture left unused: macOS's secondary click is "click
+ or tap with two or more fingers", so a three-finger click arrives as
+ `rightMouseDown` exactly like a two-finger one, and the resting count is the
+ only thing that keeps either of them off Look. `pressureChange` still
+ releases the click in flight before the new button goes out, which now steps
+ around acme's 1-3 chord (Paste) where it used to step around 1-2 (Cut). Look
+ by finger count is gone, so a Mac with "Force Click and haptic feedback" off
+ — or a trackpad with no force sensor — reaches it through a real mouse's
+ right button, the `Look` builtin, or Enter on the word. `trackpad.snap`
+ asserts all of it without a trackpad, because `NSTouch` and the pressure
+ stages have no public constructors and `Trackpad` is therefore pure policy a
+ script can name finger counts to; the swap left every golden screen
+ byte-identical under the other gesture, which is what shows the two paths
+ were exchanged and nothing else moved.
- A filtered terminal costs what an unfiltered one does. `Filter`'s second
stage asked `RGB.contrast` for every cell it painted, and that call ends in
`std.math.pow` six times over — a libm round trip per cell, per frame, to
diff --git a/src/macos/Sources/PardesView.swift b/src/macos/Sources/PardesView.swift
index 3f5f1346..f9afa7b1 100644
--- a/src/macos/Sources/PardesView.swift
+++ b/src/macos/Sources/PardesView.swift
@@ -45,25 +45,27 @@ protocol PardesViewDelegate: AnyObject {
/// constructed, so this is the part of the gesture a test can reach.
///
/// acme's three buttons are the whole vocabulary — 1 selects, 2 executes,
-/// 3 looks — and a trackpad has one surface. Two fingers is the gesture macOS
-/// itself spells "secondary", so it is Look; three is the one left over, so it
-/// is Exec, the heavier verb, which is also what a deep press means.
+/// 3 looks — and a trackpad has one surface. More than one finger is Exec,
+/// because running something is what a hand does over and over and it gets
+/// the gesture that costs nothing; Look is the deep press, one deliberate
+/// push past the click for the verb that goes somewhere.
///
-/// The finger count has to win over the stream, and that is not a preference.
-/// macOS's secondary click is "click or tap with TWO OR MORE fingers": with it
-/// on, a three-finger click is delivered as rightMouseDown exactly like a
-/// two-finger one, and a view that trusts the stream cannot tell them apart —
-/// three fingers silently did Look. Measured on real hardware, which is the
-/// only way this was ever going to be found: `rightMouseDown: resting=3`.
+/// Two and three fingers are the same verb here, and that is the hardware
+/// talking rather than a shrug. macOS's secondary click is "click or tap with
+/// TWO OR MORE fingers": with it on — the default — a three-finger click is
+/// delivered as rightMouseDown exactly like a two-finger one, and a view that
+/// trusts the stream calls both of them Look. Measured on real hardware, which
+/// is the only way this was ever going to be found:
+/// `rightMouseDown: resting=3`. So the count is what keeps a multi-finger
+/// click off Look, and the stream cannot be trusted to do it.
///
-/// So the stream is only the fallback, for when there are no fingers to count:
+/// The stream is only the fallback, for when there are no fingers to count:
/// a real mouse's right button is Look and its middle button is Exec, and both
/// arrive with an empty touch set.
enum Trackpad {
static func button(stream: pardes_mouse_button_e, fingers: Int) -> pardes_mouse_button_e {
switch fingers {
- case 2: return PARDES_MOUSE_RIGHT
- case 3...: return PARDES_MOUSE_MIDDLE
+ case 2...: return PARDES_MOUSE_MIDDLE
// One finger, or none to count: the stream is the answer. A trackpad
// single click comes in on the left stream and stays left; a real
// mouse's right and middle buttons keep their acme meanings.
@@ -72,9 +74,9 @@ enum Trackpad {
}
/// A force click is a deliberate second gesture on top of an ordinary one,
- /// so it gets the verb that does something rather than the one that
- /// navigates.
- static let forceClickButton: pardes_mouse_button_e = PARDES_MOUSE_MIDDLE
+ /// so it gets the verb that goes somewhere rather than the one that runs
+ /// something: press harder and you Look.
+ static let forceClickButton: pardes_mouse_button_e = PARDES_MOUSE_RIGHT
}
// Matches bg_default/fg_default in src/gui/gui.zig and DEFAULT_FG/DEFAULT_BG in
@@ -643,7 +645,7 @@ final class PardesView: NSView {
// hardware it does not always: AppKit routes NSTouch through the four
// touchesXxx callbacks, and the touch set hanging off a *mouse* event can
// come back empty depending on how the click was produced. Empty reads as
- // one finger, which is a two-finger Look silently degrading into a select
+ // one finger, which is a two-finger Exec silently degrading into a select
// — the exact failure this was supposed to avoid. So the count is
// maintained here and the mouse event's own set is preferred only when it
// has something in it.
@@ -674,7 +676,7 @@ final class PardesView: NSView {
registerForDraggedTypes([.fileURL])
// Indirect touches are the trackpad's. Without this the touch set is
// always empty and every click looks like one finger, which is exactly
- // the bug that would make two-finger Look silently never fire.
+ // the bug that would make two-finger Exec silently never fire.
allowedTouchTypes = [.indirect]
// Without a pressure configuration the deep-press stages are the
// system's business and stage 2 may never be delivered here.
@@ -1737,15 +1739,17 @@ final class PardesView: NSView {
/// flight. AppKit keeps sending stage-2 events while the finger stays down,
/// so only the transition counts.
///
- /// Whatever button is in flight is released before the middle one goes out:
- /// a middle press arriving while the core holds a left select-drag is
- /// acme's 1-2 chord, which is Cut. Releasing first costs a cursor move at
- /// the click point — which is what clicking there would have done anyway.
+ /// Whatever button is in flight is released before the look one goes out:
+ /// a right press arriving while the core holds a left select-drag is
+ /// acme's 1-3 chord, which is Paste. Releasing first costs a cursor move
+ /// at the click point — which is what clicking there would have done
+ /// anyway; a multi-finger press released this way fires its own Exec,
+ /// which is what the fingers already asked for.
///
/// Not gated on the press being a LEFT one, which is what stopped this
/// working: on a Force Touch trackpad the deep press is just as likely to
- /// have arrived on the right stream, and an in-flight Look upgraded by
- /// pressing harder is precisely the gesture. Already-Exec is the only case
+ /// have arrived on the right stream, and an in-flight Exec upgraded by
+ /// pressing harder is precisely the gesture. Already-Look is the only case
/// with nothing to do.
override func pressureChange(with event: NSEvent) {
trace("pressure: stage=\(event.stage) latched=\(String(describing: latchedButton?.rawValue))")
@@ -1829,7 +1833,7 @@ final class PardesView: NSView {
/// (secondary click, three-finger drag, force click, "look up"), none of
/// which this process can read, and every one of which turns a gesture into
/// a different NSEvent or into none at all. When someone reports that
- /// two-finger Look does nothing, this is the only thing that can answer
+ /// two-finger Exec does nothing, this is the only thing that can answer
/// whether AppKit saw two fingers, one, or no click at all.
private func trace(_ message: @autoclosure () -> String) {
guard PardesView.tracing else { return }