ilia 7bb3b5fc89 Fix paste creating duplicate entries in clipboard history
The clipboard polling thread was detecting programmatic writes (from
paste_and_refocus) as "new" clipboard content and re-inserting them.

Fix: share the last_hash between the polling thread and the paste
command via Arc<Mutex<String>>. When pasting, the Rust command now:
1. Computes the hash of the content being pasted
2. Updates the shared last_hash so the polling thread skips it
3. Writes to clipboard via arboard (moved from JS writeText)
4. Hides window and runs AppleScript paste

This eliminates the JS-side writeText call entirely — clipboard write
now happens in Rust where it can atomically update the hash before
the polling thread's next tick.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 14:33:33 -04:00

maCopy

A fast, native macOS clipboard manager that lives in your menu bar. Built with Tauri 2, React, TypeScript, and SQLite.

macOS Tauri License

Features

  • Menu bar app — no dock icon, stays out of your way
  • Global hotkeyCmd+Shift+V opens the window from anywhere
  • Clipboard monitoring — polls every 500ms for text, images, and file paths
  • Full-text search — instant filtering via SQLite FTS5
  • Quick pasteCmd+1 through Cmd+9 to paste the Nth item directly into the previous app
  • Paste & return — clicking an entry copies it to clipboard, hides the window, and auto-pastes into the previously-focused app
  • Multi-selectCmd+Click to toggle, Shift+Arrow or Shift+Click to range-select, Cmd+A for all, Enter to paste selected
  • Pin entries — pinned items stay at the top and are never auto-deleted
  • Context menu — right-click for Paste, Pin/Unpin, Delete (with multi-select support)
  • Resizable window — drag edges to resize; size is remembered between sessions
  • Window positioning — choose where the window appears: near cursor, center, or any corner (configurable in Settings)
  • Auto-trim — keeps up to 50K entries (configurable: 1K/5K/10K/50K)
  • Dark/light mode — follows macOS system appearance
  • Privacy — whitespace-only entries are ignored; pause monitoring from the tray

Prerequisites

  • macOS 10.15+
  • Rust 1.77+ — install via rustup
  • Node.js 18+ and npm
  • Xcode Command Line Toolsxcode-select --install
  • Accessibility permission — required for auto-paste (System Settings → Privacy & Security → Accessibility → add maCopy)

Quick Start

git clone gitea@10.0.30.169:ilia/maCopy.git
cd maCopy
npm install
npm run tauri dev

The app will compile the Rust backend, start the Vite dev server, and launch the menu bar app.

Usage

Action How
Open/close window Click tray icon or press Cmd+Shift+V
Paste an entry Click it, or press Enter
Quick paste Cmd+1 through Cmd+9
Search Just start typing
Select multiple Cmd+Click or Shift+Arrow
Select all Cmd+A
Delete Backspace or Delete (on selected items)
Pin/unpin Right-click → Pin/Unpin
Settings Tray icon → Settings…
Dismiss Escape or click outside

Development

Project Structure

maCopy/
├── src/                        # React + TypeScript frontend
│   ├── main.tsx                # Entry point
│   ├── App.tsx                 # Main app: state, keyboard nav, multi-select
│   ├── App.test.tsx            # App integration tests
│   ├── index.css               # Tailwind v4 + custom theme tokens
│   ├── types.ts                # Shared TypeScript interfaces
│   ├── test/                   # Test setup and factories
│   │   ├── setup.ts            # Tauri API mocks for jsdom
│   │   └── factories.ts        # makeEntry(), makeSettings()
│   └── components/
│       ├── SearchBar.tsx
│       ├── ClipboardList.tsx   # Entry list with multi-select, type badges
│       ├── ContextMenu.tsx     # Right-click menu with multi-select labels
│       └── SettingsPanel.tsx   # Toggles, max history, window position
├── src-tauri/                  # Rust backend
│   ├── Cargo.toml
│   ├── tauri.conf.json         # Tauri config, permissions, window
│   └── src/
│       ├── main.rs             # Binary entry point
│       ├── lib.rs              # App setup: tray, hotkey, window management
│       ├── clipboard.rs        # Background polling thread (500ms, SHA-256 dedup)
│       ├── db.rs               # SQLite CRUD + FTS5 + settings + auto-trim
│       └── commands.rs         # Tauri IPC commands
├── .cursor/rules/              # Cursor AI rules for this project
├── package.json
├── vite.config.ts
├── vitest.config.ts
└── tsconfig.json

Scripts

Command Description
npm run tauri dev Run in development mode with hot reload
npm run tauri build Build a release .app bundle
npm test Run frontend tests (Vitest, 47 tests)
npm run test:rust Run Rust backend tests (27 tests)
npm run test:all Run all tests (frontend + backend)
npm run lint TypeScript type-check
npm run check Lint + all tests

Tech Stack

Layer Technology
Framework Tauri 2
Frontend React 18, TypeScript, Tailwind CSS v4
Backend Rust, rusqlite (bundled SQLite)
Clipboard arboard for system clipboard access
Mouse position core-graphics for global cursor coordinates
Search SQLite FTS5 with content-sync triggers
Hotkey tauri-plugin-global-shortcut
Autostart tauri-plugin-autostart
Testing Vitest + @testing-library/react (frontend), cargo test (backend)

Architecture

Clipboard Polling

A dedicated Rust thread polls the system clipboard every 500ms using the arboard crate. Each new clipboard value is hashed (SHA-256) and compared against the last known hash to avoid duplicates. Text, images (stored as base64 PNG data URIs), and file paths are all captured.

Paste & Return

When you select an entry, maCopy writes it to the system clipboard, hides its window, waits 250ms for macOS to refocus the previous app, then simulates Cmd+V via AppleScript. This requires Accessibility permission.

SQLite + FTS5

The database uses a content-synced FTS5 virtual table with triggers that automatically keep the full-text index in sync with the clipboard_entries table. This enables instant prefix search as you type.

Window Behavior

The window uses titleBarStyle: "overlay" for native resize handles while keeping the frameless aesthetic. It's always-on-top and hides on blur. Position is determined by the user's setting (near cursor via CoreGraphics, center, or a screen corner).

Data Storage

The SQLite database is stored at:

~/Library/Application Support/maCopy/clipboard.db

Testing

Frontend (47 tests)

npm test

Tests cover App integration, SearchBar, ClipboardList (including multi-select), ContextMenu, and SettingsPanel. Tauri APIs are mocked in src/test/setup.ts.

Backend (27 tests)

npm run test:rust

Tests use in-memory SQLite databases and cover: CRUD operations, FTS5 search, auto-trim with pin preservation, settings persistence, SHA-256 hashing, and PNG encoding.

All tests

npm run test:all     # 74 total tests
npm run check        # lint + all tests

Building for Release

npm run tauri build

The built .app bundle will be in src-tauri/target/release/bundle/macos/.

License

MIT

Description
No description provided
Readme MIT 351 KiB
Languages
TypeScript 52.3%
Rust 45.6%
CSS 1.8%
HTML 0.3%