Skip to content

fix(mtp): release the MTP interface from macOS' Image Capture daemons - #483

Open
hariantara wants to merge 1 commit into
ganeshrvel:masterfrom
hariantara:fix/macos-image-capture-claims-mtp-interface
Open

fix(mtp): release the MTP interface from macOS' Image Capture daemons#483
hariantara wants to merge 1 commit into
ganeshrvel:masterfrom
hariantara:fix/macos-image-capture-claims-mtp-interface

Conversation

@hariantara

Copy link
Copy Markdown

Problem

On recent macOS, the device intermittently isn't detected — either nothing appears, or Refresh shows "An error occured while setting up the Phone" — while adb devices still lists the phone perfectly. The only known workaround has been restarting the Mac.

This looks like a bug in OpenMTP, but it isn't.

Cause

macOS starts ptpcamerad and mscamerad-xpc on demand for any device exposing an MTP/PTP interface, and they hold that interface exclusively. While they hold it:

USB: Open, err: <nil>
USB: ClaimInterface 0x0, err: LIBUSB_ERROR_ACCESS
MTP request OpenSession
MTP sendreq failed: LIBUSB_ERROR_NOT_FOUND
OpenSession failed: LIBUSB_ERROR_NOT_FOUND; attempting reset
error initializing: OpenSession after reset: LIBUSB_ERROR_NOT_FOUND

which surfaces as ErrorDeviceSetup.

The device stays fully visible to adb the entire time, because adb speaks to a different USB interface on the same device. That's what makes this so hard to recognise — everything suggests the phone is fine and the app is broken.

Evidence

Tested on a Redmi Note 15 5G (2717:ff48, MTP+ADB) on macOS 15.7, Apple Silicon:

  • Reproduces with OpenMTP not running at all, using the bundled kalam_debug_report — so it isn't a stale session or a leaked handle inside the app.
  • Reproduces with the released, Developer ID signed build, so it isn't a local build or signing artifact.
  • Killing only those two daemons, with adb left running, makes ClaimInterface return nil and the device enumerate fully (240 GB storage listed).
  • So adb is not the culprit, despite being the thing users notice first.

Fix

After a handshake fails, release the interface and retry.

Two details matter, and both cost me a while to find:

  1. SIGKILL, not SIGTERM. Both daemons ignore SIGTERM and survive with their original PIDs, while pkill still exits 0 for having signalled them — so the release reports success while changing nothing.
  2. No delay before the retry. launchd restarts them within milliseconds and they reclaim the interface immediately. The handshake has to follow the release with nothing awaited in between. An earlier version slept 500ms here and lost the race every single time.

Safeguards:

  • The daemons are only touched after a handshake has already failed, never pre-emptively.
  • Matched by exact process name (pkill -x), not command line — pkill -f is far too broad and would also match unrelated daemons such as appleh13camerad.
  • Both run as the current user and launchd restarts them on demand, so ending them is safe and reversible.
  • releaseMtpInterfaceClaimants() logs what it did, so this is diagnosable from a packaged build.

Testing

Verified on the hardware above, with the daemons confirmed holding the interface before launch:

  • Before: every handshake fails with ErrorDeviceSetup; daemon PIDs unchanged.
  • After: the release runs, the daemons are killed (PIDs change), and the app ends up holding interface 0 — confirmed by killing the daemons and observing that kalam_debug_report then gets LIBUSB_ERROR_ACCESS, i.e. OpenMTP has the device.
  • Unplug/replug/Refresh cycles recover on their own, with no restart needed.

Scope

Three files, +140/-2. No dependency changes and no rebuild of the Kalam dylib — it goes through the existing exported Dispose/Initialize.

Relates to #396 and #276, and likely a number of the other "not recognized on macOS" reports.

Fixes the long-standing "device not detected while adb still sees it" case
on macOS, where the only known workaround was restarting the machine.

macOS starts `ptpcamerad` and `mscamerad-xpc` on demand for any device that
exposes an MTP/PTP interface, and they hold that interface exclusively:

    USB: Open, err: <nil>
    USB: ClaimInterface 0x0, err: LIBUSB_ERROR_ACCESS
    MTP sendreq failed: LIBUSB_ERROR_NOT_FOUND
    error initializing: OpenSession after reset: LIBUSB_ERROR_NOT_FOUND

which reaches the UI as ErrorDeviceSetup. The device stays fully visible to
`adb devices` throughout, because adb speaks to a different interface on the
same device, which is what makes this so hard to recognise.

Verified on a Redmi Note 15 5G (2717:ff48, MTP+ADB) on macOS 15.7:

  - reproduces with the app closed, so it is not an OpenMTP session leak
  - killing only those two daemons, with adb left running, makes
    ClaimInterface return nil and the device enumerate fully
  - so adb is not the cause, despite being what users notice first

Two details matter, and both were found the hard way:

  - SIGKILL, not SIGTERM. Both daemons ignore SIGTERM while pkill still
    exits 0 for having signalled them, which reports a successful release
    while nothing has changed.
  - No delay before the retry. launchd restarts them within milliseconds
    and they reclaim the interface immediately, so the handshake has to
    follow the release with nothing awaited in between.

The daemons are only touched after a handshake has already failed, never
pre-emptively, and are matched by exact process name so nothing else is
affected. Both run as the current user and launchd restarts them on demand,
so ending them is safe and reversible.

Relates to ganeshrvel#396, ganeshrvel#276.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant