#!/usr/bin/python3 import os, subprocess, json, sqlite3, shutil, tempfile from pathlib import Path def readfile(path): fd = open(path, "r") result = fd.read() fd.close() return result # # GTK apps # vals = {} home = os.environ["HOME"] appsdir=["/usr/share/applications", f"{home}/.local/share/applications"] for appdir in appsdir: if not os.path.isdir(appdir): continue for appfile in os.listdir(appdir): if not appfile.endswith(".desktop"): continue path = appdir + "/" + appfile content = readfile(path) _name = None _exec = None _type = None _nodisplay = "" _hidden = "" for line in content.splitlines(content): line = line.strip() if line.startswith("Exec="): _exec = line[5:] elif line.startswith("Type="): _type = line[5:] elif line.startswith("Name=") and _name is None: _name = line[5:] elif line.startswith("NoDisplay="): _nodisplay = line[10:] elif line.startswith("Hidden="): _hidden = line[7:] if _name is None or _exec is None: continue if _type is None or _type != "Application": continue if _nodisplay == "true" or _hidden == "true": continue vals[_name] = {"kind": "app", "exec": _exec, "path": path, "name": appfile[:-8]} # # Bin # bindir = f"{home}/bin" for file in os.listdir(bindir): path = f"{bindir}/{file}" if os.path.isdir(path): continue if file.startswith("."): continue vals[file] = {"kind": "bin", "path": path} # # Chromium bookmarks # def walk_bookmarks(node, folder=""): if isinstance(node, dict): if node.get("type") == "url" and node.get("url"): name = node.get("name") or node["url"] label = f"{folder}/{name}" if folder else name vals[label] = {"kind": "url", "url": node["url"]} elif "children" in node: name = node.get("name", "") next_folder = f"{folder}/{name}" if folder and name else name or folder for child in node.get("children", []): walk_bookmarks(child, next_folder) bookmarkfile = f"{home}/.config/chromium/Default/Bookmarks" if os.path.isfile(bookmarkfile): content = readfile(bookmarkfile) data = json.loads(content) roots = data.get("roots", {}) for root in roots.values(): walk_bookmarks(root) # # Firefox bookmarks # SEARCH_DIRS = [ Path.home() / ".config" / "librewolf", ] def find_places_databases(): seen = set() for base in SEARCH_DIRS: if not base.exists(): continue for db in base.glob("**/places.sqlite"): resolved = db.resolve() if resolved not in seen: seen.add(resolved) yield db def read_firefox_bookmarks(db_path): # Copy the DB first so this also works while Firefox/LibreWolf is running. with tempfile.TemporaryDirectory() as tmp: tmp_db = Path(tmp) / "places.sqlite" shutil.copy2(db_path, tmp_db) for suffix in ("-wal", "-shm"): sidecar = Path(str(db_path) + suffix) if sidecar.exists(): shutil.copy2(sidecar, Path(str(tmp_db) + suffix)) con = sqlite3.connect(f"file:{tmp_db}?mode=ro", uri=True) cur = con.cursor() query = """ WITH RECURSIVE folders(id, path) AS ( SELECT id, COALESCE(title, '') FROM moz_bookmarks WHERE parent = 0 UNION ALL SELECT b.id, CASE WHEN folders.path = '' THEN COALESCE(b.title, '') WHEN COALESCE(b.title, '') = '' THEN folders.path ELSE folders.path || '/' || b.title END FROM moz_bookmarks b JOIN folders ON b.parent = folders.id WHERE b.type = 2 ) SELECT COALESCE(folders.path, ''), COALESCE(b.title, ''), p.url FROM moz_bookmarks b JOIN moz_places p ON b.fk = p.id LEFT JOIN folders ON b.parent = folders.id WHERE b.type = 1 AND p.url IS NOT NULL ORDER BY b.dateAdded """ for folder, title, url in cur.execute(query): name = title or url label = f"{folder}/{name}" if folder else name vals[label] = {"kind": "url", "url": url} con.close() for it in find_places_databases(): read_firefox_bookmarks(it) entries = [] for it in vals.items(): entries.append(it[0]) entries_string = "\n".join(entries) result = subprocess.run(["wmenu", "-l", "20", "-i"], input=entries_string, text=True, stdout=subprocess.PIPE) if result.returncode != 0: exit(result.returncode) choice = result.stdout.strip() if choice[0] == '@': choice = choice[1:] val = vals.get(choice) if val is not None: if val["kind"] == "bin": subprocess.run([val["path"]]) elif val["kind"] == "app": subprocess.run(["gtk-launch", val["name"]]) elif val["kind"] == "url": subprocess.run(["xdg-open", val["url"]]) else: subprocess.run(["xdg-open", "https://www.google.com/search?q=" + choice])