diff --git a/bin/omarchy-chromium-copy-url-host b/bin/omarchy-chromium-copy-url-host new file mode 100755 index 00000000..d097ee26 --- /dev/null +++ b/bin/omarchy-chromium-copy-url-host @@ -0,0 +1,50 @@ +#!/bin/bash + +# omarchy:summary=Native messaging host: copy a Chromium tab URL to the clipboard +# omarchy:hidden=true + +set -euo pipefail + +SCRIPT_PATH="${BASH_SOURCE[0]}" +export OMARCHY_PATH="${OMARCHY_PATH:-$(cd -- "$(dirname -- "$SCRIPT_PATH")/.." && pwd)}" +export PATH="$OMARCHY_PATH/bin:/usr/local/bin:/usr/bin:$PATH" + +parse_url() { + jq -r '.url // empty' 2>/dev/null <<<"$1" || true +} + +copy_url() { + local url="$1" + + [[ -n $url ]] || return 1 + printf '%s' "$url" | wl-copy --type text/plain + omarchy-notification-send -g "URL copied to clipboard" +} + +reply_copied() { + if [[ $1 == "true" ]]; then + printf '\x0f\x00\x00\x00{"copied":true}' + else + printf '\x10\x00\x00\x00{"copied":false}' + fi +} + +main() { + local length payload url + + length=$(head -c4 | od -An -v -tu4 --endian=little | tr -d ' ') + [[ -n ${length:-} ]] && (( length > 0 )) || exit 0 + + payload=$(head -c "$length") + url=$(parse_url "$payload") + + if copy_url "$url"; then + reply_copied true + else + reply_copied false + fi +} + +if [[ ${BASH_SOURCE[0]} == "$0" ]]; then + main "$@" +fi diff --git a/bin/omarchy-install-browser b/bin/omarchy-install-browser index 66ed2f54..90fc4f00 100755 --- a/bin/omarchy-install-browser +++ b/bin/omarchy-install-browser @@ -17,6 +17,7 @@ announce_browser_installed() { copy_chromium_flags() { mkdir -p ~/.config cp -f "$OMARCHY_PATH/config/chromium-flags.conf" "$1" + omarchy-install-chromium-copy-url omarchy-install-chromium-ytdlp } diff --git a/bin/omarchy-install-chromium-copy-url b/bin/omarchy-install-chromium-copy-url new file mode 100755 index 00000000..4f6dae02 --- /dev/null +++ b/bin/omarchy-install-chromium-copy-url @@ -0,0 +1,28 @@ +#!/bin/bash + +# omarchy:summary=Install the native messaging host for the Copy URL Chromium extension + +set -euo pipefail + +HOST_NAME="com.omarchy.copy_url" +HOST_PATH="$OMARCHY_PATH/bin/omarchy-chromium-copy-url-host" +TEMPLATE="$OMARCHY_PATH/default/chromium/native-messaging-hosts/$HOST_NAME.json" + +browser_dirs=( + "$HOME/.config/chromium" + "$HOME/.config/google-chrome" + "$HOME/.config/google-chrome-beta" + "$HOME/.config/google-chrome-unstable" + "$HOME/.config/BraveSoftware/Brave-Browser" + "$HOME/.config/BraveSoftware/Brave-Browser-Beta" + "$HOME/.config/BraveSoftware/Brave-Browser-Nightly" + "$HOME/.config/microsoft-edge" + "$HOME/.config/microsoft-edge-dev" +) + +manifest=$(sed "s|__HOST_PATH__|$HOST_PATH|g" "$TEMPLATE") + +for dir in "${browser_dirs[@]}"; do + mkdir -p "$dir/NativeMessagingHosts" + printf '%s\n' "$manifest" >"$dir/NativeMessagingHosts/$HOST_NAME.json" +done diff --git a/bin/omarchy-refresh-chromium b/bin/omarchy-refresh-chromium index 77165837..368373f4 100755 --- a/bin/omarchy-refresh-chromium +++ b/bin/omarchy-refresh-chromium @@ -15,7 +15,8 @@ fi # Refresh the Chromium configuration omarchy-refresh-config chromium-flags.conf -# Install/refresh the native messaging host used by the yt-dlp extension +# Install/refresh the native messaging hosts used by the bundled extensions +omarchy-install-chromium-copy-url omarchy-install-chromium-ytdlp # Re-install Google accounts if previously configured diff --git a/default/chromium/extensions/copy-url/background-2.js b/default/chromium/extensions/copy-url/background-2.js deleted file mode 100644 index 822236a8..00000000 --- a/default/chromium/extensions/copy-url/background-2.js +++ /dev/null @@ -1,65 +0,0 @@ -// NOTE: this file is intentionally version-numbered (background-2.js). Chromium -// caches the service worker for a command-line --load-extension and does NOT -// re-register it when the script changes in place, so an updated worker never -// takes effect on existing installs. Changing the filename is a new script URL, -// which forces a fresh registration. If you change this worker's code, rename it -// (background-3.js, ...) and update manifest.json's background.service_worker. - -let creatingOffscreenDocument; - -async function ensureOffscreenDocument() { - if (await chrome.offscreen.hasDocument()) return; - - if (!creatingOffscreenDocument) { - creatingOffscreenDocument = chrome.offscreen.createDocument({ - url: 'offscreen.html', - reasons: ['CLIPBOARD'], - justification: 'Copy the active tab URL to the clipboard' - }).finally(() => { - creatingOffscreenDocument = undefined; - }); - } - - await creatingOffscreenDocument; -} - -async function copyUrl(url) { - if (!url) return; - - try { - await ensureOffscreenDocument(); - const copied = await chrome.runtime.sendMessage({ - target: 'offscreen', - type: 'copy-url', - url - }); - - if (!copied) return; - - // The omarchy notification shell renders a chromium toast slim (no icon - // slot) when the summary begins with a glyph followed by 2+ spaces and the - // body is empty. iconUrl is a required field, so keep a 1x1 transparent png. - chrome.notifications.create({ - type: 'basic', - iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUAAXpeqz8AAAAASUVORK5CYII=', - title: ' URL copied to clipboard', - message: '' - }); - } catch (error) { - console.error('[copy-url] failed:', error); - } -} - -// Keyboard shortcut (Alt+Shift+L). -chrome.commands.onCommand.addListener((command) => { - if (command !== 'copy-url') return; - - chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { - copyUrl(tabs[0] && tabs[0].url); - }); -}); - -// Clicking the extension's toolbar icon. -chrome.action.onClicked.addListener((tab) => { - copyUrl(tab && tab.url); -}); diff --git a/default/chromium/extensions/copy-url/background-4.js b/default/chromium/extensions/copy-url/background-4.js new file mode 100644 index 00000000..6fd30052 --- /dev/null +++ b/default/chromium/extensions/copy-url/background-4.js @@ -0,0 +1,23 @@ +// Keep this filename versioned. Chromium caches service workers for extensions +// loaded via --load-extension, so a new URL forces registration of new code. + +function copyUrl(url) { + if (!url) return; + + // The native host owns both the Wayland clipboard and confirmation toast. + chrome.runtime.sendNativeMessage('com.omarchy.copy_url', { url }, () => { + void chrome.runtime.lastError; + }); +} + +chrome.commands.onCommand.addListener((command) => { + if (command !== 'copy-url') return; + + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + copyUrl(tabs[0] && tabs[0].url); + }); +}); + +chrome.action.onClicked.addListener((tab) => { + copyUrl(tab && tab.url); +}); diff --git a/default/chromium/extensions/copy-url/manifest.json b/default/chromium/extensions/copy-url/manifest.json index fac9b7ed..a0401e41 100644 --- a/default/chromium/extensions/copy-url/manifest.json +++ b/default/chromium/extensions/copy-url/manifest.json @@ -1,11 +1,11 @@ { "manifest_version": 3, "name": "Copy URL", - "version": "1.3", + "version": "1.5", "description": "Copy current URL to clipboard, this extension is installed by Omarchy", "action": { "default_title": "Copy URL" }, "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxbOMKgGcG20uelP6lx5OnuafGP9gNR1ajzURuQ45tv/Is5sc1tn9ii9PsKxpzDzPL9Z5lAqrTFgEcKHyTJnZXAGOMIruB0odJ0BW6eoQ3rxaQ/fBK2yGN7FZAfygPOWHj+Fdrh2eYQ7Ap92Dmlz8x0ceGyais415KmeevVcPwS7jjHFd1bGBp17cEMOBQcOx/3zCnko1hGtCzSdyZQl3cNMebW/FGvmxlPUMLVfWhysfFUuzxMRRf58+/j0mo/9iKQ0uTuxbQ8+W64OmYOVR87IXYNgbo+hEtORjiQhVg1KzaBx1bZ6clQMv96W/xYy1cIzUA5yI9y7lNr7n9zKijwIDAQAB", - "permissions": ["activeTab", "clipboardWrite", "notifications", "offscreen"], + "permissions": ["activeTab", "nativeMessaging"], "icons": { "16": "icon.png", "48": "icon.png", @@ -17,5 +17,5 @@ "description": "Copy URL" } }, - "background": {"service_worker": "background-2.js"} + "background": {"service_worker": "background-4.js"} } diff --git a/default/chromium/extensions/copy-url/offscreen.html b/default/chromium/extensions/copy-url/offscreen.html deleted file mode 100644 index 973a56f6..00000000 --- a/default/chromium/extensions/copy-url/offscreen.html +++ /dev/null @@ -1,9 +0,0 @@ - - -
- - - - - - diff --git a/default/chromium/extensions/copy-url/offscreen.js b/default/chromium/extensions/copy-url/offscreen.js deleted file mode 100644 index a39d1171..00000000 --- a/default/chromium/extensions/copy-url/offscreen.js +++ /dev/null @@ -1,17 +0,0 @@ -chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { - if (message.target !== 'offscreen' || message.type !== 'copy-url') return; - - // Offscreen documents are never focused, so navigator.clipboard.writeText - // rejects with "Document is not focused". execCommand('copy') has no focus - // requirement and is authorized by the clipboardWrite permission. - try { - const target = document.getElementById('target'); - target.value = message.url; - target.focus(); - target.select(); - sendResponse(document.execCommand('copy')); - } catch (error) { - console.error('[copy-url offscreen] error:', error); - sendResponse(false); - } -}); diff --git a/default/chromium/native-messaging-hosts/com.omarchy.copy_url.json b/default/chromium/native-messaging-hosts/com.omarchy.copy_url.json new file mode 100644 index 00000000..fbce4882 --- /dev/null +++ b/default/chromium/native-messaging-hosts/com.omarchy.copy_url.json @@ -0,0 +1,9 @@ +{ + "name": "com.omarchy.copy_url", + "description": "Omarchy Copy URL clipboard host", + "path": "__HOST_PATH__", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://bgpiichlckmfanooecilcjemknkcpngb/" + ] +} diff --git a/migrations/1784763917.sh b/migrations/1784763917.sh new file mode 100644 index 00000000..48fb1690 --- /dev/null +++ b/migrations/1784763917.sh @@ -0,0 +1,3 @@ +echo "Install the native messaging host for the Chromium Copy URL extension" + +omarchy-install-chromium-copy-url diff --git a/test/shell.d/chromium-copy-url-test.sh b/test/shell.d/chromium-copy-url-test.sh index b7398cce..4b3fcbf0 100644 --- a/test/shell.d/chromium-copy-url-test.sh +++ b/test/shell.d/chromium-copy-url-test.sh @@ -38,15 +38,17 @@ pass "copy-url extension manifest has the stable id" jq -e ' .manifest_version == 3 and - (.permissions | index("clipboardWrite")) and - (.permissions | index("offscreen")) and - (.background.service_worker | startswith("background-")) + (.permissions | index("nativeMessaging")) and + (.permissions | index("notifications") | not) and + (.permissions | index("clipboardWrite") | not) and + (.permissions | index("offscreen") | not) and + .background.service_worker == "background-4.js" ' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null || - fail "copy-url extension uses an offscreen clipboard document" -[[ -f $ROOT/default/chromium/extensions/copy-url/offscreen.html && - -f $ROOT/default/chromium/extensions/copy-url/offscreen.js ]] || - fail "copy-url extension ships its offscreen clipboard document" -pass "copy-url extension uses an offscreen clipboard document" + fail "copy-url extension uses its native messaging host" +grep -q "sendNativeMessage('com.omarchy.copy_url'" \ + "$ROOT/default/chromium/extensions/copy-url/background-4.js" || + fail "copy-url extension sends URLs to its native messaging host" +pass "copy-url extension uses its native messaging host" jq -e '.action != null' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null && grep -q 'action.onClicked' "$ROOT/default/chromium/extensions/copy-url/"background-*.js || @@ -54,6 +56,39 @@ jq -e '.action != null' "$ROOT/default/chromium/extensions/copy-url/manifest.jso pass "copy-url extension is clickable from the toolbar" TMPDIR=$(mktemp -d) +test_home="$TMPDIR/home" +native_manifest="$test_home/.config/chromium/NativeMessagingHosts/com.omarchy.copy_url.json" + +HOME="$test_home" OMARCHY_PATH="$ROOT" omarchy-install-chromium-copy-url + +[[ -f $native_manifest ]] || fail "copy-url native host installer creates fresh Chromium profile root" +jq -e --arg path "$ROOT/bin/omarchy-chromium-copy-url-host" ' + .name == "com.omarchy.copy_url" and + .path == $path and + (.allowed_origins | index("chrome-extension://bgpiichlckmfanooecilcjemknkcpngb/")) +' "$native_manifest" >/dev/null || fail "copy-url native host manifest uses Omarchy host path and extension id" +pass "copy-url native host installer registers the stable extension id" + +copied_url=$(bash -c ' + source "$1" + wl-copy() { cat; } + omarchy-notification-send() { :; } + copy_url "$2" +' bash "$ROOT/bin/omarchy-chromium-copy-url-host" 'https://example.test/path?q=one&name=two') + +[[ $copied_url == "https://example.test/path?q=one&name=two" ]] || + fail "copy-url native host writes the complete URL" "$copied_url" +pass "copy-url native host writes the complete URL" + +native_reply=$(bash -c ' + source "$1" + reply_copied true +' bash "$ROOT/bin/omarchy-chromium-copy-url-host" | od -An -v -tx1 | tr -d ' \n') + +[[ $native_reply == "0f0000007b22636f70696564223a747275657d" ]] || + fail "copy-url native host returns a framed success response" "$native_reply" +pass "copy-url native host returns a framed success response" + preferences="$TMPDIR/Preferences" backup="$TMPDIR/Preferences.bak" patch_script="$TMPDIR/repair-shortcuts.py"