Why I Built This
Late at night, I plugged my Android phone into my Mac. It wasn't recognized. I swapped the cable. Restarted. Still nothing.
Sound familiar?
- Transfer stops halfway — restarting doesn't help
- Sending a few GBs takes 20–30 minutes, then fails halfway through and you have to start over
- Used a sync tool, made a mistake, and accidentally deleted an entire important folder
That last one was the final straw. I decided to build a simple, reliable tool focused purely on file transfer — and that's how this app was born.
What I Built
Hiyoko MTP — an Android file transfer app for Mac.
↓ Transfer in progress (41.9MB/s)

Tech Stack
| Tech | Role |
|---|---|
| Rust | Transfer engine, nusb integration |
| Tauri | Desktop app shell |
| React | UI |
| nusb | Rust-native USB library, direct IOKit calls |
| Tokio | Async runtime |
Why Rust + Tauri?
The root cause of instability in existing tools is that they handle the MTP protocol on a single thread. When one file transfer gets stuck, it blocks everything — leading to crashes and freezes.
By using Rust's concurrency model and isolating transfer queues per thread, I achieved a structure where one file failing doesn't block the whole queue. That's the heart of the multi-lane transfer engine.
I chose Tauri over Electron because it's lighter and feels closer to a native Mac app.
Technical Deep-Dive
Switching from libmtp to nusb
I initially built the whole thing with libmtp. Then I hit a critical problem.
libmtp and libusb were originally designed for Linux, and macOS handles USB devices in a fundamentally different way:
libmtp (designed for Linux)
↓
libusb
↓
macOS IOKit ← conflict happens here
On Linux, you can call detach_kernel_driver() to release the kernel driver — but this function doesn't work on macOS. The result: phones wouldn't be recognized at all.
The fix was switching to nusb:
Custom MTP stack (Rust)
↓
nusb (Rust-native, calls IOKit directly)
↓
macOS IOKit ← no conflict!
nusb is a Rust-native USB library that calls IOKit directly on macOS. Phone recognition issues? Gone.
Transfer Design Inspired by Video Streaming
The design for the multi-lane transfer engine was surprisingly inspired by video streaming.
Streaming services fetch multiple chunks in parallel so that one slow chunk doesn't stall the whole video. I applied the same idea to MTP transfers — by isolating transfer queues per thread, one file failing no longer blocks the rest.
Handling Edge Cases
Cable disconnect recovery
If you unplug the cable mid-transfer, you can reconnect and hit "Resume" to pick up right where you left off. The transfer queue state is managed independently from the session, so you don't have to start over.
Special character filenames
Characters like ?, :, * are fine on Mac but invalid in Android's MTP filesystem. These are automatically replaced with _ during transfer. I actually tested this by force-creating files with special characters in the terminal.
Tuning Turbo Mode
Turbo mode was the hardest part to implement. When sending files over MTP, chunk size directly affects speed:
- Too small → more overhead → slower
- Too large → too much memory → freezes
I tuned the parameters through a lot of trial and error. The concept of "adaptive bitrate" from video streaming was useful here too.
Lessons from Shipping
Shipping an indie app turned out to be harder than building it.
No Apple Developer Signature (Yet)
Since I'm not enrolled in the Apple Developer Program (~$99/year), Gatekeeper shows a warning on first launch. The current workaround:
xattr -cr /Applications/Hiyoko\ MTP.app
I plan to add proper signing once the app starts generating revenue.
Building a Universal Binary Without Apple Silicon Hardware
I'm on an Intel Mac, so I haven't been able to test on Apple Silicon personally. The build is set up as a Universal Binary and early M1/M2 users have reported it working — but I'd love more feedback from Apple Silicon users to confirm stability. If you try it, please let me know how it goes!
Fighting Gumroad's Identity Verification
Gumroad requires identity verification through Stripe. My ID kept getting rejected, and at one point I found myself at midnight holding a lamp to get a clearer photo of my ID card.
The cause? I'd registered on Gumroad with my name in Roman characters, but my Japanese ID shows it in kanji. A completely avoidable mistake — discovered at midnight. 😅
Key Features
The core is the multi-lane transfer engine. Everything else is "nice to have" extras I packed in.
File Management
- Multi-lane transfer engine: Rust concurrency dramatically reduces transfer wait times
- Dual pane: View Mac and Android side by side for intuitive operation
- Drag & drop: Drop files directly from Finder
- 4GB+ file support: Stable transfer for 4K video files
- Conflict resolution dialog: Choose Skip / Overwrite / Keep Both per file
- Smart Resume: Auto-skips already-transferred files by size comparison
- Batch rename: Three modes — Replace Text / Add Text / Format
- Priority Strategy: Small Files first / Large Files first / Sequential
- Auto-retry: Retry failed tasks individually or all at once
Transfer Modes
| Mode | Speed | Timestamp | Best for |
|---|---|---|---|
| Turbo | Fastest | Updated | When you just need speed |
| Balanced | Medium | Preserved | Everyday use |
| Safe (1 Lane) | Slower | Preserved | Important files |
When in doubt, use Balanced.
Requirements
- macOS 13 Ventura or later
- Intel Mac / Apple Silicon (Universal Binary)
- Android 11–16
I'm just getting started with indie dev, but I hope this reaches someone struggling with the same problem. 🐤
If you have an M1/M2 Mac and want to try it out, feedback is especially welcome — bug reports and thoughts via the English form below.
🐤 Hiyoko MTP
- One-time purchase: $20 — https://hiyokoko.gumroad.com/l/bwfmdk
- 30-day no-questions-asked refund — "device not recognized" or "speed not what I expected" are both totally valid reasons
- Bug reports & feedback (English): https://forms.gle/wxorzWq1MSt32Ru99
Top comments (0)