summaryrefslogtreecommitdiff
path: root/local/bin/dmenu.py
diff options
context:
space:
mode:
authorcoasteen <coasteen@proton.me>2026-07-09 10:42:28 +0330
committercoasteen <coasteen@proton.me>2026-07-09 10:42:28 +0330
commit8cbadb61604667b407b53ee1258433994fa4765a (patch)
tree97765fb29418fd6278fcb4cbb9760893afaf7a58 /local/bin/dmenu.py
Diffstat (limited to 'local/bin/dmenu.py')
-rwxr-xr-xlocal/bin/dmenu.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/local/bin/dmenu.py b/local/bin/dmenu.py
new file mode 100755
index 0000000..948dbd8
--- /dev/null
+++ b/local/bin/dmenu.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+
+DMENU_ARGS = [
+ "dmenu",
+ "-i",
+ "-fn", "JetBrainsMono Nerd Font-10",
+ "-nb", "#121212",
+ "-nf", "#e0e0e0",
+ "-sb", "#222222",
+ "-sf", "#e0e0e0",
+ "-l", "10",
+ "-p", "Launch:",
+]
+
+DESKTOP_DIRS = [
+ Path.home() / ".local/share/applications",
+ Path.home() / ".local/share/flatpak/exports/share/applications",
+ Path("/var/lib/flatpak/exports/share/applications"),
+ Path("/usr/share/applications"),
+ Path("/usr/local/share/applications"),
+]
+
+INVALID_EXEC_PREFIXES = ("wine", "wscript", "rundll32", "winhlp32", "hh ")
+
+
+def parse_desktop_file(path: Path):
+ """
+ Returns (name, exec_cmd) or (None, None) if the file should be skipped.
+ Handles: NoDisplay=true, Hidden=true, missing Name/Exec, field codes, env wrappers.
+ """
+ props = {}
+ in_desktop_entry = False
+
+ try:
+ with path.open(encoding="utf-8", errors="replace") as f:
+ for line in f:
+ line = line.rstrip("\n")
+ if line.startswith("["):
+ in_desktop_entry = line == "[Desktop Entry]"
+ continue
+ if not in_desktop_entry or "=" not in line:
+ continue
+ key, _, value = line.partition("=")
+ key = key.strip()
+ if key not in props:
+ props[key] = value.strip()
+ except OSError:
+ return None, None
+
+ if props.get("NoDisplay", "").lower() == "true":
+ return None, None
+ if props.get("Hidden", "").lower() == "true":
+ return None, None
+ if props.get("Type", "") != "Application":
+ return None, None
+
+ name = props.get("Name")
+ exec_raw = props.get("Exec")
+
+ if not name or not exec_raw:
+ return None, None
+
+ exec_cmd = clean_exec(exec_raw)
+
+ if not exec_cmd:
+ return None, None
+
+ lower = exec_cmd.lower()
+ if any(lower.startswith(p) for p in INVALID_EXEC_PREFIXES):
+ return None, None
+
+ terminal = props.get("Terminal", "").lower() == "true"
+ if terminal:
+ term = os.environ.get("TERMINAL", "xterm")
+ exec_cmd = f"{term} -e {exec_cmd}"
+
+ return name, exec_cmd
+
+
+def clean_exec(exec_raw: str) -> str:
+ tokens = exec_raw.split()
+ cleaned = []
+ for token in tokens:
+ if token.startswith("%") and len(token) == 2 and token[1].isalpha():
+ continue
+ cleaned.append(token)
+ return " ".join(cleaned).strip()
+
+
+def collect_apps():
+ seen_stems = {}
+ for directory in DESKTOP_DIRS:
+ if not directory.is_dir():
+ continue
+ for desktop_file in sorted(directory.glob("*.desktop")):
+ stem = desktop_file.stem.lower()
+ if stem not in seen_stems:
+ seen_stems[stem] = desktop_file
+
+ apps = {}
+ for desktop_file in seen_stems.values():
+ name, cmd = parse_desktop_file(desktop_file)
+ if name and cmd and name not in apps:
+ apps[name] = cmd
+ return apps
+
+
+def run_dmenu(app_names: list[str]) -> str | None:
+ names_input = "\n".join(sorted(app_names, key=str.casefold)).encode()
+ try:
+ result = subprocess.run(
+ DMENU_ARGS,
+ input=names_input,
+ capture_output=True,
+ )
+ except FileNotFoundError:
+ sys.exit("error: dmenu not found in PATH")
+
+ if result.returncode != 0:
+ return None
+
+ return result.stdout.decode().strip()
+
+
+def launch(cmd: str):
+ subprocess.Popen(
+ cmd,
+ shell=True,
+ stdin=subprocess.DEVNULL,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ start_new_session=True,
+ )
+
+
+def main():
+ apps = collect_apps()
+
+ if not apps:
+ sys.exit("error: no applications found")
+
+ selected = run_dmenu(list(apps.keys()))
+
+ if not selected:
+ return
+
+ cmd = apps.get(selected)
+ if not cmd:
+ sys.exit(f"error: no command found for '{selected}'")
+
+ launch(cmd)
+
+
+if __name__ == "__main__":
+ main()