From 235c377361d21b7317d6721dad28c850f4838fb3 Mon Sep 17 00:00:00 2001 From: andwati Date: Wed, 12 Aug 2026 11:33:46 +0300 Subject: [PATCH] Fix Chrome save-as prompt and unreliable finish notifications Chrome's own "Ask where to save each file" setting shows a native picker before the extension can ever intercept the download, and no extension API can read or suppress it -- surface this clearly in install.sh, the README, and a popup shortcut instead of leaving it buried as a footnote. Also fixes silently missing "download complete" notifications: KGet only fires its own finished-notification if a transfer was observed in a non-finished state at least once, so small/fast downloads that complete before that first observation never get one. The native host now drives its own notification via notify-send, polling actual transfer progress instead of relying on KGet's internal check, and install.sh disables KGet's own notification to avoid duplicates. --- README.md | 47 +++++++++++------ extension/manifest.json | 2 +- extension/popup.css | 9 ++++ extension/popup.html | 4 ++ extension/popup.js | 4 ++ install.sh | 56 +++++++++++++++++--- native-host/kget_capture_host.py | 87 ++++++++++++++++++++++++++++++-- 7 files changed, 180 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 41684b9..7441da2 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,11 @@ addition to) the browser's built-in downloader. starts is cancelled and handed off to KGet the same way, automatically, with the correct filename (resolved by Chrome itself, so it's right even for URLs whose path is just an opaque ID). Defaults to ON. -- KGet's own "Download Finished" desktop notification is enabled on install, - so you still get feedback once a capture completes. +- A "Download complete" desktop notification appears once a capture actually + finishes downloading. This is driven by the extension itself (not KGet's + own per-transfer notification, which KGet silently skips for downloads + that finish very quickly — see Troubleshooting), so it fires reliably + regardless of file size or speed. ## Requirements @@ -20,8 +23,12 @@ addition to) the browser's built-in downloader. - One of: Google Chrome, Chromium, Brave, Vivaldi, Microsoft Edge - `python3` and `dbus-send` on `PATH` (standard on virtually all Linux/KDE systems already) -- `kwriteconfig6` (ships with KDE Plasma) — optional, only used to enable the - download-finished notification; install still works without it +- `notify-send` (ships with most Linux desktops via libnotify) — optional, + only used for the download-complete notification; captures still work + without it, just silently +- `kwriteconfig6` (ships with KDE Plasma) — optional, only used to disable + KGet's own unreliable per-transfer notification so it doesn't double up + with the extension's; install still works without it ## Install @@ -38,6 +45,12 @@ finds installed, then prints the extension's fixed ID and next steps: 2. Enable **Developer mode** (top-right toggle). 3. Click **Load unpacked** and select this repo's `extension/` directory. 4. Confirm the loaded extension's ID matches the one `install.sh` printed. +5. Open `chrome://settings/downloads` (or your browser's equivalent, e.g. + `brave://settings/downloads`, `edge://settings/downloads`) and turn **off** + "Ask where to save each file before downloading". Chrome shows this dialog + itself, before the extension ever gets a chance to intercept the download — + no extension API can read or suppress it, so this one-time setting change + is required for captures to work without an extra prompt. ## Usage @@ -89,12 +102,8 @@ git push origin v1.0.0 the URL path, which is wrong for opaque/signed URLs (e.g. cloud storage links). Fixing this would require broad host permissions and an extra HTTP request per right-click, which isn't worth it for now. -- **Chrome may still show its own "Save As" dialog.** This is controlled by - Chrome's own **Ask where to save each file before downloading** setting - (`chrome://settings/downloads`) — no extension API can read or suppress - it, and if it's on, Chrome shows the picker *before* our extension gets a - chance to see the download at all. Turn that setting off for auto-capture - to work without an extra prompt. +- **Chrome may still show its own "Save As" dialog** if you skipped step 5 of + Install above — see there for why and how to fix it. - **KGet must already be running.** If it's not, captures fail silently from the user's perspective — check the service worker console for the actual error (see Troubleshooting). @@ -113,11 +122,19 @@ git push origin v1.0.0 org.kde.kget.main.addTransfer string:"https://example.com/file.zip" string:"$HOME/Downloads/file.zip" boolean:true ``` If this fails with `ServiceUnknown`, KGet isn't running. -- **Download-finished notification not showing?** `install.sh` enables it via - `kwriteconfig6 --file knotifyrc --group "Event/kget/finished" --key Action Popup`. - Check System Settings → Notifications → Applications → "KGet Download - Manager" to confirm it's on, or disable it the same way with - `kwriteconfig6 --file knotifyrc --group "Event/kget/finished" --key Action None`. +- **Download-complete notification not showing?** Confirm `notify-send` is + installed (`command -v notify-send`) — without it, `kget_capture_host.py` + skips the notification entirely rather than erroring. Otherwise, check that + the transfer actually reached 100%: `kget_capture_host.py` polls + `org.kde.kget.transfer.percent` on the new transfer's D-Bus object path + (e.g. `/KGet/Transfers/9`, printed by the `addTransfer` call above) until it + hits 100, so a stuck/erroring transfer never fires one. Note this + notification is separate from KGet's own built-in "Download Finished" + event — `install.sh` disables that one (`kwriteconfig6 --file knotifyrc + --group "Event/kget/finished" --key Action None`) because KGet only fires + it for transfers it observed in a non-finished state at least once, so it + silently skips small/fast downloads that complete before that first + observation. ## License diff --git a/extension/manifest.json b/extension/manifest.json index 230e2a7..a911e22 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "KGet Capture", - "version": "1.0.1", + "version": "1.0.2", "description": "Send browser downloads to KGet (KDE Download Manager) on Linux.", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM1nFg5pPd3UBzynUJHj6aUy7bjudZaX9AQTWGMqAX3fXylw1lubSzP1/BaS9ivvgOJg/loyEgzEZlcNPzblFVlg8BqEJ3tntdk2OzYvUs0pUdcPzzyjso9TykCkUUTI7ev8JkreW/FYlinfvNwSYN4/c1s3h8/I6XHtxOdmO1G3bS98+BQaXPldbDmxoKwyWR8u1f8gTO2Mg3GY4BHau3RWf1kgtNzh20p9wE3sEFdWkFu7ArgRCIrWLh6m3O6lolnHQSCWPwtOTx893Qkp9Eu6FwUnoWc8rsYx8sqa0eNCTCS6Kjr1Iu3yEJ5y9390jUiDZTQ/c62ueCTQaYv2bwIDAQAB", "permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"], diff --git a/extension/popup.css b/extension/popup.css index 189d84f..8bf071b 100644 --- a/extension/popup.css +++ b/extension/popup.css @@ -27,3 +27,12 @@ h1 { color: #777; line-height: 1.4; } + +.hint button { + display: block; + margin-top: 6px; + padding: 4px 8px; + font-size: 12px; + font-family: inherit; + cursor: pointer; +} diff --git a/extension/popup.html b/extension/popup.html index a9bf6d1..2b90be2 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -13,6 +13,10 @@

Right-click any link, image, video, or audio and choose "Download with KGet" to send it to KGet on demand.

+

Downloads still prompting for a location? Chrome shows that + dialog itself, before this extension can intercept anything. + +

diff --git a/extension/popup.js b/extension/popup.js index 8f2907b..fef10a8 100644 --- a/extension/popup.js +++ b/extension/popup.js @@ -14,3 +14,7 @@ checkbox.addEventListener("change", () => { chrome.storage.local.set({ autoCaptureEnabled: checkbox.checked }); renderStatus(checkbox.checked); }); + +document.getElementById("open-download-settings").addEventListener("click", () => { + chrome.tabs.create({ url: "chrome://settings/downloads" }); +}); diff --git a/install.sh b/install.sh index e560ccf..98f401c 100755 --- a/install.sh +++ b/install.sh @@ -51,6 +51,24 @@ if [ "$registered_any" = false ]; then done fi +declare -A BROWSER_SETTINGS_URLS=( + ["Google Chrome"]="chrome://settings/downloads" + ["Chromium"]="chrome://settings/downloads" + ["Brave"]="brave://settings/downloads" + ["Vivaldi"]="vivaldi://settings/downloads" + ["Microsoft Edge"]="edge://settings/downloads" +) + +echo "Checking Chrome's \"Ask where to save each file\" setting..." +prompt_warnings=() +for browser in "${!BROWSER_DIRS[@]}"; do + config_dir="${BROWSER_DIRS[$browser]}" + prefs_file="$config_dir/Default/Preferences" + if [ -f "$prefs_file" ] && grep -q '"prompt_for_download":true' "$prefs_file" 2>/dev/null; then + prompt_warnings+=(" - $browser: open ${BROWSER_SETTINGS_URLS[$browser]} and turn off \"Ask where to save each file before downloading\"") + fi +done + echo "Checking KGet's default download folder..." python3 - <<'PYEOF' import configparser @@ -90,15 +108,25 @@ else: print("(If KGet is currently running, restart it for this to take effect.)") PYEOF -echo "Checking KGet's download-finished notification..." -KNOTIFYRC="$HOME/.config/knotifyrc" -if [ -f "$KNOTIFYRC" ] && grep -q '^\[Event/kget/finished\]' "$KNOTIFYRC"; then - echo "KGet's download-finished notification is already configured, leaving it as-is" -elif command -v kwriteconfig6 >/dev/null 2>&1; then - kwriteconfig6 --file knotifyrc --group "Event/kget/finished" --key Action Popup - echo "Enabled a desktop popup notification for KGet's \"Download Finished\" event" +echo "Disabling KGet's own per-transfer notification..." +# KGet only fires this event if the transfer was still non-Finished the +# first time its UI observed it -- small/fast downloads routinely finish +# before that, so KGet silently skips it for them. kget_capture_host.py +# shows its own "Download complete" notification (via notify-send) for +# every capture instead, driven by polling actual transfer progress, so it +# fires reliably regardless of download speed. Disable KGet's copy here to +# avoid a duplicate popup on the transfers that are slow enough for it to +# fire on its own. +if command -v kwriteconfig6 >/dev/null 2>&1; then + kwriteconfig6 --file knotifyrc --group "Event/kget/finished" --key Action None + echo "Done -- KGet Capture's own notification will be used instead" else - echo "Skipped: 'kwriteconfig6' not found (not on KDE Plasma?) -- no popup notification set up" + echo "Skipped: 'kwriteconfig6' not found (not on KDE Plasma?) -- KGet's own notification, if any, is left as configured" +fi + +echo "Checking for 'notify-send' (used for the download-complete notification)..." +if ! command -v notify-send >/dev/null 2>&1; then + echo "WARNING: 'notify-send' not found -- captures will still work, but you won't get a notification when they finish" fi EXT_ID=$(cat "$SCRIPT_DIR/keys/extension-id.txt") @@ -117,3 +145,15 @@ Next steps: Make sure KGet is running (or set to autostart) for captures to work. EOF + +if [ "${#prompt_warnings[@]}" -gt 0 ]; then + cat <= 100: + finished = True + break + time.sleep(2) + +if finished: + subprocess.run( + ["notify-send", "--app-name=KGet Capture", "--icon=kget", + "Download complete", dest.rsplit("/", 1)[-1]], + timeout=10, + ) +""" + + +def spawn_notifier(transfer_path, dest): + if not transfer_path or not shutil.which("notify-send"): + return + # Detached (new session, no inherited pipes) so it keeps running after + # Chrome closes the native-messaging connection and kills this process -- + # sendNativeMessage() tears down its native host as soon as it gets a + # reply, which is long before a real download finishes. + subprocess.Popen( + [sys.executable, "-c", _NOTIFIER_SCRIPT, transfer_path, dest], + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True, + ) def main(): @@ -78,7 +153,9 @@ def main(): write_message({"ok": False, "error": "invalid request"}) return - ok, error = add_transfer(message["url"], message.get("filename")) + ok, error, transfer_path, dest = add_transfer(message["url"], message.get("filename")) + if ok: + spawn_notifier(transfer_path, dest) write_message({"ok": True} if ok else {"ok": False, "error": error})