Skip to content

macOS port: full parity third platform (Windows/Linux/macOS from one codebase)#2

Merged
alexj2k0 merged 14 commits into
mainfrom
docs/macos-port-plan
Jul 4, 2026
Merged

macOS port: full parity third platform (Windows/Linux/macOS from one codebase)#2
alexj2k0 merged 14 commits into
mainfrom
docs/macos-port-plan

Conversation

@alexj2k0

@alexj2k0 alexj2k0 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Brings the complete macOS port into main, finishing the port plan's core idea: one repo, one branch, no fork — macOS as a third #[cfg] arm alongside Windows and Linux, with every bare not(windows) gate that touched platform-specific behavior split into explicit linux/macos gates.

What's in here (phases 1–4 + parity pass)

  • Compile & launch: GTK/layer-shell deps and code re-gated to Linux; macOSPrivateApi for window transparency.
  • UX parity: Cmd+Shift+Space default hotkey, HudWindow vibrancy + rounded corners, Accessory activation policy (no Dock icon), tray with a monochrome >_ template glyph, cursor-monitor positioning, native NSWindow.alphaValue transparency.
  • Feature backends: app launcher (bundle scan + open), screenshot (screencapture -R of the cursor monitor, overlay at screen-saver level), paste-to-previous (NSWorkspace capture → reactivate → ⌘V CGEvents, with a pointed Accessibility error), volume (osascript), system actions (pmset / System Events), processes (sysinfo + SIGKILL), file/app icons (NSWorkspace.iconForFile:), .command scripts, system stats (sysinfo CPU/mem).
  • Packaging & sync hygiene: cross-platform release script (.exe / binary / .app), .icns + CFBundleURLTypes deep links, CI matrix (this PR is its first run), CLAUDE.md platform docs.
  • Parity pass against the Linux-parity merge: @search resolves the focused Finder folder via AppleScript (home-folder fallback otherwise), clipboard history encrypted at rest (ChaCha20-Poly1305, 0600 key file — see crypto.rs for why not Keychain: ad-hoc signatures re-prompt every rebuild and block launch), Empty Trash wording, Hibernate hidden on macOS.

Windows and Linux arms were deliberately left untouched throughout; the diff to their behavior should be zero. CI on this PR is the cross-platform proof — verified green on macOS locally (cargo test 18/18, live app: palette toggle, clipboard migration re-encrypting real rows, Finder @search fallback under a pending Automation prompt).

Remaining on-device-only items (permission grants that can't be scripted) are tracked in docs/macos-port.md.

🤖 Generated with Claude Code

alexj2k0 and others added 14 commits July 4, 2026 02:50
Plan for adding macOS as a third supported target alongside Windows and
Linux from the same codebase: split the `not(windows)` cfg gates into
explicit linux/macos arms (GTK is Linux-only), stand up a native window
layer, and fill in Mac feature backends. Phased checklist in
docs/macos-port.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add macOS as a third supported target from the same codebase:

- Cargo.toml: regate the gtk / gtk-layer-shell deps from cfg(not(windows))
  to cfg(target_os = "linux") so they aren't pulled in on macOS (which is
  also "not windows"). tauri-build auto-added the macos-private-api feature.
- lib.rs: move every gtk/layer-shell/COSMIC cfg(not(windows)) block to
  cfg(target_os = "linux"); resize_palette's no-op arm is now
  cfg(not(target_os = "linux")) so Windows and macOS both keep params used.
- tauri.conf.json: enable app.macOSPrivateApi so the transparent palette
  window actually renders transparent on macOS.

No frontend changes: IS_LINUX (userAgent) is false on macOS, so the palette
already uses the native setSize path. cargo build is clean and the app
launches without panic (file index scans, transparent window confirmed).

Feature backends (launcher, screenshot, audio, system, process, paste),
tray, window positioning, and packaging remain — see docs/macos-port.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core UX parity for macOS:

- shortcuts.rs: default global hotkey is Cmd+Shift+Space on macOS
  (Ctrl+Space is the input-source switcher, Cmd+Space is Spotlight).
- Cargo.toml + lib.rs: apply window-vibrancy (NSVisualEffectMaterial
  HudWindow + 12px radius) to the palette in a cfg(target_os = "macos")
  setup arm; best-effort.
