Skip to content

Release 1.4.2: UHID HID input, overlay cursor, and pointer compensation - #44

Merged
anasvhora284 merged 18 commits into
masterfrom
release/1.4.2
Sep 23, 2026
Merged

anasvhora284 merged 18 commits into
masterfrom
release/1.4.2

Conversation

@anasvhora284

@anasvhora284 anasvhora284 commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

Overview

Release 1.4.2 represents a major architectural and functional upgrade for Input Leaf. It introduces native kernel-level UHID hardware keyboard and mouse emulation via Shizuku, dynamic Android system pointer speed compensation, mid-session display rotation handling, rich in-app Markdown release notes, and accessibility overlay reliability improvements, while completely removing legacy daemon sidecars.


Features Added

  • Native Hardware Keyboard & Mouse Emulation (UHID via Shizuku)

    • Attaches genuine Linux kernel HID keyboard and mouse devices when the cursor enters the phone screen and cleanly detaches on Leave.
    • Enables Android's native physical hardware keyboard subsystem, letting Gboard (and other system IMEs) display the on-screen candidate composition bar for Chinese Pinyin, Gujarati Inscript, Indic, Russian, and other language layouts.
  • System Pointer Speed Synchronization & Sub-Pixel Tracking

    • Directly observes Android's native mouse pointer speed setting (Settings.System.POINTER_SPEED, range -7 to +7).
    • Implements dynamic gain curve compensation and accumulates fractional sub-pixel deltas so the pointer responds accurately to the user's system mouse preferences without needing a redundant in-app slider.
  • Mid-Session Dynamic Display Rotation (DINF)

    • Sends live screen dimensions and orientation updates over the Input Leap connection on screen rotation mid-session.
    • Keeps coordinate mapping, pointer clamping, and edge anchoring aligned without requiring a disconnect/reconnect.
  • Rich Markdown Changelog in Update Dialog

    • Integrated a lightweight, zero-dependency Compose Markdown engine (MarkdownContent.kt) that renders GitHub release notes with full support for headings, bullet/numbered lists, bold/italic styling, inline code, and clickable URLs.
  • Accessibility & Connection UX Enhancements

    • Accessibility overlay cursor remains visible and reliable without treating SYSTEM_ALERT_WINDOW as an exclusive blocker.
    • Server cards show clear real-time connecting states; first Enter after connecting no longer suffers from latency freezes; and Shizuku binders re-attach reliably across app reboots.

Technical & Architectural Improvements

  • In-Process /dev/uhid Communication: Completely removed the standalone uhid-server Java DEX sidecar daemon and unframed local socket IPC. Input Leaf now writes standard Linux kernel uhid_event structs directly into /dev/uhid via Shizuku (UhidChannel.kt, UhidProtocol.kt).
  • Direct Evdev-to-HID Scancode Pipeline: Replaced legacy keysym translation tables with EvdevToHid.kt and standard USB HID keyboard reports (HidKeyboard.kt), providing 1:1 hardware key injection.
  • Modular Pointer Engine: Added HidMouseState.kt (delta accumulation), MouseEdgeAnchor.kt (edge snapping and re-entry), MousePointerCompensation.kt (OEM curve compensation including OnePlus/OPPO offsets), and HidAttachmentController.kt.
  • Codebase Pruning: Removed obsolete setup wizards, unused resolvers, legacy patch files (uhid_mouse_backup.patch), and stale documentation (DEVELOPMENT_JOURNEY.md, UI_REDESIGN.md).
  • Expanded Unit Test Suite: Added comprehensive test coverage across UhidProtocolTest, HidKeyboardTest, HidMouseTest, MouseEdgeAnchorTest, MousePointerCompensationTest, PointerSpeedTest, and MarkdownParserTest.

