diff --git a/config/windowrules.lua b/config/windowrules.lua index a4ca6b6..b256ab6 100644 --- a/config/windowrules.lua +++ b/config/windowrules.lua @@ -133,4 +133,12 @@ hl.window_rule({ }, no_dim = true, content = "game" +}) +hl.window_rule({ + name = "Floorp", + match = { + class = ".*floorp.*", + }, + no_dim = true, + content = "video" }) \ No newline at end of file diff --git a/scripts/makropad/writing/tmdb.sh b/scripts/makropad/writing/tmdb.sh index 5db7167..7029a45 100755 --- a/scripts/makropad/writing/tmdb.sh +++ b/scripts/makropad/writing/tmdb.sh @@ -1,74 +1,196 @@ -#!/bin/bash +#!/usr/bin/env python3 +"""Lookup TMDB ID for the TMDB browser tab on a Hyprland workspace +and type " () [tmdb-]" via wtype. -tmdbid() { - local type q url - type="$1" - shift +Uses the TMDB API v3 (stdlib only, no curl/jq needed). +API key can be overridden without editing the file: + export TMDB_API_KEY="your-key" - q=$(printf '%s' "$*" | jq -sRr @uri) +Usage: + tmdb.sh [workspace] [--print] + --print / -p : print result to stdout instead of typing (for testing) +""" - if [ "$type" = "tv" ]; then - url="https://www.themoviedb.org/search/tv?query=$q" - else - url="https://www.themoviedb.org/search/movie?query=$q" - fi +import json +import os +import re +import shutil +import subprocess +import sys +import time +import urllib.parse +import urllib.request - curl -sL -A 'Mozilla/5.0' "$url" \ - | grep -oE '/(movie|tv)/[0-9]+' \ - | head -1 \ - | cut -d/ -f3 -} +TMDB_API_KEY = os.environ.get("TMDB_API_KEY", "7ba1c818fea0dec15d3f4d27bf1ffbdb") +TMDB_API = "https://api.themoviedb.org/3" +TMDB_LANG = "en-US" -clean_title() { - local s="$1" +# Characters illegal on Windows (< > : " / \ | ? *) plus "/" (separator on +# Linux/macOS too). Everything else - incl. numbers, unicode letters and +# harmless punctuation like - _ . ' & , ! ( ) [ ] - is kept. +UNSAFE_RE = re.compile(r'[/\\:*?"<>|]') +CONTROL_RE = re.compile(r"[\x00-\x1f\x7f]") +YEAR_PARENS_RE = re.compile(r"\s*\([^)]*\d{4}[^)]*\)") +ANY_PARENS_RE = re.compile(r"\s*\([^)]*\)\s*") +YEAR_RE = re.compile(r"(\d{4})") +TV_RE = re.compile(r"TV\s+Series", re.IGNORECASE) - # remove (year) or (TV Series year) style parentheses - s=$(echo "$s" | sed -E 's/ *\([^)]*[0-9]{4}[^)]*\)//') +UMLAUTS = str.maketrans({ + "ä": "ae", "ö": "oe", "ü": "ue", + "Ä": "Ae", "Ö": "Oe", "Ü": "Ue", + "ß": "ss", +}) - # umlaut replacement - s="${s//ä/ae}"; s="${s//ö/oe}"; s="${s//ü/ue}" - s="${s//Ä/Ae}"; s="${s//Ö/Oe}"; s="${s//Ü/Ue}" - # remove all numbers - s=$(echo "$s" | tr -d '0-9') +def die(msg: str) -> "NoReturn": + print(f"tmdb: {msg}", file=sys.stderr) + if shutil.which("notify-send"): + subprocess.run(["notify-send", "tmdb.sh", msg], check=False) + sys.exit(1) - # remove special characters except spaces and letters - s=$(echo "$s" | tr -cd '[:alpha:] [:space:]') - # clean extra spaces - s=$(echo "$s" | tr -s ' ') +def clean_title(s: str) -> str: + # Remove "(year)" / "(TV Series year...)" blocks; the wrapper re-adds + # "(year)" itself so this avoids "Title (2013) (2013)". + s = YEAR_PARENS_RE.sub("", s) + s = s.translate(UMLAUTS) + s = UNSAFE_RE.sub(" ", s) + s = CONTROL_RE.sub(" ", s) + s = re.sub(r"\s+", " ", s).strip() + s = re.sub(r"[. ]+$", "", s).strip() + return s - echo "$s" -} -ws="${1:-3}" +def get_browser_title(workspace: int) -> str: + try: + out = subprocess.run( + ["hyprctl", "clients", "-j"], + capture_output=True, text=True, timeout=10, check=True, + ) + except FileNotFoundError: + die("missing dependency: hyprctl") + except subprocess.CalledProcessError as e: + die(f"hyprctl failed: {e.stderr.strip() or e}") + try: + clients = json.loads(out.stdout) + except json.JSONDecodeError as e: + die(f"could not parse hyprctl output: {e}") + for client in clients: + try: + ws = client.get("workspace", {}).get("id") + except AttributeError: + continue + title = client.get("title", "") or "" + if ws == workspace and "The Movie Database" in title: + return title + die(f"no TMDB tab found on workspace {workspace}") -title=$(hyprctl clients -j | jq ".[] | select(.workspace.id == $ws)" | grep "The Movie Database" | sed 's/ — The Movie Database.*//' | sed 's/ "title": "//' | sed 's/"$//') -search_title=$(echo "$title" | sed -E 's/ *\([^)]*\) *//g') +def strip_tmdb_suffix(title: str) -> str: + # TMDB tab titles look like: "Dampfnudelblues (2013) — The Movie Database (TMDB)" + for sep in (" — ", " -- ", " - The Movie Database"): + if sep in title: + title = title.split(sep, 1)[0] + break + return title.strip() -if echo "$title" | grep -qi "TV [Ss]eries"; then - type="tv" -else - type="movie" -fi -tmdbid=$(tmdbid "$type" "$search_title") +def tmdb_search(media_type: str, query: str, want_year: str = ""): + """Return (id, title, date) for the best hit, or None.""" + if len(query) < 2: + return None + endpoint = f"{TMDB_API}/search/{'tv' if media_type == 'tv' else 'movie'}" + params = { + "api_key": TMDB_API_KEY, + "language": TMDB_LANG, + "page": "1", + "include_adult": "false", + "query": query, + } + url = endpoint + "?" + urllib.parse.urlencode(params) + req = urllib.request.Request(url, headers={"Accept": "application/json"}) + try: + with urllib.request.urlopen(req, timeout=15) as resp: + data = json.load(resp) + except Exception as e: + print(f"tmdb: API request failed: {e}", file=sys.stderr) + return None + if isinstance(data, dict) and data.get("success") is False: + print(f"tmdb: API error: {data.get('status_message', 'unknown error')}", + file=sys.stderr) + return None + results = data.get("results") or [] + if want_year: + for r in results: + date = str(r.get("release_date") or r.get("first_air_date") or "") + if date.startswith(want_year): + return (r.get("id"), r.get("title") or r.get("name") or "", date) + if results: + r = results[0] + return (r.get("id"), r.get("title") or r.get("name") or "", + str(r.get("release_date") or r.get("first_air_date") or "")) + return None -if [ -z "$tmdbid" ]; then - if [ "$type" = "tv" ]; then - tmdbid=$(tmdbid "movie" "$search_title") - else - tmdbid=$(tmdbid "tv" "$search_title") - fi -fi -if [[ $title =~ ([0-9]{4}) ]]; then - year="${BASH_REMATCH[1]}" -else - year="" -fi +def main() -> None: + args = sys.argv[1:] + do_print = any(a in ("-p", "--print", "--dry-run") for a in args) + args = [a for a in args if a not in ("-p", "--print", "--dry-run")] -clean=$(clean_title "$title") + ws = 3 + if args: + if not re.fullmatch(r"\d+", args[0]): + die(f"invalid workspace id: {args[0]}") + ws = int(args[0]) -wtype "$clean ($year) [tmdb-$tmdbid]" + if not TMDB_API_KEY: + die("TMDB_API_KEY is empty") + if shutil.which("hyprctl") is None: + die("missing dependency: hyprctl") + if not do_print and shutil.which("wtype") is None: + die("missing dependency: wtype") + + raw_title = get_browser_title(ws) + title = strip_tmdb_suffix(raw_title).strip() + search_title = ANY_PARENS_RE.sub(" ", title) + search_title = re.sub(r"\s+", " ", search_title).strip() + if not search_title: + die(f"could not derive search title from: {title}") + + media_type = "tv" if TV_RE.search(title) else "movie" + m = YEAR_RE.search(title) + want_year = m.group(1) if m else "" + + result = tmdb_search(media_type, search_title, want_year) + if result is None: + fallback = "movie" if media_type == "tv" else "tv" + result = tmdb_search(fallback, search_title, want_year) + if result is None or result[0] is None: + die(f"no TMDB result for: {search_title}") + + tmdb_id, api_title, api_date = result + + clean = clean_title(api_title) if api_title else clean_title(title) + + year = want_year + if not year: + m = YEAR_RE.search(api_date or "") + year = m.group(1) if m else "" + + out = f"{clean} ({year}) [tmdb-{tmdb_id}]" if year else f"{clean} [tmdb-{tmdb_id}]" + + if do_print: + print(out) + return + + # Let the macro-pad key release before typing, otherwise held modifiers + # can mangle/overwrite the typed text in the focused window. + time.sleep(0.25) + try: + subprocess.run(["wtype", "--", out], check=True) + except subprocess.CalledProcessError as e: + die(f"wtype failed: {e}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/makropad/writing/todo.sh b/scripts/makropad/writing/todo.sh index f60e013..ab72de1 100755 --- a/scripts/makropad/writing/todo.sh +++ b/scripts/makropad/writing/todo.sh @@ -20,6 +20,7 @@ sleep 0.1 # Tab twice ydotool key 15:1 15:0 ydotool key 15:1 15:0 +ydotool key 15:1 15:0 sleep 0.1