- lib.rs: set_activation_policy(Accessory) so the app is a background
  agent (no Dock icon / Cmd-Tab entry), like Raycast/Spotlight.
- lib.rs: enable the tray icon on macOS (setup_tray gate widened to
  windows|macos) so there's an entry point + Quit without a Dock icon.

Builds clean; app launches, toggles the palette via single-instance, and
survives show/hide with no panic. Deferred to 2b: tauri-nspanel
(non-activating panel), cursor-monitor positioning, macOS
set_window_transparency fallback. See docs/macos-port.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lback

- lib.rs: add a macOS position_on_cursor_monitor built on Tauri's
  cross-platform cursor_position/monitor_from_point/work_area APIs (the
  Win32 path is untouched, so no Windows regression). The palette now
  opens centered on the display under the cursor, top at ~20% of the work
  area. Both show paths (toggle/show) call it on windows|macos.
- src/lib/tauri.ts: route macOS through the Linux-style CSS-opacity path in
  setWindowTransparency instead of the Windows-only native invoke (which
  errored on mac). The transparency setting now works on macOS.

Builds clean (cargo + tsc); launches and toggles twice with no panic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ResultsList: replace scrollIntoView({block:'nearest'}) with deterministic
  scrollTop math. WKWebView (macOS) recenters on 'nearest', making the
  selection jump to the middle when paging past the bottom of a long list;
  the manual computation behaves identically on Chromium (Windows/Linux)
  and WebKit (macOS) and only scrolls the minimum needed.
- Palette: add borderRadius:12 to the container on macOS/Linux (Windows
  rounds the native frame via DWM). Matches the 12px native vibrancy radius
  on macOS, so the corners are actually rounded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CSS-opacity fallback faded the webview onto the opaque native vibrancy
layer, so the transparency slider produced a blurred-wallpaper patch rather
than genuine see-through. Implement set_window_transparency natively on
macOS: set NSWindow.alphaValue (via objc2 msg_send on the main thread), the
direct analogue of the Windows LWA_ALPHA path. This makes the whole window
(vibrancy + content) translucent, revealing the desktop behind.

Frontend: macOS now uses the native invoke path again (only Linux keeps the
CSS-opacity fallback). Adds objc2 as a macOS dep, pinned to window-vibrancy's
0.5 so only one copy builds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sparency)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every stubbed/Linux-only backend now has a macOS arm:

- launcher: scan ~/Applications, /Applications, /System/Applications for
  .app bundles (no descent into bundles; user apps shadow system ones);
  launch via `open` (LaunchServices activates running instances)
- screenshot: `screencapture -x -R<cursor monitor rect>` (points from
  Tauri monitor APIs; PNG arrives at native Retina pixels). Overlay reuses
  the Windows physical positioning; its NSWindow is raised to screen-saver
  level in setup so it covers the menu bar without constrainFrameRect
  clamping, and click-away cancel now applies on macOS. Clipboard copy
  uses the arboard arm (wl-copy stays Linux-only)
- paste to previous: NSWorkspace frontmost-app pid captured on palette
  show; paste reactivates it (main thread) then posts ⌘V CGEvents at the
  HID tap, with an actionable error when Accessibility isn't granted
- audio: osascript get/set volume + mute on the default output, exposed
  as a single "System Output" pseudo-device
- system: pmset lock/sleep; graceful shutdown/restart/logout via System
  Events; Finder empty-trash; Hibernate errors as not-a-macOS-concept
- process: sysinfo list + libc SIGKILL (new macOS-only deps, with
  core-graphics for CGEvent)
- scripts: .command runs via `open` (Terminal); fallback opener is `open`
  instead of xdg-open; gio/.desktop gated to Linux

Adds macOS smoke tests (app scan, volume round-trip, process list) —
cargo test green. crypto.rs verified portable (plaintext passthrough,
nothing Linux-specific). tauri-nspanel deliberately not adopted: explicit
reactivation makes it unnecessary for paste.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
WKWebView re-fires mouseenter/mousemove when rows re-rank under a
stationary cursor, so every keystroke (which resets selection to the top)
let the row under the idle mouse yank the selection back — typically to
the middle of the list, since the palette opens near the cursor. Chromium
doesn't synthesize these events, which is why Windows behaved.