Issues Closed & Resolved

  • Closes Add configurable virtual pointer sensitivity #18 (Configurable pointer sensitivity) — Virtual mouse movement dynamically hooks into the native Android system setting (Settings.System.POINTER_SPEED, range -7 to +7) with active compensation curves and fractional sub-pixel accumulation in HidMouseState.
  • Closes Add framing and versioning to the app-to-UHID event protocol #20 (App-to-UHID event protocol framing) — Superseded by the complete removal of the standalone uhid-server Java DEX sidecar daemon and local socket IPC in favor of direct /dev/uhid via Shizuku.
  • Closes IME layouts (Gujarati Inscript, etc.) send no KeyDown from Deskflow #28 (IME layouts like Gujarati Inscript / Indic dropping keys) — Resolved via physical hardware keyboard emulation. Users keep the host keyboard on standard QWERTY and configure the desired layout in Android's physical keyboard settings (Settings → System → Physical keyboard → Gboard), allowing Android to compose Gujarati, Indic, and other language layouts natively.
  • Closes 无法使用电脑键盘在手机上输入中文 #34 (Chinese IME input via computer keyboard) — Resolved via physical keyboard attachment. Android recognizes the phone as having an external hardware keyboard, enabling Gboard's native physical keyboard IME pipeline (Pinyin composition bar and candidate selection) directly on-device.

