diff --git a/README.md b/README.md index 0bdba92..d3cb200 100644 --- a/README.md +++ b/README.md @@ -97,13 +97,6 @@ git push origin v1.0.0 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** 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 diff --git a/extension/background.js b/extension/background.js index c5b0733..a39b133 100644 --- a/extension/background.js +++ b/extension/background.js @@ -52,6 +52,11 @@ chrome.runtime.onInstalled.addListener(async (details) => { refreshBadge(); }); +// Downloads started from the context menu, tracked so captureDownload() +// still resolves/forwards their real filename even when auto-capture is +// toggled off -- a right-click "Download with KGet" should always work. +const pendingContextMenuDownloads = new Set(); + chrome.contextMenus.onClicked.addListener((info) => { if (info.menuItemId !== MENU_ID) return; const url = info.linkUrl || info.srcUrl; @@ -60,7 +65,22 @@ chrome.contextMenus.onClicked.addListener((info) => { console.warn("[kget-capture] cannot hand a local/embedded URL to KGet:", url); return; } - sendToKGet(url, null); + // Route through chrome.downloads instead of sending the raw URL straight + // to KGet -- this reuses onDeterminingFilename/captureDownload below to + // get Chrome's real resolved filename (Content-Disposition, etc.) instead + // of leaving KGet to guess one from the bare URL, which drops query + // strings and gets opaque/signed URLs wrong. + chrome.downloads.download({ url }, (downloadId) => { + if (chrome.runtime.lastError || downloadId === undefined) { + console.error( + "[kget-capture] couldn't start download to resolve filename, falling back to raw URL:", + chrome.runtime.lastError && chrome.runtime.lastError.message + ); + sendToKGet(url, null); + return; + } + pendingContextMenuDownloads.add(downloadId); + }); }); // onDeterminingFilename (rather than onCreated) fires once Chrome has @@ -75,8 +95,9 @@ chrome.downloads.onDeterminingFilename.addListener((item, suggest) => { }); async function captureDownload(item) { + const isContextMenuCapture = pendingContextMenuDownloads.delete(item.id); const { autoCaptureEnabled } = await chrome.storage.local.get({ autoCaptureEnabled: true }); - if (!autoCaptureEnabled) return; + if (!autoCaptureEnabled && !isContextMenuCapture) return; const url = item.finalUrl || item.url; if (!isCapturableUrl(url)) { diff --git a/native-host/kget_capture_host.py b/native-host/kget_capture_host.py index 7914b2b..e4242fd 100755 --- a/native-host/kget_capture_host.py +++ b/native-host/kget_capture_host.py @@ -49,7 +49,12 @@ def add_transfer(url, filename): # 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 + # A bare directory path with no trailing slash (e.g. "/home/ian/Downloads") + # gets misparsed by KGet's addTransfer as dir="/home/ian" + filename= + # "Downloads" -- the same split it applies to a real dir/filename path -- + # landing the transfer in the parent directory instead. A trailing slash + # keeps it unambiguous when we don't have a real filename to append. + dest = os.path.join(dest_dir, filename) if filename else dest_dir.rstrip("/") + "/" cmd = [ "dbus-send", "--print-reply",