app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
v2.0.664 (insiders): extension lane: stuck mouse button released before the next mouse event
3 files changed
+29−17
rust/Cargo.lock+12−12@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-pup-bridge"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-browser", "pup-cdp",@@ -655,7 +655,7 @@ dependencies = [ [[package]] name = "pup-browser"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-core", "pup-host",@@ -667,7 +667,7 @@ dependencies = [ [[package]] name = "pup-cdp"-version = "2.0.663"+version = "2.0.664" dependencies = [ "serde_json", "thiserror",@@ -677,7 +677,7 @@ dependencies = [ [[package]] name = "pup-core"-version = "2.0.663"+version = "2.0.664" dependencies = [ "serde", "serde_json",@@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "pup-dashboard"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-cdp", "pup-core",@@ -700,7 +700,7 @@ dependencies = [ [[package]] name = "pup-host"-version = "2.0.663"+version = "2.0.664" dependencies = [ "serde", "serde_json",@@ -709,14 +709,14 @@ dependencies = [ [[package]] name = "pup-host-mac"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-host", ] [[package]] name = "pup-host-win"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-core", "pup-host",@@ -726,7 +726,7 @@ dependencies = [ [[package]] name = "pup-identity"-version = "2.0.663"+version = "2.0.664" dependencies = [ "fontdue", "image",@@ -736,7 +736,7 @@ dependencies = [ [[package]] name = "pup-session"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-core", "serde",@@ -746,7 +746,7 @@ dependencies = [ [[package]] name = "pup-vault"-version = "2.0.663"+version = "2.0.664" dependencies = [ "pup-core", "pup-host",@@ -757,7 +757,7 @@ dependencies = [ [[package]] name = "pup-verbs"-version = "2.0.663"+version = "2.0.664" dependencies = [ "base64", "png 0.17.16",
rust/Cargo.toml+1−1@@ -4,7 +4,7 @@ members = ["core", "host", "host-win", "host-mac", "cdp", "browser", "session", # One version for the whole bridge, in lockstep with src/BRIDGE_VERSION (tools/ship.sh bumps both). [workspace.package]-version = "2.0.663"+version = "2.0.664" edition = "2021" license = "MIT"
rust/verbs/src/input.rs+16−4@@ -382,14 +382,26 @@ pub fn input_dispatch(ctx: &Ctx, caller: &Caller, args: &Value) -> Value { let lane = match crate::transport::lane_for(ctx, &s) { Ok(l) => l, Err(e) => return e }; let send = |method: &str, p: Value| -> Result<(), Value> { crate::transport::cdp_raw(&lane, &t, method, p, 15).map(|_| ()) }; let btn = args.get("button").and_then(|v| v.as_str()).unwrap_or("left").to_string();+ let sid = t.cdp_session.clone();+ // 2.0.664: the same press bookkeeping as the CDP branch (the jump-list gate caught a press left down on this+ // lane that the next click did not release): release an orphaned button first, record a press, clear it on release.+ let clear_stuck = |ctx: &Ctx| {+ let held = ctx.pending_press.lock().unwrap().get(&sid).cloned();+ if let Some((hx, hy, hb)) = held {+ if send("Input.dispatchMouseEvent", json!({ "type": "mouseReleased", "x": hx, "y": hy, "button": hb, "clickCount": 1 })).is_ok() { press_up(ctx, &sid); pup_core::log(format!("[input] released a {hb} button an earlier click left down (extension lane)")); }+ }+ }; let r = match kind {- "click" => send("Input.dispatchMouseEvent", json!({ "type": "mouseMoved", "x": x, "y": y, "button": "none" }))- .and_then(|_| send("Input.dispatchMouseEvent", json!({ "type": "mousePressed", "x": x, "y": y, "button": btn, "clickCount": 1 })))- .and_then(|_| { std::thread::sleep(Duration::from_millis(40 + (x as u64 % 50))); send("Input.dispatchMouseEvent", json!({ "type": "mouseReleased", "x": x, "y": y, "button": btn, "clickCount": 1 })) }),+ "click" => { clear_stuck(ctx); send("Input.dispatchMouseEvent", json!({ "type": "mouseMoved", "x": x, "y": y, "button": "none" }))+ .and_then(|_| { let r = send("Input.dispatchMouseEvent", json!({ "type": "mousePressed", "x": x, "y": y, "button": btn, "clickCount": 1 })); if r.is_ok() { press_down(ctx, &sid, x, y, &btn); } r })+ .and_then(|_| { std::thread::sleep(Duration::from_millis(40 + (x as u64 % 50))); let r = send("Input.dispatchMouseEvent", json!({ "type": "mouseReleased", "x": x, "y": y, "button": btn, "clickCount": 1 })); if r.is_ok() { press_up(ctx, &sid); } r }) } "mouseMoved" | "mousePressed" | "mouseReleased" | "mouseWheel" => {+ if kind != "mousePressed" { clear_stuck(ctx); } let mut p = json!({ "type": kind, "x": x, "y": y, "button": if kind == "mouseMoved" { "none".to_string() } else { btn.clone() }, "clickCount": 1 }); if kind == "mouseWheel" { p["deltaX"] = json!(args.get("deltaX").and_then(|v| v.as_f64()).unwrap_or(0.0)); p["deltaY"] = json!(args.get("deltaY").and_then(|v| v.as_f64()).unwrap_or(0.0)); }- send("Input.dispatchMouseEvent", p)+ let r = send("Input.dispatchMouseEvent", p);+ if r.is_ok() { if kind == "mousePressed" { press_down(ctx, &sid, x, y, &btn); } else if kind == "mouseReleased" { press_up(ctx, &sid); } }+ r } "keyDown" | "keyUp" | "rawKeyDown" | "char" => send("Input.dispatchKeyEvent", json!({ "type": kind, "key": args.get("key"), "text": args.get("text"), "code": args.get("code") })), _ => Err(err("bad_type", "type must be click | mouseMoved | mousePressed | mouseReleased | mouseWheel | keyDown | keyUp | rawKeyDown | char", None)),