mirror of
https://github.com/andwati/kget-capture.git
synced 2026-10-18 03:02:56 +00:00
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.
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -62,14 +64,87 @@ def add_transfer(url, filename):
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||
except FileNotFoundError:
|
||||
return False, "dbus-send not found on this system"
|
||||
return False, "dbus-send not found on this system", None, dest
|
||||
except subprocess.TimeoutExpired:
|
||||
return False, "dbus-send call timed out (is KGet running?)"
|
||||
return False, "dbus-send call timed out (is KGet running?)", None, dest
|
||||
|
||||
if result.returncode != 0:
|
||||
stderr = result.stderr.strip()
|
||||
return False, f"dbus-send failed: {stderr or 'unknown error'}"
|
||||
return True, None
|
||||
return False, f"dbus-send failed: {stderr or 'unknown error'}", None, dest
|
||||
|
||||
# addTransfer replies with an array of the new transfer's D-Bus object
|
||||
# path(s), e.g. `array [ string "/KGet/Transfers/9" ]` -- grab the first
|
||||
# one so we can poll it for completion (see spawn_notifier).
|
||||
match = re.search(r'string "(/KGet/Transfers/\d+)"', result.stdout)
|
||||
transfer_path = match.group(1) if match else None
|
||||
return True, None, transfer_path, dest
|
||||
|
||||
|
||||
# KGet only fires its own "Download Finished" desktop notification if the
|
||||
# transfer was still in a non-Finished state the first time KGet's UI
|
||||
# observed it (see GenericObserver::transfersChangedEvent in KGet's
|
||||
# core/kget.cpp: `status() == Finished && startStatus() != Finished`).
|
||||
# Small/fast downloads routinely finish before that first observation, so
|
||||
# KGet silently skips the notification for them -- confirmed by tracing
|
||||
# actual D-Bus traffic against a running KGet instance. We replace it with
|
||||
# our own, driven by polling the transfer's real progress, so captures
|
||||
# always get a completion notification regardless of how fast they finish.
|
||||
_NOTIFIER_SCRIPT = """
|
||||
import subprocess, sys, time
|
||||
|
||||
path, dest = sys.argv[1], sys.argv[2]
|
||||
|
||||
def get_percent():
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["dbus-send", "--session", "--print-reply", "--dest=org.kde.kget",
|
||||
path, "org.kde.kget.transfer.percent"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
return None
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
for line in result.stdout.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("int32"):
|
||||
return int(line.split()[-1])
|
||||
return None
|
||||
|
||||
deadline = time.time() + 6 * 3600
|
||||
finished = False
|
||||
while time.time() < deadline:
|
||||
percent = get_percent()
|
||||
if percent is None:
|
||||
break # transfer object is gone: finished+cleared, cancelled, or KGet restarted
|
||||
if percent >= 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})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user