lan-mouse/src
Jon Kinney 55bcf00825 feat(clipboard): macOS app-suppression — concealed pasteboard, per-OS sections, running-app picker
Closes the macOS gap left by 9269ce6 ("Phase 4: Linux + Windows
only") and rebuilds the suppression-list UX around what macOS
actually exposes vs hides to a non-Cocoa LSUIElement child.

Concealed-pasteboard auto-suppression (`input-capture`)
- Wire `objc2` + `objc2-app-kit` (NSWorkspace / NSPasteboard /
  NSImage / NSBitmapImageRep / NSRunningApplication) and
  `objc2-foundation` (NSString / NSData / NSDictionary / NSURL).
- `clipboard.rs::is_concealed_clipboard` checks the general
  pasteboard's `types` array for `org.nspasteboard.ConcealedType`
  — the nspasteboard.org convention password managers use to opt
  out of clipboard-manager capture. Honored before the user list
  so 1Password etc. just work without a manual entry.
- `frontmost_app::macos::frontmost_app` now resolves via
  `NSWorkspace.frontmostApplication.bundleIdentifier`, replacing
  the Phase-4 stub. Doc updates in CLIPBOARD_PLAN.md mark the
  macOS TODOs done.

Per-OS data model (`lan-mouse-ipc`, `src/config.rs`, `src/service.rs`)
- `ClipboardSuppression { macos, windows, linux_wayland, linux_x11:
  Vec<String> }` replaces the flat `Vec<AppIdent>`. Each host
  reads/writes only its own slot via `host()` / `host_mut()`; the
  other slots round-trip untouched, so a config synced across
  machines (dotfiles, Syncthing) keeps each machine's list intact.
- `HostKind::current()` picks `MacBundle` / `WindowsExe` /
  `LinuxWayland` / `LinuxX11` (Wayland-vs-X11 decided at runtime
  via `WAYLAND_DISPLAY`). `make_ident(value)` wraps a host string
  in the matching `AppIdent` variant for the runtime suppression
  check.
- `FrontendRequest::AddSuppressedApp(String)` /
  `RemoveSuppressedApp(String)` and
  `FrontendEvent::SuppressedAppsUpdated(Vec<String>)` now carry
  plain identifier strings; the kind is implicit from the host
  OS. Service rebuilds the runtime `HashSet<AppIdent>` shared
  with `ClipboardMonitor` whenever the host slot changes.

Running-app picker with icons (`lan-mouse-gtk` ↔ `input-capture`)
- New `RunningApp { display_name, identifier, icon_png:
  Option<Vec<u8>> }` IPC type. `FrontendEvent::RunningApps(Vec<
  RunningApp>)` carries the picker payload.
- `frontmost_app::macos::list_running_apps` shells out to
  `osascript` → System Events for `every process where background
  only is false`. Three direct AppKit APIs (NSWorkspace
  .runningApplications, NSRunningApplication
  .runningApplicationWithProcessIdentifier, CGWindowListCopyWindow
  Info) all silently scope to the caller's loginwindow / Aqua
  session and return only ~3 entries from a non-Cocoa GTK process
  — System Events is itself fully session-attached so it returns
  the real list. Apple Events permission is already declared via
  `NSAppleEventsUsageDescription` (we use it for input emulation).
- Icons via `NSWorkspace.iconForFile:` (path-based, session-
  independent), encoded to PNG by picking the closest-but-no-
  smaller-than-64 px rep. Per-bundle-id icon cache amortizes the
  5-second auto-refresh.
- New `frontmost_app::lookup_app_metadata(identifier)` resolves a
  bundle ID to display-name + icon via Launch Services
  (`URLForApplicationWithBundleIdentifier`) so the suppressed-
  apps list renders 1Password's name + icon even when 1Password
  isn't currently running.
- `lan-mouse-gtk` gains a direct `input-capture` dep (default
  features off) and bumps `gtk4` to `v4_6` for `Texture::from_
  bytes`. Picker enumeration runs in the GUI process — the daemon
  child can't see other apps (same Aqua-session restriction).

GTK rewrite (`clipboard_privacy_window`, `window`)
- `AdwComboRow` with a custom `SignalListItemFactory` renders
  Image + Label per row at a fixed 320 px min-width so the
  popover doesn't shrink horizontally as the user types into
  search. `RunningAppObject` GObject carries display_name +
  identifier + decoded `gdk::Texture`.
- Already-suppressed apps are filtered out of the picker so the
  user can't add a duplicate; selection is preserved across
  refreshes if the picked app is still present.
- The suppressed-apps list (above the picker) renders the same
  `Image + display_name` treatment instead of raw bundle IDs;
  metadata is mirrored from the running-apps cache and lazily
  filled via `lookup_app_metadata` for not-currently-running
  entries. Trash button uses `error` style (red) to match the
  authorize-key UI in `key_row.ui`.
- `Window::open_clipboard_privacy_window` calls
  `frontmost_app::list_running_apps()` directly on first open,
  then via a 5-second `glib::timeout_add_local` while the modal
  is visible. Refresh is skipped while the picker's popover is
  open so a search-in-progress isn't disrupted.
- Removing a suppressed entry now re-applies the picker filter
  against the cached running-apps snapshot immediately, so the
  removed app reappears as a candidate without waiting for the
  next 5 s tick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:38:01 -05:00
..
capture.rs feat(clipboard): vendor primitives + protocol from #327 2026-05-07 10:46:26 -05:00
capture_test.rs feat: simplify and change configuration (#279) 2025-03-15 18:45:19 +01:00
client.rs feat(clipboard): per-pair config + IPC + Service routing 2026-05-07 11:00:21 -05:00
config.rs feat(clipboard): macOS app-suppression — concealed pasteboard, per-OS sections, running-app picker 2026-05-07 14:38:01 -05:00
connect.rs feat(clipboard): per-pair config + IPC + Service routing 2026-05-07 11:00:21 -05:00
crypto.rs Encryption and One-Way-Control (#200) 2024-11-09 13:54:43 +01:00
discovery.rs fix(discovery,service): drop unused method, add MissedTickBehavior::Skip 2026-05-07 00:50:24 -05:00
dns.rs fix(dns): resolve hostnames via the OS resolver instead of pure DNS 2026-05-07 00:50:24 -05:00
emulation.rs feat(clipboard): per-pair config + IPC + Service routing 2026-05-07 11:00:21 -05:00
emulation_test.rs feat: simplify and change configuration (#279) 2025-03-15 18:45:19 +01:00
lib.rs feat(macos): TCC.db watcher with fresh-subprocess AX probe 2026-05-07 00:50:24 -05:00
listen.rs feat(clipboard): per-pair config + IPC + Service routing 2026-05-07 11:00:21 -05:00
macos_tcc_probe.rs feat(macos): TCC.db watcher with fresh-subprocess AX probe 2026-05-07 00:50:24 -05:00
macos_tcc_watch.rs feat(macos): TCC.db watcher with fresh-subprocess AX probe 2026-05-07 00:50:24 -05:00
main.rs feat(macos): TCC.db watcher with fresh-subprocess AX probe 2026-05-07 00:50:24 -05:00
service.rs feat(clipboard): macOS app-suppression — concealed pasteboard, per-OS sections, running-app picker 2026-05-07 14:38:01 -05:00