Make Copy URL use native clipboard host
This commit is contained in:
Executable
+50
@@ -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
|
||||||
@@ -17,6 +17,7 @@ announce_browser_installed() {
|
|||||||
copy_chromium_flags() {
|
copy_chromium_flags() {
|
||||||
mkdir -p ~/.config
|
mkdir -p ~/.config
|
||||||
cp -f "$OMARCHY_PATH/config/chromium-flags.conf" "$1"
|
cp -f "$OMARCHY_PATH/config/chromium-flags.conf" "$1"
|
||||||
|
omarchy-install-chromium-copy-url
|
||||||
omarchy-install-chromium-ytdlp
|
omarchy-install-chromium-ytdlp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Executable
+28
@@ -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
|
||||||
@@ -15,7 +15,8 @@ fi
|
|||||||
# Refresh the Chromium configuration
|
# Refresh the Chromium configuration
|
||||||
omarchy-refresh-config chromium-flags.conf
|
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
|
omarchy-install-chromium-ytdlp
|
||||||
|
|
||||||
# Re-install Google accounts if previously configured
|
# Re-install Google accounts if previously configured
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Copy URL",
|
"name": "Copy URL",
|
||||||
"version": "1.3",
|
"version": "1.5",
|
||||||
"description": "Copy current URL to clipboard, this extension is installed by Omarchy",
|
"description": "Copy current URL to clipboard, this extension is installed by Omarchy",
|
||||||
"action": { "default_title": "Copy URL" },
|
"action": { "default_title": "Copy URL" },
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxbOMKgGcG20uelP6lx5OnuafGP9gNR1ajzURuQ45tv/Is5sc1tn9ii9PsKxpzDzPL9Z5lAqrTFgEcKHyTJnZXAGOMIruB0odJ0BW6eoQ3rxaQ/fBK2yGN7FZAfygPOWHj+Fdrh2eYQ7Ap92Dmlz8x0ceGyais415KmeevVcPwS7jjHFd1bGBp17cEMOBQcOx/3zCnko1hGtCzSdyZQl3cNMebW/FGvmxlPUMLVfWhysfFUuzxMRRf58+/j0mo/9iKQ0uTuxbQ8+W64OmYOVR87IXYNgbo+hEtORjiQhVg1KzaBx1bZ6clQMv96W/xYy1cIzUA5yI9y7lNr7n9zKijwIDAQAB",
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxbOMKgGcG20uelP6lx5OnuafGP9gNR1ajzURuQ45tv/Is5sc1tn9ii9PsKxpzDzPL9Z5lAqrTFgEcKHyTJnZXAGOMIruB0odJ0BW6eoQ3rxaQ/fBK2yGN7FZAfygPOWHj+Fdrh2eYQ7Ap92Dmlz8x0ceGyais415KmeevVcPwS7jjHFd1bGBp17cEMOBQcOx/3zCnko1hGtCzSdyZQl3cNMebW/FGvmxlPUMLVfWhysfFUuzxMRRf58+/j0mo/9iKQ0uTuxbQ8+W64OmYOVR87IXYNgbo+hEtORjiQhVg1KzaBx1bZ6clQMv96W/xYy1cIzUA5yI9y7lNr7n9zKijwIDAQAB",
|
||||||
"permissions": ["activeTab", "clipboardWrite", "notifications", "offscreen"],
|
"permissions": ["activeTab", "nativeMessaging"],
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icon.png",
|
"16": "icon.png",
|
||||||
"48": "icon.png",
|
"48": "icon.png",
|
||||||
@@ -17,5 +17,5 @@
|
|||||||
"description": "Copy URL"
|
"description": "Copy URL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"background": {"service_worker": "background-2.js"}
|
"background": {"service_worker": "background-4.js"}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="offscreen.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<textarea id="target"></textarea>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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/"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
echo "Install the native messaging host for the Chromium Copy URL extension"
|
||||||
|
|
||||||
|
omarchy-install-chromium-copy-url
|
||||||
@@ -38,15 +38,17 @@ pass "copy-url extension manifest has the stable id"
|
|||||||
|
|
||||||
jq -e '
|
jq -e '
|
||||||
.manifest_version == 3 and
|
.manifest_version == 3 and
|
||||||
(.permissions | index("clipboardWrite")) and
|
(.permissions | index("nativeMessaging")) and
|
||||||
(.permissions | index("offscreen")) and
|
(.permissions | index("notifications") | not) and
|
||||||
(.background.service_worker | startswith("background-"))
|
(.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 ||
|
' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null ||
|
||||||
fail "copy-url extension uses an offscreen clipboard document"
|
fail "copy-url extension uses its native messaging host"
|
||||||
[[ -f $ROOT/default/chromium/extensions/copy-url/offscreen.html &&
|
grep -q "sendNativeMessage('com.omarchy.copy_url'" \
|
||||||
-f $ROOT/default/chromium/extensions/copy-url/offscreen.js ]] ||
|
"$ROOT/default/chromium/extensions/copy-url/background-4.js" ||
|
||||||
fail "copy-url extension ships its offscreen clipboard document"
|
fail "copy-url extension sends URLs to its native messaging host"
|
||||||
pass "copy-url extension uses an offscreen clipboard document"
|
pass "copy-url extension uses its native messaging host"
|
||||||
|
|
||||||
jq -e '.action != null' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null &&
|
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 ||
|
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"
|
pass "copy-url extension is clickable from the toolbar"
|
||||||
|
|
||||||
TMPDIR=$(mktemp -d)
|
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"
|
preferences="$TMPDIR/Preferences"
|
||||||
backup="$TMPDIR/Preferences.bak"
|
backup="$TMPDIR/Preferences.bak"
|
||||||
patch_script="$TMPDIR/repair-shortcuts.py"
|
patch_script="$TMPDIR/repair-shortcuts.py"
|
||||||
|
|||||||
Reference in New Issue
Block a user