Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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…". |
Expand Down Expand Up @@ -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.

Expand Down
6 changes: 2 additions & 4 deletions app/src-tauri/src/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@ fn remember<R: Runtime>(app: &AppHandle<R>, 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] = &[
Expand Down
46 changes: 39 additions & 7 deletions app/src-tauri/src/tray.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,50 @@ pub fn surface_at<R: Runtime>(app: &AppHandle<R>, 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<R: Runtime>(window: &tauri::WebviewWindow<R>) {
let _ = window.with_webview(|webview| {
if let Some(ns_window) = std::ptr::NonNull::new(webview.ns_window()) {
cp_mac_sys::titlebar::without_zoom(ns_window);
}
});
}

#[cfg(not(target_os = "macos"))]
fn unzoomed<R: Runtime>(_window: &tauri::WebviewWindow<R>) {}

#[cfg(target_os = "macos")]
fn framed<'a, R: Runtime, M: Manager<R>>(
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<R>>(
builder: WebviewWindowBuilder<'a, R, M>,
) -> WebviewWindowBuilder<'a, R, M> {
builder.decorations(false)
}

fn ahead<R: Runtime>(window: &tauri::WebviewWindow<R>) {
let was = window.is_always_on_top().unwrap_or(false);
let _ = window.set_always_on_top(true);
Expand Down
50 changes: 2 additions & 48 deletions app/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion app/src/ui/Chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className={`chrome${mac ? " mac" : ""}${knobs ? "" : " bare"}`} data-tauri-drag-region>
<span className="chrome-who" data-tauri-drag-region>
CopyPaste
</span>
{knobs && (
{drawn && (
<div className="chrome-does">
<button
type="button"
Expand Down
10 changes: 7 additions & 3 deletions crates/cp-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ use std::sync::atomic::{AtomicU64, Ordering};

pub const FILE: &str = "config.toml";

#[cfg(target_os = "macos")]
pub const SHORTCUT: &str = "Cmd+Alt+V";
#[cfg(not(target_os = "macos"))]
pub const SHORTCUT: &str = "Ctrl+Alt+V";

#[cfg(target_os = "macos")]
const FORMER_SHORTCUT: &str = "Cmd+Alt+V";

pub const KEEPS_DAYS: u16 = 30;

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -90,6 +90,10 @@ impl Config {
self.shortcut = SHORTCUT.to_owned();
}
self.shortcut = self.shortcut.trim().to_owned();
#[cfg(target_os = "macos")]
if self.shortcut == FORMER_SHORTCUT {
self.shortcut = SHORTCUT.to_owned();
}
if self.keeps_days == Some(0) {
self.keeps_days = None;
}
Expand Down
20 changes: 20 additions & 0 deletions crates/cp-config/src/lib_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,23 @@ fn a_blank_welcome_is_no_welcome() {
.sane();
assert_eq!(kept.welcomed.as_deref(), Some("3.0.1"));
}

#[cfg(target_os = "macos")]
#[test]
fn the_old_mac_default_moves_to_the_one_handed_one_when_read() {
let dir = a_dir();
let path = at(dir.path());
std::fs::write(&path, "shortcut = \"Cmd+Alt+V\"\n").expect("writes");
assert_eq!(read(&path).expect("reads").shortcut, SHORTCUT);
}

#[cfg(target_os = "macos")]
#[test]
fn a_command_option_v_picked_by_hand_is_left_alone() {
let asked = Config {
shortcut: "Alt+Cmd+V".into(),
..Config::default()
}
.sane();
assert_eq!(asked.shortcut, "Alt+Cmd+V");
}
3 changes: 2 additions & 1 deletion crates/cp-mac-sys/src/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ pub fn as_accessory() -> bool {

pub fn is_ours_up_front() -> Option<bool> {
let mtm = MainThreadMarker::new()?;
Some(NSApplication::sharedApplication(mtm).isActive())
let app = NSApplication::sharedApplication(mtm);
Some(app.isActive() || app.keyWindow().is_some())
}

pub fn is_accessory() -> Option<bool> {
Expand Down
Loading
Loading