Real-time file sharing between Macs and PCs on the same local network. Drag, drop, done.
Xhare is a small desktop app that finds every other Xhare instance on your Wi‑Fi or wired LAN and lets you push files to all of them at once — no accounts, no cloud round-trips, no chat-app compression. Files travel directly between machines over TCP at LAN speeds (think: hundreds of MB/s on gigabit Ethernet).
Built with Tauri 2 (Rust + WebView), React 19, TypeScript, and Tailwind CSS v4. Lives in your menu bar / system tray.
- Zero‑config LAN discovery — mDNS-based, peers show up automatically the moment they launch Xhare. Falls back to manual IP entry when needed.
- Direct TCP transfer — 256 KB chunks with CRC32 per chunk, broadcast to every online peer in parallel. No server in the middle.
- Folder support — drop a folder and Xhare auto-zips it in the background with progress feedback ("Zipping… 47%") before transmitting.
- Clipboard‑aware —
⌘V/Ctrl+Vpastes a file straight from Finder or Explorer (including screenshots and image data from the system clipboard);⌘C/Ctrl+Ccopies selected received files as real file references so pasting in Finder / Explorer / WhatsApp / iMessage drops the actual file. - Smart received files
- Browser-style name dedupe:
notes.txt,notes (1).txt, … - Saved-to-disk indicator, hover actions for save / open / reveal
- Batched OS notifications (one toast per burst, suppressed while window is focused)
- Browser-style name dedupe:
- Multi-select with bulk actions —
⌘A/Ctrl+Atoggles select-all, click anywhere on a row to toggle, floating action bar to save or delete selected files in one go. - Native tray icon — closing the window keeps the app alive in the menu bar (macOS) or system tray (Windows), Docker-style. The macOS dock icon hides itself in that state and comes back when the window is reopened.
- Cross-platform UX parity — keyboard, drag-drop, clipboard, and reveal behaviors all map to native OS conventions on both platforms.
Pre-built installers are published on the Releases page:
- macOS — universal
.dmg(Intel + Apple Silicon) - Windows —
.exeNSIS installer (no admin required, auto-detects pt-BR)
Builds are unsigned, so the OS will complain the first time you open them.
macOS: double-click is blocked. Go to System Settings → Privacy & Security, scroll to the bottom and click "Open Anyway" next to the Xhare entry. First time only — subsequent launches are normal.
Windows: SmartScreen may warn. Click "More info" → "Run anyway".
- Launch Xhare on every machine you want to share between.
- They show up in the Devices sidebar within ~2 seconds.
- Drag any file or folder onto the window — or
⌘V/Ctrl+Vto paste — and it streams to every online peer. - Received files land in the feed. Click Save to copy them to your download folder, or open them straight from cache.
The cache is wiped on app exit, so anything you don't explicitly save is discarded — your real copies live wherever you chose to save them.
| macOS | Windows | |
|---|---|---|
| Node.js 20+ | volta install node or nodejs.org |
same |
| pnpm 9+ | npm i -g pnpm |
same |
| Rust (stable) | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
winget install Rustlang.Rustup |
| C/C++ toolchain | — | MSVC Build Tools with "Desktop development with C++" workload |
After installing Rust, restart your terminal so cargo is on PATH.
cd app
pnpm install
# pnpm 10+ requires explicit approval for packages with build scripts
pnpm approve-builds # approve esbuild and unrs-resolver when promptedcd app
pnpm tauri devThe first launch builds the Rust crate (a couple of minutes); subsequent launches are near-instant thanks to incremental compilation.
| Command | What it does |
|---|---|
pnpm tauri dev |
Full app in dev mode with hot reload |
pnpm dev |
Vite dev server only (no Tauri shell) |
pnpm build |
Production frontend build |
pnpm tauri build |
Build native installers locally (.dmg / .exe) |
pnpm typecheck |
TypeScript check, no emit |
pnpm lint / pnpm lint:fix |
ESLint |
pnpm test / pnpm test:watch |
Vitest |
app/
├── src/
│ ├── components/ UI primitives (Button, Dialog, Toast, …)
│ ├── features/
│ │ ├── devices/ Peer list + manual-add dialog
│ │ ├── file-feed/ File rows, drag overlay, bulk action bar
│ │ ├── logs/ In-app log viewer
│ │ └── settings/ Download folder + cache TTL
│ ├── hooks/ Cross-cutting hooks (paste, drag-drop, shortcuts)
│ ├── services/ Thin wrappers around Tauri invokes
│ ├── stores/ Zustand stores (devices, files, settings, …)
│ ├── types/ Shared TS types
│ └── utils/ Small helpers (formatSize, uniqueName, cn)
└── src-tauri/
└── src/
├── discovery.rs mDNS browse + ARP scan + heartbeat reconciliation
├── transfer.rs TCP file protocol, folder zip, clipboard ops
├── lan_scan.rs Cross-platform `arp -a` parser
├── settings.rs Persistent JSON settings (atomic writes)
├── tray.rs Native tray icon + unread badge
└── logger.rs File + terminal logging with daily rotation
┌──────────────────────┬──────────────────────────────┐
│ u32 header length │ header bytes (JSON, UTF-8) │
└──────────────────────┴──────────────────────────────┘
┌──────────────────────┬──────────┬───────────────────┐
│ u32 chunk length │ u32 crc32│ chunk bytes │ ← repeats
└──────────────────────┴──────────┴───────────────────┘
┌─────────────────┐
│ u32 length = 0 │ ← EOF sentinel
└─────────────────┘
Header schema: { fileId, name, size, from }. Chunks are 256 KB; CRC mismatch
or short read aborts the transfer with a structured error event.
| Concern | macOS | Windows |
|---|---|---|
| Cache dir | ~/Library/Caches/Xhare/ |
%LOCALAPPDATA%\Xhare\ |
| Logs | ~/Library/Application Support/com.felipe.xhare/logs/ |
%APPDATA%\com.felipe.xhare\logs\ |
| Settings | ~/Library/Application Support/com.felipe.xhare/settings.json |
%APPDATA%\com.felipe.xhare\settings.json |
| Tray | Menu bar (top) | System tray (bottom right) |
| Reveal in folder | open -R |
explorer /select, |
| Clipboard files | NSPasteboard via AppleScriptObjC |
Get-Clipboard -Format FileDropList |
Use the helper script — it bumps every manifest, commits, tags, and pushes in one shot:
cd app
pnpm release:patch # 0.1.0 → 0.1.1 bug fixes
pnpm release:minor # 0.1.5 → 0.2.0 new feature, backward compatible
pnpm release:major # 0.4.2 → 1.0.0 breaking changeThe script (app/scripts/release.mjs):
- Refuses to run if you're not on
main, the working tree is dirty, ormainis out of sync withorigin. - Bumps the version in
package.json,tauri.conf.json, andCargo.toml(plus refreshesCargo.lock). - Commits with
release: vX.Y.Zand creates an annotated tag. - Pushes both the commit and the tag.
The tag push triggers
.github/workflows/release.yml, which builds
the macOS universal .dmg and Windows .exe (NSIS) in parallel and publishes
a GitHub Release with both binaries attached (~15-25 min end-to-end).
First release: since
package.jsonalready starts at0.1.0, cut the very first tag manually instead of bumping:git tag v0.1.0 && git push --tagsEvery release after that goes through
pnpm release:*.
You can also dispatch the workflow manually from the Actions tab (no new tag needed) to rebuild after fixing a CI issue.
Unit tests run with Vitest (pnpm test). Components use
@testing-library/react; Tauri APIs are mocked in src/test/setup.ts.
The Rust side has cargo test (mostly small unit tests for path helpers and
parsers).
- Image / video thumbnails in the feed
- Pause / resume / cancel mid-transfer
- Global hotkey (
⌘Shift+X/Ctrl+Shift+X) to summon the window - Drag onto the tray icon to send (native, hard cross-platform)
- Code signing + notarization for friction-free first launch
- Auto-update via Tauri updater
MIT — see LICENSE if present, otherwise treat as MIT.
Does it work across networks? No — both devices must be on the same LAN (same Wi-Fi, same router). Xhare doesn't punch through NAT or proxy through a server.
Why are notifications coming from "Terminal" / "PowerShell"?
That's expected in dev mode (pnpm tauri dev): the OS attributes the
notification to the parent process. Run a build (pnpm tauri build) and
install the produced .dmg / .exe — the bundled app registers properly and
notifications come from "Xhare" with the right icon.
Where do received files go? Into the OS cache dir (see State of the art above), until you either save them explicitly or quit the app — quitting wipes the cache so nothing accumulates.
Can I add a peer manually if mDNS isn't working?
Yes — there's a "+" button at the top of the device list that takes an IP.
The peer needs to be running Xhare for the transfer port (9876/tcp) to be
open.