From 5075a84d18c324c1fc0597dec191e6d102cff80c Mon Sep 17 00:00:00 2001 From: rgdevment Date: Tue, 6 Oct 2026 02:31:10 -0300 Subject: [PATCH 1/3] fix(macos): panel takes typing and hides on leave, settings use native traffic lights, Ctrl+Alt+V by default --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- README.md | 4 +- app/src-tauri/src/keys.rs | 6 +-- app/src-tauri/src/tray.rs | 50 +++++++++++++++++++++---- app/src/index.css | 50 +------------------------ app/src/ui/Chrome.tsx | 3 +- crates/cp-config/src/lib.rs | 10 +++-- crates/cp-config/src/lib_test.rs | 20 ++++++++++ crates/cp-mac-sys/src/activation.rs | 3 +- crates/cp-mac-sys/src/floating.rs | 51 ++++++++++++++++++++++++++ crates/cp-mac-sys/src/floating_test.rs | 39 ++++++++++++++++++++ crates/cp-mac-sys/src/lib.rs | 2 + crates/cp-mac-sys/src/titlebar.rs | 20 ++++++++++ crates/cp-mac-sys/src/titlebar_test.rs | 39 ++++++++++++++++++++ crates/cp-panel/src/here.rs | 9 ++++- 15 files changed, 239 insertions(+), 69 deletions(-) create mode 100644 crates/cp-mac-sys/src/floating.rs create mode 100644 crates/cp-mac-sys/src/floating_test.rs create mode 100644 crates/cp-mac-sys/src/titlebar.rs create mode 100644 crates/cp-mac-sys/src/titlebar_test.rs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bf191b21..85c548c0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -69,7 +69,7 @@ body: description: "Name the application you copied from or pasted into: most clipboard bugs belong to one pair of them." placeholder: | 1. Copy an image from Firefox - 2. Open the panel with Ctrl+Alt+V (⌥⌘V on a Mac) + 2. Open the panel with Ctrl+Alt+V (⌃⌥V on a Mac) 3. Press Enter on the image 4. It pastes as text in Word validations: diff --git a/README.md b/README.md index 93227d50..c3f012e6 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ keyboard does not have are replaced by the ones it does. | Where | Windows | macOS | Action | | :---- | :------ | :---- | :----- | | App you're in | Ctrl+V | ⌘V | Normal paste. CopyPaste does not intercept it. | -| Anywhere | Ctrl+Alt+V | ⌥⌘V | Open the panel. Change it in Settings → Keyboard shortcuts; if another program already uses it, free alternatives are offered. | +| Anywhere | Ctrl+Alt+V | ⌃⌥V | Open the panel. Change it in Settings → Keyboard shortcuts; if another program already uses it, free alternatives are offered. | | Panel | Enter | ⏎ | Paste the selected item. | | Panel | Shift + Enter | ⇧⏎ | Paste as plain text. | | Panel | Alt + Enter · Ctrl + Enter | ⌥⏎ · ⌘⏎ | Open or close "Paste as…". | @@ -339,7 +339,7 @@ emptying the history. | **Windows** | Microsoft Store | Standalone `.exe` · winget (coming soon) | | **macOS** | Homebrew | Standalone `.dmg` | -After installing, open the panel with **Ctrl+Alt+V** on Windows or **⌥⌘V** on a +After installing, open the panel with **Ctrl+Alt+V** on Windows or **⌃⌥V** on a Mac. Both are customizable in Settings → Keyboard shortcuts, and the first run walks you through it. diff --git a/app/src-tauri/src/keys.rs b/app/src-tauri/src/keys.rs index 6ceca8f0..d091ded0 100644 --- a/app/src-tauri/src/keys.rs +++ b/app/src-tauri/src/keys.rs @@ -48,12 +48,10 @@ fn remember(app: &AppHandle, said: Option<&str>) { #[cfg(target_os = "macos")] pub const SPARE: &[&str] = &[ - "Alt+Cmd+V", + "Ctrl+Alt+V", "Shift+Cmd+V", - "Alt+Cmd+C", + "Ctrl+Shift+V", "Shift+Cmd+Space", - "Ctrl+Alt+V", - "Alt+Cmd+Space", ]; #[cfg(not(target_os = "macos"))] pub const SPARE: &[&str] = &[ diff --git a/app/src-tauri/src/tray.rs b/app/src-tauri/src/tray.rs index 0d78d550..4c8ac909 100644 --- a/app/src-tauri/src/tray.rs +++ b/app/src-tauri/src/tray.rs @@ -136,18 +136,54 @@ pub fn surface_at(app: &AppHandle, rail: Option<&str>) { let page = rail.map_or(WebviewUrl::default(), |rail| { WebviewUrl::App(format!("index.html#{rail}").into()) }); - let built = WebviewWindowBuilder::new(app, "main", page) - .title("CopyPaste") - .inner_size(780.0, 580.0) - .min_inner_size(620.0, 460.0) - .decorations(false) - .center() - .build(); + let built = framed( + WebviewWindowBuilder::new(app, "main", page) + .title("CopyPaste") + .inner_size(780.0, 580.0) + .min_inner_size(620.0, 460.0) + .center(), + ) + .build(); if let Ok(window) = built { + unzoomed(&window); ahead(&window); } } +#[cfg(target_os = "macos")] +fn unzoomed(window: &tauri::WebviewWindow) { + let Ok(ns_window) = window.ns_window() else { + return; + }; + let at = ns_window as usize; + let _ = window.run_on_main_thread(move || { + if let Some(ns_window) = std::ptr::NonNull::new(at as *mut std::ffi::c_void) { + cp_mac_sys::titlebar::without_zoom(ns_window); + } + }); +} + +#[cfg(not(target_os = "macos"))] +fn unzoomed(_window: &tauri::WebviewWindow) {} + +#[cfg(target_os = "macos")] +fn framed<'a, R: Runtime, M: Manager>( + builder: WebviewWindowBuilder<'a, R, M>, +) -> WebviewWindowBuilder<'a, R, M> { + builder + .title_bar_style(tauri::TitleBarStyle::Overlay) + .hidden_title(true) + .maximizable(false) + .traffic_light_position(tauri::LogicalPosition::new(14.0, 17.0)) +} + +#[cfg(not(target_os = "macos"))] +fn framed<'a, R: Runtime, M: Manager>( + builder: WebviewWindowBuilder<'a, R, M>, +) -> WebviewWindowBuilder<'a, R, M> { + builder.decorations(false) +} + fn ahead(window: &tauri::WebviewWindow) { let was = window.is_always_on_top().unwrap_or(false); let _ = window.set_always_on_top(true); diff --git a/app/src/index.css b/app/src/index.css index f84f681a..196ff7f6 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -157,61 +157,15 @@ body { } .chrome.mac { - padding-left: 0; + padding-left: 64px; padding-right: 13px; } -/* on a Mac the left inset comes from the knobs, so a window without them needs its own */ +/* on a Mac the left inset leaves room for the system's traffic lights, so a window without them needs its own */ .chrome.mac.bare { padding-left: 13px; } -.chrome.mac .chrome-does { - order: -1; - flex-direction: row-reverse; - align-items: center; - gap: 8px; - height: auto; - padding: 0 8px 0 13px; -} - -.chrome.mac .chrome-knob { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--hair); -} - -.chrome.mac .chrome-knob svg { - width: 8px; - height: 8px; - opacity: 0; - stroke: rgba(0, 0, 0, 0.55); - stroke-width: 1.4; -} - -.chrome.mac .chrome-knob.shut { - background: #ff5f57; -} - -.chrome.mac .chrome-knob:not(.shut) { - background: #febc2e; -} - -.chrome.mac .chrome-does:hover .chrome-knob svg { - opacity: 1; -} - -.chrome.mac .chrome-knob:hover, -.chrome.mac .chrome-knob.shut:hover { - background: inherit; -} - -.chrome.mac .chrome-knob:hover svg, -.chrome.mac .chrome-knob.shut:hover svg { - stroke: rgba(0, 0, 0, 0.55); -} - .shell { display: grid; grid-template-columns: 186px minmax(0, 1fr); diff --git a/app/src/ui/Chrome.tsx b/app/src/ui/Chrome.tsx index 32555b9d..1e052167 100644 --- a/app/src/ui/Chrome.tsx +++ b/app/src/ui/Chrome.tsx @@ -8,13 +8,14 @@ export default function Chrome({ knobs = true }: { knobs?: boolean }) { held.current ??= getCurrentWindow(); const win = held.current; const mac = onMac(); + const drawn = knobs && !mac; return (
CopyPaste - {knobs && ( + {drawn && (