Apply the movement guard ResultsGrid already had (ignore events whose
coordinates match the last ones) as a container-level mousemove handler in
ResultsList and ActionPanel, and remove the per-row/per-cell onMouseEnter
handlers that bypassed it. ResultRow's separate hover tint went with it:
real hovers now select the row instantly, so the tint only ever showed on
synthetic events.

Also extract the Phase 2 minimal-scroll fix into lib/scroll.ts and apply
it to ActionPanel and ResultsGrid, which still used
scrollIntoView({block:'nearest'}) and had the same WKWebView recentering
jump when paging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add macOS .app bundle release via scripts/release.cjs (cross-platform).
- Add src-tauri/icons/icon.icns and CFBundleURLTypes for commandeer:// deep links.
- Implement macOS file-search icons via NSWorkspace.iconForFile:.
- Implement macOS system stats (CPU/RAM) using sysinfo.
- Enable user-configured screenshot hotkey on macOS (no default to avoid conflicts).
- Clean up platform cfg gates in fs.rs (explicit linux/macos instead of not(windows)).
- Add CI matrix (ubuntu/windows/macos) for frontend type-check and Rust build/tests.
- Update CLAUDE.md and docs/macos-port.md with macOS status and dev notes.
Reconciles the overnight Linux-parity push with the macOS port:

- Keep macOS backends (screencapture capture, ⌘V paste, sysinfo process
  list, cursor-monitor overlay positioning) alongside the new Linux
  code (four-CLI screenshot fallback chain, X11 support, /proc process
  list, SIGTERM→SIGKILL, wl-copy/xclip + arboard clipboard fallback).
- Re-gate upstream's #[cfg(not(windows))] modules to target_os="linux"
  where they pull in gtk/gio or shell out to Linux tools (desktop,
  linux_icons, system::linux, audio::linux, capture_screen_to,
  desktop_dir_entries) so the macOS build doesn't compile them.
- paste_to_previous: adopt the new Result<bool> "was the keystroke
  delivered" contract; macOS returns true after ⌘V.
- setup_tray: non-fatal on macOS/Linux per upstream; palette CSS
  radius merged (macOS 12px to match vibrancy, Linux 8px).
- Keep scripts/release.cjs for npm run release (handles the .app
  bundle) over upstream's inline bun script; merge CLAUDE.md macOS
  notes with the new Shipping changes section and architecture text.

Verified on macOS: tsc + vite build clean, cargo check clean (one
pre-existing warning), 16/16 cargo tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…System fixes, template tray icon

Gaps found comparing the finished port against the Windows/Linux arms:

- @search was broken on macOS (capture_location was Windows-only and the
  home fallback Linux-only, so it always threw). When the palette opens
  over Finder the front window's folder is resolved via AppleScript on a
  worker thread (only queried when Finder was frontmost, so the one-time
  Automation prompt can't fire over unrelated apps); otherwise @search
  falls back to the home folder like Linux.
- Clipboard history was plaintext at rest on macOS while Windows (DPAPI)
  and Linux (ChaCha20 + Secret Service) encrypted. The ChaCha20-Poly1305
  arm now covers macOS with a 0600 key file next to the db, plus the same
  one-time plaintext re-encryption migration (verified live: pre-existing
  rows re-encrypted on startup, new copies encrypted as captured).
  Keychain deliberately NOT used: its ACLs bind to the code signature, so
  an ad-hoc-signed binary re-prompts on every rebuild — and the prompt
  fires inside setup and blocks launch (observed via process sample stuck
  in SecKeychainFindGenericPassword).
- System folder: "Empty Recycle Bin" is now "Empty Trash" on macOS, and
  Hibernate (backend rejects it as not a macOS concept) is not listed.
- Tray uses a monochrome >_ template glyph on macOS (18pt @2x, alpha-only,
  icon_as_template) instead of the colored app icon, which macOS would
  render as an opaque square; Windows/Linux keep the app icon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_bf870673
First real run of the Phase 4 workflow: glib-sys failed on a bare
ubuntu-latest because Tauri links system GTK/WebKit. Also pull in
gtk-layer-shell (palette overlay surface) and D-Bus headers
(keyring/Secret Service).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_bf870673
@alexj2k0
alexj2k0 merged commit fda98ce into main Jul 4, 2026
3 of 4 checks passed
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