2 Commits
Author SHA1 Message Date
andwati c395c5fbe2 Bump version to 1.0.1 2026-08-12 10:01:26 +03:00
andwati 64c3a55df4 Get correct filenames, skip KGet's dialog, and add a finished notification
Switch auto-capture from onCreated to onDeterminingFilename, which
fires once Chrome has already resolved the real filename (from
Content-Disposition or the URL). That name is now passed through to
the native host and used to build a full dir+filename destination for
addTransfer, replacing showNewTransferDialog. A non-empty destDir
makes KGet skip its interactive dialog and start immediately, which
also fixes it showing an opaque URL path segment as the filename for
signed/obfuscated URLs (e.g. cloud storage links) instead of the real
name.

Also enable KGet's existing (but disabled-by-default) "Download
Finished" KNotify popup via install.sh, so there's still feedback now
that captures no longer show an interactive dialog.
2026-08-12 09:46:54 +03:00
5 changed files with 107 additions and 44 deletions
+35 -21
View File
@@ -5,12 +5,14 @@ A Chrome (and other Chromium-browser) extension that sends downloads to
addition to) the browser's built-in downloader.
- Right-click any link, image, video, or audio element and choose
**"Download with KGet"** to open KGet's "New Download" dialog, prefilled
with the URL — edit the filename or destination folder if you want, then
confirm to start the transfer.
**"Download with KGet"** to send it straight to KGet — it starts
downloading immediately into your Downloads folder, no dialog.
- A toolbar toggle enables **auto-capture**: while on, every download Chrome
starts is cancelled and redirected to the same KGet dialog automatically.
Defaults to ON.
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.
## Requirements
@@ -18,6 +20,8 @@ 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
## Install
@@ -72,14 +76,25 @@ git push origin v1.0.0
- **Auto-capture has a brief head start.** Manifest V3 removed blocking
`webRequest`, so Chrome begins the download before the extension can cancel
it. A small/partial temp file may transiently appear before it's erased.
- **A KGet dialog opens for every capture.** The extension uses KGet's
`showNewTransferDialog` D-Bus method, which opens the interactive "New
Download" dialog rather than starting the transfer immediately. This is
intentional — it lets you rename the file or change the destination folder
before it starts. If you'd rather it start immediately without a prompt,
see [Troubleshooting](#troubleshooting) for the alternate `addTransfer`
call — note that route only returns once the dialog is dismissed, so it's
not what the extension uses by default.
- **No editable dialog.** Captures start immediately with no chance to
rename the file or pick a different folder beforehand — rename/move it
afterward in KGet or your file manager if needed. This is intentional: KGet
only skips its interactive "New Download" dialog when given a real
destination path up front, so instant capture and an editable prompt are
mutually exclusive given KGet's D-Bus API.
- **The context-menu capture can't always get the correct filename.**
Auto-capture gets the real filename for free from Chrome's own
`Content-Disposition` resolution. Right-click captures don't have that —
the extension only has the raw URL — so KGet falls back to guessing from
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.
- **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).
@@ -95,15 +110,14 @@ git push origin v1.0.0
- **Test D-Bus directly:**
```bash
dbus-send --print-reply --dest=org.kde.kget /KGet \
org.kde.kget.main.showNewTransferDialog array:string:"https://example.com/file.zip"
```
If this fails with `ServiceUnknown`, KGet isn't running. To skip the dialog
and start a transfer straight away instead, use `addTransfer` with a real
destination folder:
```bash
dbus-send --print-reply --dest=org.kde.kget /KGet \
org.kde.kget.main.addTransfer string:"https://example.com/file.zip" string:"$HOME/Downloads" boolean:true
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`.
## License
+30 -13
View File
@@ -11,16 +11,20 @@ async function refreshBadge() {
updateBadge(autoCaptureEnabled);
}
function sendToKGet(url) {
chrome.runtime.sendNativeMessage(NATIVE_HOST, { action: "addTransfer", url }, (response) => {
if (chrome.runtime.lastError) {
console.error("[kget-capture] native host error:", chrome.runtime.lastError.message);
return;
function sendToKGet(url, filename) {
chrome.runtime.sendNativeMessage(
NATIVE_HOST,
{ action: "addTransfer", url, filename: filename || null },
(response) => {
if (chrome.runtime.lastError) {
console.error("[kget-capture] native host error:", chrome.runtime.lastError.message);
return;
}
if (!response || !response.ok) {
console.error("[kget-capture] KGet reported failure:", response && response.error);
}
}
if (!response || !response.ok) {
console.error("[kget-capture] KGet reported failure:", response && response.error);
}
});
);
}
chrome.runtime.onInstalled.addListener(async (details) => {
@@ -40,10 +44,21 @@ chrome.contextMenus.onClicked.addListener((info) => {
if (info.menuItemId !== MENU_ID) return;
const url = info.linkUrl || info.srcUrl;
if (!url) return;
sendToKGet(url);
sendToKGet(url, null);
});
chrome.downloads.onCreated.addListener(async (item) => {
// onDeterminingFilename (rather than onCreated) fires once Chrome has
// already resolved the real filename (from Content-Disposition or the URL),
// which is exactly what we need to hand KGet the correct name instead of an
// opaque URL path segment.
chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
// We're about to cancel this download regardless, so Chrome's own choice
// doesn't matter -- just let it proceed so the event resolves promptly.
suggest();
captureDownload(item);
});
async function captureDownload(item) {
const { autoCaptureEnabled } = await chrome.storage.local.get({ autoCaptureEnabled: true });
if (!autoCaptureEnabled) return;
@@ -67,8 +82,10 @@ chrome.downloads.onCreated.addListener(async (item) => {
} catch (e) {
console.error("[kget-capture] failed to erase Chrome download entry:", e);
}
sendToKGet(url);
});
const filename = item.filename ? item.filename.split(/[\\/]/).pop() : null;
sendToKGet(url, filename);
}
chrome.storage.onChanged.addListener((changes, area) => {
if (area === "local" && "autoCaptureEnabled" in changes) {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "KGet Capture",
"version": "1.0.0",
"version": "1.0.1",
"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"],
+11
View File
@@ -90,6 +90,17 @@ 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"
else
echo "Skipped: 'kwriteconfig6' not found (not on KDE Plasma?) -- no popup notification set up"
fi
EXT_ID=$(cat "$SCRIPT_DIR/keys/extension-id.txt")
cat <<EOF
+30 -9
View File
@@ -2,11 +2,28 @@
"""Native messaging host bridging Chrome to KGet's D-Bus service."""
import json
import os
import struct
import subprocess
import sys
def default_download_dir():
home = os.path.expanduser("~")
try:
result = subprocess.run(
["xdg-user-dir", "DOWNLOAD"], capture_output=True, text=True, timeout=5
)
downloads = result.stdout.strip()
except (FileNotFoundError, subprocess.TimeoutExpired):
downloads = ""
# xdg-user-dir falls back to bare $HOME (not $HOME/Downloads) when
# there's no ~/.config/user-dirs.dirs at all -- treat that as not found.
if not downloads or downloads.rstrip("/") == home.rstrip("/"):
return os.path.join(home, "Downloads")
return downloads
def read_message():
raw_length = sys.stdin.buffer.read(4)
if len(raw_length) < 4:
@@ -23,20 +40,24 @@ def write_message(message):
sys.stdout.buffer.flush()
def add_transfer(url):
# showNewTransferDialog opens KGet's "New Download" dialog (editable name
# and destination) and returns immediately without waiting for the user
# to interact with it. addTransfer, by contrast, only returns once the
# dialog is dismissed when destDir is empty -- it blocks the D-Bus call
# on human input, which is why we don't use it here.
def add_transfer(url, filename):
# A non-empty destDir makes KGet add+start the transfer immediately with
# no interactive dialog. Passing dir+filename (when we know the real
# filename, e.g. from Chrome's own Content-Disposition resolution) also
# sidesteps KGet's own filename guess, which is derived purely from the
# URL path and gets it wrong for opaque/signed URLs.
dest_dir = default_download_dir()
dest = os.path.join(dest_dir, filename) if filename else dest_dir
cmd = [
"dbus-send",
"--print-reply",
"--reply-timeout=10000",
"--dest=org.kde.kget",
"/KGet",
"org.kde.kget.main.showNewTransferDialog",
f"array:string:{url}",
"org.kde.kget.main.addTransfer",
f"string:{url}",
f"string:{dest}",
"boolean:true",
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
@@ -57,7 +78,7 @@ def main():
write_message({"ok": False, "error": "invalid request"})
return
ok, error = add_transfer(message["url"])
ok, error = add_transfer(message["url"], message.get("filename"))
write_message({"ok": True} if ok else {"ok": False, "error": error})