The Problem
If you use Android and Mac together, you've probably felt this pain.
You take a screenshot on your phone. Now you need it on your Mac. So you either wait for Google Photos to sync, DM it to yourself, or plug in a cable and dig through MTP folders.
None of these are broken. But doing it every single day gets old fast.
So I built Hiyoko Shot — a macOS menu bar app that transfers Android screenshots to your Mac the moment you take them.
What It Does
- 📱 Auto-transfers screenshots from Android to Mac (USB or Wi-Fi)
- 🖼 Gallery UI with preview and native drag & drop
- ⚡ Low-overhead transfer via ADB
exec-out - 🦀 Built with Rust (backend), React + Framer Motion (frontend), Tauri (framework)
The Interesting Parts
exec-out vs adb pull
The obvious approach for transferring files from Android is adb pull. But Hiyoko Shot uses adb exec-out instead.
adb exec-out screencap -p
Here's why it matters:
| Method | Flow |
|---|---|
adb pull |
Write to Android → Transfer → Read on Mac |
exec-out |
Stream stdout directly to Mac |
exec-out eliminates the intermediate write step entirely. The output streams directly to the host, cutting out the overhead of saving and re-reading a file on the Android side.
Polling for New Screenshots
To detect when a new screenshot is taken, the app polls /sdcard/DCIM/Screenshots/ on the Android device every 5 seconds via ADB.
// pseudocode
loop {
let current_files = list_screenshots_via_adb(&device)?;
let new_files = diff(&last_snapshot, ¤t_files);
for file in new_files {
transfer_file(&device, &file).await?;
}
last_snapshot = current_files;
sleep(Duration::from_secs(5)).await;
}
When the app is hidden in the background, the polling interval increases to reduce CPU and memory usage.
Displaying Images in Tauri: The Base64 Approach
When building the gallery UI in Tauri, passing a file path directly to <img src> didn't work reliably across environments. The fix was to encode images as Base64 Data URIs on the Rust side and pass them to React.
// Rust
let bytes = fs::read(&image_path)?;
let base64 = general_purpose::STANDARD.encode(&bytes);
Ok(format!("data:image/jpeg;base64,{}", base64))
// React
<img src={base64DataUri} alt="screenshot" />
This works 100% reliably regardless of environment.
Native Drag & Drop
This is the feature I'm most proud of.
You can grab any image from the gallery and drop it directly into Slack, Discord, Finder — whatever. Tauri's startDrag API initiates a native macOS drag session, so it works exactly like dragging a file from Finder.
The difference in daily workflow is subtle but real. Instead of "find the file → drag it", it's just "drag from the app". One less step, every time.
Wi-Fi Mode
Once you pair via USB, the app can connect over Wi-Fi on the same network. It uses ADB over TCP/IP, so you don't need a cable for subsequent sessions.
adb tcpip 5555
adb connect 192.168.x.x:5555
Stack Summary
| Layer | Tech |
|---|---|
| Backend | Rust |
| Frontend | React + Framer Motion |
| Framework | Tauri |
| ADB | exec-out / TCP/IP |
| Persistence | tauri-plugin-store |
| Image conversion | Rust image crate |
| UI | Menu bar popover (dark mode native) |
Wrapping Up
"Get Android screenshots onto a Mac" is a niche problem — but it's one that comes up every single day for anyone who lives between the two platforms.
Building this in Rust + Tauri was a great experience. Writing low-level ADB communication in Rust feels natural, and the performance headroom is comforting.
If you're an Android + Mac user, give it a try. It's a one-time purchase at $7.