fix: prevent duplicate app launches by adding QML click debouncing and single-instance Python launcher script
This commit is contained in:
parent
c4f9e90996
commit
1419be11f0
@ -139,51 +139,25 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
property real lastLaunchTime: 0
|
||||
property string lastLaunchCmd: ""
|
||||
|
||||
function launchApp(cmd) {
|
||||
if (!cmd) return;
|
||||
let cleanCmd = cmd.trim()
|
||||
if (cleanCmd === "") return;
|
||||
|
||||
let now = Date.now()
|
||||
if (cleanCmd === lastLaunchCmd && (now - lastLaunchTime) < 600) {
|
||||
return; // Ignore rapid double-clicks within 600ms
|
||||
}
|
||||
lastLaunchTime = now
|
||||
lastLaunchCmd = cleanCmd
|
||||
|
||||
let execCmd = cleanCmd.replace(/%[a-zA-Z]/g, "").trim()
|
||||
root.activeAppId = execCmd.toLowerCase()
|
||||
|
||||
if (execCmd.startsWith("flatpak run ") || execCmd.includes("flatpak ")) {
|
||||
Quickshell.execDetached(["sh", "-c", execCmd + " &"])
|
||||
return;
|
||||
}
|
||||
|
||||
let baseName = execCmd.replace(/\.desktop$/, "").trim()
|
||||
|
||||
// Build universal Flatpak + desktop launcher + native binary fallback chain
|
||||
let shellCmd = ""
|
||||
if (baseName.startsWith("org.") || baseName.startsWith("io.") || baseName.startsWith("com.") || baseName.startsWith("app.")) {
|
||||
let shortName = baseName.split(".").pop()
|
||||
shellCmd = "flatpak run " + baseName + " 2>/dev/null || flatpak run " + shortName + " 2>/dev/null || gtk-launch " + baseName + " 2>/dev/null || gtk-launch " + shortName + " 2>/dev/null || " + shortName + " 2>/dev/null || " + execCmd + " &"
|
||||
} else {
|
||||
// Known reverse domain mappings for common Flatpak apps like spotify, feishin, zen, vesktop, discord, etc.
|
||||
let flatpakMapping = {
|
||||
"spotify": "com.spotify.Client",
|
||||
"feishin": "io.github.jeffvli.feishin",
|
||||
"zen": "io.github.zen_browser.zen",
|
||||
"zen-browser": "io.github.zen_browser.zen",
|
||||
"vesktop": "dev.vencord.Vesktop",
|
||||
"discord": "com.discordapp.Discord",
|
||||
"code": "com.visualstudio.code",
|
||||
"vscodium": "com.vscodium.codium",
|
||||
"obs": "com.obsproject.Studio",
|
||||
"steam": "com.valvesoftware.Steam",
|
||||
"heroic": "com.heroicgameslauncher.hgl"
|
||||
}
|
||||
|
||||
let mappedFlatpak = flatpakMapping[baseName.toLowerCase()] || ""
|
||||
if (mappedFlatpak !== "") {
|
||||
shellCmd = "flatpak run " + mappedFlatpak + " 2>/dev/null || flatpak run " + baseName + " 2>/dev/null || gtk-launch " + mappedFlatpak + " 2>/dev/null || gtk-launch " + baseName + " 2>/dev/null || " + execCmd + " &"
|
||||
} else {
|
||||
shellCmd = "flatpak run " + baseName + " 2>/dev/null || gtk-launch " + baseName + " 2>/dev/null || " + execCmd + " &"
|
||||
}
|
||||
}
|
||||
|
||||
Quickshell.execDetached(["sh", "-c", shellCmd])
|
||||
Quickshell.execDetached(["python3", Quickshell.env("HOME") + "/.config/quickshell/services/python/launch_app.py", cleanCmd])
|
||||
}
|
||||
|
||||
function closeApp(appId) {
|
||||
|
||||
66
quickshell/services/python/launch_app.py
Executable file
66
quickshell/services/python/launch_app.py
Executable file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
import re
|
||||
|
||||
def launch():
|
||||
if len(sys.argv) < 2:
|
||||
return
|
||||
cmd = sys.argv[1].strip()
|
||||
if not cmd:
|
||||
return
|
||||
|
||||
# Strip desktop field codes (%u, %U, %f, %F, etc.)
|
||||
clean_cmd = re.sub(r'%[a-zA-Z]', '', cmd).strip()
|
||||
base_name = clean_cmd.replace('.desktop', '').strip()
|
||||
lower_name = base_name.lower()
|
||||
|
||||
flatpak_mappings = {
|
||||
"spotify": "com.spotify.Client",
|
||||
"feishin": "io.github.jeffvli.feishin",
|
||||
"zen": "io.github.zen_browser.zen",
|
||||
"zen-browser": "io.github.zen_browser.zen",
|
||||
"vesktop": "dev.vencord.Vesktop",
|
||||
"discord": "com.discordapp.Discord",
|
||||
"code": "com.visualstudio.code",
|
||||
"vscodium": "com.vscodium.codium",
|
||||
"obs": "com.obsproject.Studio",
|
||||
"steam": "com.valvesoftware.Steam",
|
||||
"heroic": "com.heroicgameslauncher.hgl"
|
||||
}
|
||||
|
||||
# 1. Flatpak launch check (launch exactly once if Flatpak ID is installed)
|
||||
candidates_flatpak = []
|
||||
if base_name in flatpak_mappings:
|
||||
candidates_flatpak.append(flatpak_mappings[base_name])
|
||||
if lower_name in flatpak_mappings:
|
||||
candidates_flatpak.append(flatpak_mappings[lower_name])
|
||||
if base_name.startswith(("org.", "io.", "com.", "app.")):
|
||||
candidates_flatpak.append(base_name)
|
||||
|
||||
for fp_id in candidates_flatpak:
|
||||
check = subprocess.run(["flatpak", "info", fp_id], capture_output=True, text=True)
|
||||
if check.returncode == 0:
|
||||
subprocess.Popen(["flatpak", "run", fp_id], start_new_session=True)
|
||||
return
|
||||
|
||||
# 2. GTK Desktop launcher check
|
||||
if shutil.which("gtk-launch"):
|
||||
for gtk_id in [base_name, f"{base_name}.desktop"]:
|
||||
res = subprocess.run(["gtk-launch", gtk_id], capture_output=True, text=True)
|
||||
if res.returncode == 0:
|
||||
return
|
||||
|
||||
# 3. Direct binary execution
|
||||
bin_name = clean_cmd.split()[0]
|
||||
if shutil.which(bin_name):
|
||||
subprocess.Popen(clean_cmd, shell=True, start_new_session=True)
|
||||
return
|
||||
|
||||
# 4. Fallback execution
|
||||
subprocess.Popen(f"{clean_cmd} &", shell=True, start_new_session=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
launch()
|
||||
Loading…
Reference in New Issue
Block a user