(Note: #18, #28, and #34 are resolved in Shizuku / UHID mode where a real kernel device is recognized by Android. Pure Accessibility fallback remains touch-drag and InputLeafIME-based).


Test Plan & Verification

  • Overlay Cursor: Visible and functional in accessibility mode without treating overlay permission as an exclusive gate.
  • UHID Device Lifecycle: HID keyboard and mouse attach cleanly on Enter and disconnect immediately on Leave.
  • Rotation Mid-Session: Live screen dimensions update via DINF and pointer bounds remain 1:1 after rotating phone.
  • Pointer Tracking: Tracks smoothly across DPIs with sub-pixel compensation and edge anchoring.
  • Connection Stability: Server cards reflect connecting state; first Enter does not freeze; Shizuku reconnects reliably.
  • Rich Changelog: Markdown parser formats headers, bullet lists, bold/italic, code, and links in update dialog.
  • Device Verified: Tested and verified live on Android 16 (OnePlus Nord 4).

anasvhora284 and others added 2 commits September 15, 2026 16:59
The DEX was packaged with no app launcher; keep HID work on experiment/uhid-system-cursor instead of shipping a second unused stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
Handshake logs go through Log.* with a JVM-safe wrapper; connection state stays a small class next to the service.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 13:20

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @anasvhora284, your pull request is larger than the review limit of 150,000 diff characters

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

… rich Markdown changelog

- Native UHID keyboard and mouse attach on Enter and disconnect on Leave
- Live mid-session DINF screen size updates and native pointer compensation/edge anchoring
- Server cards now reflect real-time connecting states
- In-house lightweight Markdown parser and renderer for update changelogs
- Fixed accessibility overlay cursor visibility without SYSTEM_ALERT_WINDOW-only gate
- Fixed first-enter freeze and Shizuku bind/reconnection lifecycle
- Fixed raw Markdown syntax rendering in the Update Available dialog
- Added MarkdownContent component and 14 unit test cases in MarkdownParserTest
@guaje
guaje self-requested a review September 21, 2026 12:53

@guaje guaje left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the 1.4.2 UHID subsystem and changelog renderer — 5 findings, anchored inline.

  • UhidProtocol.kt:20 — UHID event-type constants diverge from the kernel ABI; the readiness wait happens to work on the wrong-but-adjacent kernel signal, and the unit tests cannot catch it (issue)
  • MarkdownContent.kt:169 — Markdown-link URLs from the remote release body bypass the http(s) restriction applied to raw URLs (issue, security)
  • ConnectionService.kt:588 — retry-delay logic inlined into a Service method and its boundary tests deleted; the patch gate now passes because the logic is no longer measured (issue)
  • ShizukuInputInjector.kt:170 — phantom UHID keyboard/mouse can persist after binder death because kernel cleanup relies on process-kill timing (issue)
  • ShizukuInputInjector.kt:371 — wheel-notch truncation plus three smaller low-severity notes (nitpick)

For balance: the UHID_CREATE2 byte layout was checked field-by-field against uhid.h and is exact (incl. rd_size u16 at 260), the module removal and CI teardown are coherent, and the coverage gates were not relaxed.

Comment thread app/src/main/java/com/inputleaf/android/shizuku/uhid/UhidProtocol.kt Outdated
Comment thread app/src/main/java/com/inputleaf/android/ui/components/MarkdownContent.kt Outdated
Comment thread app/src/main/java/com/inputleaf/android/service/ConnectionService.kt Outdated
Comment thread app/src/main/java/com/inputleaf/android/shizuku/ShizukuInputInjector.kt Outdated
anasvhora284 and others added 8 commits September 22, 2026 10:50
compose-ui-test-junit4 pulls Espresso 3.5.0 transitively, and nothing in the
build declared Espresso at all. 3.5.0 reflects on InputManager.getInstance(),
which Android 14 removed, so InputManagerEventInjectionStrategy.initialize()
throws NoSuchMethodException and every Compose UI test dies inside
Espresso.onIdle before reaching an assertion. On a Pixel-class API 36 device
both MainActivitySmokeTest cases failed this way.

3.6.1 does not fix it -- verified by disassembling the artifact, it still calls
getInstance() with no fallback. 3.7.0 routes through getInputManager() instead.

With this pin the full instrumentation suite passes 13/13, which also means the
MainActivitySmokeTest assertions for the two-page onboarding flow are covered
for the first time; they had been updated but never actually executed.

Test-scope only: no app code and no production dependency is affected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #44 review finding. Changelog text is fetched from the GitHub release API,
and the Markdown-link branch of parseInline passed the URL straight into
LinkAnnotation.Url with no scheme validation -- while the raw-URL branch twenty
lines below was already restricted to http(s). The default AndroidUriHandler
dispatches whatever it is through ACTION_VIEW, so a tampered, typosquatted or
compromised release body containing [label](intent://...) turned one tap in the
update dialog into an arbitrary implicit intent.

Rather than repeat the check, both link branches now go through a single
MarkdownParser.isDispatchableUrl gate, since the cause of the bug was two
branches with one policy between them. It fails closed: odd casing or leading
whitespace renders as plain text rather than being normalised and dispatched.

The @mention branch needed no change -- it builds a hardcoded
https://github.com/ prefix and the regex limits the username charset.

Tests assert no dispatchable annotation survives for intent/javascript/file/
content/market schemes, that http(s) still linkifies, and that the check fails
closed on casing and padding. Verified by mutation: replacing the gate with
`if (true)` fails all three.

Note for reviewers: a URL containing parens, e.g. javascript:alert(1), leaves a
stray ')' in the rendered text, because INLINE_TOKEN_REGEX ends the URL at the
first ')'. That is a pre-existing cosmetic quirk of the regex and not a hole in
the scheme check -- the link is still suppressed. It is asserted separately so
the security test does not depend on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #44 review finding. The constants did not match uapi/linux/uhid.h: START was
4 and OPEN was 6, which are really the kernel's OPEN and OUTPUT. So the reader
ignored the real START(2), consumed the real OPEN(4) under the name "START", and
its "OPEN" branch watched for OUTPUT(6) -- a host->device report -- and therefore
never fired. The tests injected the same wrong constants back into fake streams,
so CI proved the file agreed with itself rather than with the kernel.

Wire behaviour is deliberately unchanged. The gate was already effectively
kernel OPEN(4), and that is the correct gate: START only means hid-core created
the device, which is before EventHub attaches, so writing INPUT2 on START would
race the very drop the readiness wait exists to prevent. The fix names what was
already happening rather than re-pointing the wait at START(2), which would have
been a real regression dressed up as a correction.

- Constants now carry the kernel's values, plus STOP/CLOSE/OUTPUT/GET_REPORT/
  SET_REPORT, so the read path can name every event it may see.
- awaitReady waits on OPEN then probes sysfs. The old second window also accepted
  "OPEN"(=OUTPUT), which for a keyboard is really an LED report; dropping that
  costs at most presenceTimeoutMs in a rare case and no longer conflates a host
  report with readiness.
- Config renamed startTimeoutMs -> openTimeoutMs, and openGraceMs removed: it
  only existed to bound a wait for an event that could never arrive.
- payloadSize corrected. OUTPUT was treated as zero-payload when it carries
  data[4096] + u16 size + u8 rtype, so draining one would have left 4099 bytes
  in the stream and misframed every later read. Only waitForStart consumes this,
  and that function currently has no production caller -- worth deciding whether
  to keep it, but it should not carry a wrong table meanwhile.
- Tests assert the literal kernel numbers, not the constants, so they guard the
  ABI instead of restating it. Verified by mutation: restoring START=4/OPEN=6
  fails both new tests, and the readiness tests still pass unchanged because the
  wire value on that path was always 4.

410 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…view notes

PR #44 review findings #3 and #5.

#3 -- RetryDelayCalculator restored. Its lookup had been inlined into
ConnectionService.scheduleRetry and its boundary tests (attempts 0/1/2/3/4/5/10
and negative) deleted with it. Logic inside a Service method is unreachable from
the JVM suite, so the exact delay per attempt was asserted nowhere and the patch
gate passed because the code was no longer measured. Tests restored verbatim.
The clamp is now a single coerceIn rather than a branch plus a minOf.

#5 -- wheel notches no longer truncate. `yDelta / 120` discarded anything finer
than a notch, so a 60-unit delta scrolled nothing while the accessibility branch
used float division. Input Leap normalises to 120 units, so only non-conforming
servers lost input, silently. The remainder is now banked.

Extracted as WheelNotchAccumulator rather than kept inline, for the same reason
as #3: ShizukuInputInjector needs a bound AIDL service to exercise, so anything
inline there is untestable. Covered for sub-notch banking, negative symmetry,
cancellation, multi-notch deltas, reset across attach, and no drift over 1000
conforming events. A partial notch is reset on detach and disconnect so it
cannot leak into the next session, and a banked delta still counts as handled so
it is not replayed by the fallback path.

Review notes:
1. ConnectionService.screenWidth/screenHeight are now @volatile. They are written
   on the main thread (rotation/DINF) and read from the IO event loop; the HID
   path is published via HidMouseState.resizeDisplay, but the fallback touch path
   read them directly and could clamp against stale bounds.
2. MousePointerCompensation.gainForSpeedMmPerS no longer carries an unreachable
   fallback: the curve is +Inf-terminated, so a segment always matches.
3. UhidChannelReadinessTest no longer races wall-clock timing. The kernel event
   is written into the pipe up front instead of from a sleeping feeder thread,
   and the deadline maths runs on a virtual clock through the sleeper/nanoTime
   seams, so no assertion depends on machine speed. Confirmed stable over five
   consecutive clean reruns.

Two tests added there while the seams were in place: that a missing OPEN degrades
within its bounded window rather than hanging (the ColorOS path), and that a
kernel START alone is not treated as readiness.

423 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR #44 review finding #4. Nothing wrote UHID_DESTROY when the client went away:
the teardown in disconnect() runs in the client, so by the time it fires its
closeVirtualKeyboard()/closeVirtualMouse() calls throw DeadObjectException and
the runCatching wrappers swallow them. Cleanup then depended entirely on the
injector process being reaped, and while that lingers -- reportedly a while on
some OEM Shizuku builds -- the devices stay attached and Android goes on
believing a physical keyboard is present, keeping the soft keyboard suppressed.

The injector now watches the client: attachClient(IBinder) links to death on a
Binder owned by the client process and closes both UhidChannels from the death
callback, inside the process that owns the /dev/uhid fds. A token that is already
dead throws at linkToDeath instead of calling back, so that case tears down
inline. Registration is best-effort on the client side: an injector that cannot
watch still works, it just falls back to process reaping as before.

SERVICE_VERSION 4 -> 5, because a cached v4 UserService from a previous install
does not implement attachClient and would throw on every bind.

Also adds the openChannel seam to InputInjectorService so the UHID lifecycle can
be exercised without a real /dev/uhid; the death path was otherwise untestable.
Covered for: DESTROY written for an attached mouse, no device left open
afterwards, idempotent death with nothing attached, a null token ignored, and
re-attach replacing the previous watch. Verified by mutation -- emptying the
death callback fails three of the five.

Scope, honestly: this closes the client-death leak, which is reachable by simply
force-stopping the app while HID is attached. It does not fully close the case
the review described, where the Shizuku *server* dies while the app lives: the
client binder is still alive then, so this watch does not fire, and detecting
that from inside the injector needs a separate signal. That part is unverified
without a device and is left open deliberately rather than claimed as fixed.

428 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ocess

Crossing onto the phone left the cursor stranded: it flashed at display centre
and then behaved as though centre were the entry point, so every later move was
offset by that delta for the rest of the session.

Cause: every Leave destroys the HID mouse and every Enter recreates it, so AOSP
seeds a brand new pointer at display centre each time. The warp was then sent
from the app process, back across Binder. AOSP initialises the sprite
asynchronously, and when that landed after the warp it overwrote it -- leaving
the native pointer at centre while hidMouse.cooked believed it was at the entry
point. The two disagreed permanently.

The Enter coords are now handed to the injector (onHidMouseEnter/onHidMouseLeave)
and HidMouseEnterWarp applies the snap there, immediately after UHID OPEN, in the
process that owns the /dev/uhid fd. No Binder hop, so nothing can lose the race.

openVirtualMouse reports whether it actually warped (consumeEnterWarpApplied)
rather than the client inferring it: the idempotent "already open" branch emits
no INPUT2, and guessing from app state would make the client skip a snap that
never happened. The client advances its cooked model without replaying the
reports (sendHid=false), so the movement is not applied twice.

SERVICE_VERSION 5 -> 6 for the AIDL additions.

This is a Shizuku-path fix. It was written on feature/root-injection only because
that is where the work happened -- nothing here touches libsu, PrivilegeKind or
PrivilegedUserServiceHost -- and without it this release ships the stranded
cursor. Verified on a OnePlus Nord 4 (Android 16, Shizuku): daemon logs "applied
after UHID ready hidReports=2", client logs "daemonWarped=true ... sendHid=false",
and the cursor lands where it entered.

Known remaining: the pointer is still briefly visible at centre before the warp
lands, because AOSP draws the sprite on device creation and the warp is one frame
behind. Removing that entirely means not destroying the device on every Leave,
which is a separate design change.

439 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e is ready

Two write-path defects in UhidChannel, both of which delayed or lost the first
reports after CREATE2.

The readiness reader was never stopped. It stayed blocked in read() on the same
/dev/uhid handle used for writing, so INPUT2 and DESTROY could serialise behind
a pending read. stopReader now closes a dup'd ParcelFileDescriptor, which is
what actually unblocks read(): a FileInputStream built from a PFD does not own
the fd, so closing the stream alone is a no-op for the kernel object. If dup
fails there is nothing we could close later, so the reader is not started at
all rather than left unkillable -- readiness degrades to the presence probe.

Reports sent before the device was ready went straight out. AOSP seeds a new
pointer at display centre while it enumerates the device, and a report that
lands before the seed is simply overwritten -- the app believes the pointer
moved, the sprite never did, and every later relative delta is computed from
the wrong origin. Reports are now queued until readiness completes and flushed
in one go, so they land after the seed rather than before it.

CREATE2 also takes the write lock now, so device creation cannot interleave
with a report already in flight.

Measured on a OnePlus Nord 4 (Android 16, Shizuku): this took Enter from never
landing to landing roughly 3 times in 10. It is a real fix and a necessary one,
but it is not sufficient on its own -- winning that race reliably needs the
device to stop being destroyed on every Leave, which the next commit does.

439 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Crossing onto the phone was unreliable: the pointer appeared at display centre
and then behaved as though centre were the entry point, so every movement was
offset for the rest of the session.

Every Leave destroyed the HID mouse and every Enter created a new one, and AOSP
seeds a new pointer at display centre each time. Everything before this tried to
warp away from that seed before it was noticed, which is a race against AOSP's
own asynchronous initialisation -- and one that cannot be won reliably, because
nothing signals that the seed has landed. Three separate attempts at winning it
got as far as 3 successes in 10.

So stop causing it. The mouse now stays registered across a Leave and is only
destroyed after the cursor has been away for 30s. A normal crossing creates no
device, so there is no seed and no race: the pointer does not move while the
cursor is away, the position model stays true, and Enter is an ordinary delta
from a known position. Logs confirm it -- phase=ATTACHED and centerSeed=false on
every crossing, snapping from the real last position rather than from centre.

The keyboard still detaches immediately. While a HID keyboard is registered
Android believes a physical keyboard is present and keeps the soft keyboard
suppressed, and unlike the mouse it has no respawn problem.

The idle timer is cancelled by cancelLeaveDebounce, which Enter already calls,
so rapid back-and-forth never trips it.

Known remaining: pointer feel is not yet 1:1 under acceleration. That is the
compensation model, not this path, and is being investigated separately.

439 unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@anasvhora284

Copy link
Copy Markdown
Owner Author

@guaje I have made the changes according to your findings and some bugs i encountered

@anasvhora284
anasvhora284 requested a review from guaje September 22, 2026 12:27

@guaje guaje left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @anasvhora284,

Approved! I ran a second pass over the fix round (this time reading the full reader lifecycle and the new death-watch logic, and double-checking the UHID stuff against the kernel source), and it holds up. All five findings from the first review are properly fixed — the kernel-pinned ABI tests and the restored RetryDelayCalculator are especially nice. CI is green across the board.

One small thing you might want to fold in before tagging 1.4.2 (it's a one-word change, and it closes a potential crash): in ShizukuInputInjector.onHidMouseEnter, the generic catch runs handleRemoteException() and then falls through into finishPendingSnap(svc) using the binder it just declared dead. If injectHidMouse throws DeadObjectException there, nothing catches it — it escapes into the event-loop collector in ConnectionService, which has no catch either, so it reaches the uncaught handler and takes the app down. The DeadObjectException branch in the same handler already returns; doing the same after handleRemoteException() would make this path safe. If you fold it in, I won't file a follow-up issue.

On the readiness gate — I dug into it a bit since presence got moved behind OPEN, and I think it's worth a look soon, but not worth touching right before the tag:

  • From drivers/hid/uhid.c: UHID_START is queued during probe (uhid_hid_start), so it's guaranteed whenever the device is bound. UHID_OPEN only gets queued when a userspace process actually opens the device — stock InputReader does open scanned devices, so on AOSP the OPEN arrives, but nothing in the kernel guarantees an opener exists, and OEM ROMs with input filters / HID security policies are exactly this app's territory. On such a device, the gate waits for an event that never comes and burns the full openTimeoutMs on every attach.
  • The old ColorOS comment turned out to be an artifact of the wrong constants: what the old code kept seeing was the real OPEN (it was mislabeled "START"), and the event it "never saw" was value 6 = UHID_OUTPUT, which never arrives on any device because it's host→device. So we don't have evidence of an OPEN-less device, but we also don't have evidence that every device emits it, and the kernel allows OPEN-less operation, which is what matters for us.
  • Small thing I noticed: the reader already tracks START (UhidChannel.kt:220, and there's even a log branch for it), but the loop at :169 only exits on sawOpen. And the sysfs presence check now runs after OPEN (:173-178), so when OPEN arrives on time we still pay up to presenceTimeoutMs — where before, whichever signal came first won.

Since createDevice() proceeds either way, this is extra attach latency, not breakage — which is why I'd rather not rush a change to every device's attach path before the tag. Exiting on sawStart || sawOpen and demoting presence to a logged confirmation (plus virtual-clock tests for both exits through the existing seams) should cover it, I think.

Thanks for the quick turnaround on all of this 🙌

@rlqnwkd

rlqnwkd commented Sep 22, 2026

Copy link
Copy Markdown
44444444 3123213213123 Is it possible to reflect the mobile screen size in real-time—specifically the folded state—similar to Samsung's Multi Control?

A DeadObjectException from the enter snap escaped the event loop and crashed the process.

Co-authored-by: Cursor <cursoragent@cursor.com>
@anasvhora284

Copy link
Copy Markdown
Owner Author

@rlqnwkd This ships with the 1.4.2 release.

When the phone switches between the cover display and the inner display, Input Leaf sends the new screen size to the server on the open connection. A half-fold on the same inner screen does not change the reported size, so that posture stays as it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants