diff --git a/alacritty/.config/alacritty/alacritty.toml b/alacritty/.config/alacritty/alacritty.toml index 7a71b40..08002f4 100644 --- a/alacritty/.config/alacritty/alacritty.toml +++ b/alacritty/.config/alacritty/alacritty.toml @@ -1,8 +1,8 @@ # Alacritty Configuration # Migrated from Ghostty configuration -# Import noctalia theme -import = ["~/.config/alacritty/themes/noctalia.toml"] +# Import rose-pine theme +import = ["~/.config/alacritty/themes/rose-pine.toml"] # Font configuration [font] @@ -46,4 +46,3 @@ action = "DecreaseFontSize" key = "Key0" mods = "Control|Shift" action = "ResetFontSize" - diff --git a/local-bin/.local/bin/niri-dev-launcher b/local-bin/.local/bin/niri-dev-launcher index bdd61e8..2204b8d 100755 --- a/local-bin/.local/bin/niri-dev-launcher +++ b/local-bin/.local/bin/niri-dev-launcher @@ -1,272 +1,238 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env python3 +"""Niri Dev Launcher - Launch development environments from git repositories""" -# Configuration -CACHE_MAX_AGE=300 -MRU_SIZE=5 +import argparse +import hashlib +import json +import os +import re +import shutil +import subprocess +import sys +import time +from pathlib import Path -# Debug logging (set DEBUG=1 to enable via environment variable) -DEBUG="${DEBUG:-0}" +CACHE_MAX_AGE = 300 +MRU_SIZE = 5 -debug() { - [ "$DEBUG" = "1" ] && echo "[DEBUG] $*" >&2 || true -} -# Parse arguments - flags first, then DEV_DIR -NO_CACHE=false -CLEAR_CACHE=false -EDITOR="${EDITOR:-nvim}" -DEV_DIR="" +def debug(msg): + if os.getenv("DEBUG") == "1": + print(f"[DEBUG] {msg}", file=sys.stderr) -debug "Starting niri-dev-launcher with args: $*" -for arg in "$@"; do - case "$arg" in - --help | -h) - cat </dev/null || true - exit 0 -fi -# Find git repositories -find_git_repos() { - debug "find_git_repos: checking cache..." - if [ "$NO_CACHE" = false ] && [ -f "$CACHE_FILE" ]; then - local cache_age=$(($(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0))) - debug "Cache age: ${cache_age}s (max: ${CACHE_MAX_AGE}s)" - if [ $cache_age -lt $CACHE_MAX_AGE ]; then - debug "Using cached repos" - cat "$CACHE_FILE" - return - fi - debug "Cache expired, rescanning..." - fi +def find_git_repos(dev_dir, no_cache=False): + cache_file, _ = get_cache_files(dev_dir) + debug("find_git_repos: checking cache...") + + if not no_cache and cache_file.exists(): + cache_age = time.time() - os.path.getmtime(cache_file) + debug(f"Cache age: {cache_age:.0f}s (max: {CACHE_MAX_AGE}s)") + if cache_age < CACHE_MAX_AGE: + debug("Using cached repos") + return [r for r in cache_file.read_text().strip().split("\n") if r] + debug("Cache expired, rescanning...") + + debug(f"Scanning for git repositories in {dev_dir}...") + if not Path(dev_dir).exists(): + return [] + + repos = [] + if _has_fd(): + debug("Using fd for scanning") + result = subprocess.run( + ["fd", "-H", "-t", "d", "^\.git$", dev_dir, "-d", "3", "-0"], + capture_output=True, text=True, + ) + else: + debug("Using find for scanning") + result = subprocess.run( + ["find", dev_dir, "-maxdepth", "3", "-type", "d", "-name", ".git", "-print0"], + capture_output=True, text=True, + ) + + if result.returncode == 0: + repos = [str(Path(g).parent) for g in result.stdout.strip("\0").split("\0") if g] + + repos = sorted(set(repos)) + cache_file.parent.mkdir(parents=True, exist_ok=True) + cache_file.write_text("\n".join(repos)) + debug(f"Found {len(repos)} repositories") + return repos - debug "Scanning for git repositories in $DEV_DIR..." - if command -v fd >/dev/null 2>&1; then - debug "Using fd for scanning" - fd -H -t d "^\.git$" "$DEV_DIR" -d 3 -0 | xargs -0 -n1 dirname | sort -u >"$CACHE_FILE" - else - debug "Using find for scanning" - find "$DEV_DIR" -maxdepth 3 -type d -name ".git" -print0 | - xargs -0 -n1 dirname | sort -u >"$CACHE_FILE" - fi - local repo_count=$(wc -l <"$CACHE_FILE") - debug "Found $repo_count repositories" - cat "$CACHE_FILE" -} -# Get sanitized project name for tmux session -get_project_name() { - basename "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-\|-$//g' -} +def get_project_name(repo_path): + name = Path(repo_path).name.lower() + name = re.sub(r"[^a-z0-9]", "-", name) + name = re.sub(r"-+", "-", name) + return name.strip("-") -# Update MRU list -update_mru() { - local selected="$1" - local temp_file - temp_file=$(mktemp) - echo "$selected" >"$temp_file" - [ -f "$MRU_FILE" ] && grep -vFx "$selected" "$MRU_FILE" >>"$temp_file" || true - head -n "$MRU_SIZE" "$temp_file" >"$MRU_FILE" - rm -f "$temp_file" -} +def update_mru(selected_display, dev_dir): + _, mru_file = get_cache_files(dev_dir) + mru_list = [l.strip() for l in mru_file.read_text().split("\n") if l.strip()] if mru_file.exists() else [] + # Remove if exists (using list comprehension is faster than remove+insert for small lists) + mru_list = [s for s in mru_list if s != selected_display] + mru_list.insert(0, selected_display) + mru_file.parent.mkdir(parents=True, exist_ok=True) + mru_file.write_text("\n".join(mru_list[:MRU_SIZE])) -# Sort by MRU (MRU items first with ⭐, then others) -sort_by_mru() { - local all_repos="$1" - local mru_list - local temp_file - [ -f "$MRU_FILE" ] && mru_list=$(grep -v '^$' "$MRU_FILE") || mru_list="" +def sort_by_mru(display_list, dev_dir): + _, mru_file = get_cache_files(dev_dir) + mru_set = set() + if mru_file.exists(): + mru_set = {l.strip() for l in mru_file.read_text().split("\n") if l.strip()} + + if not mru_set: + return sorted(display_list) + + # Use set for O(1) lookup instead of list + mru_repos = sorted([f"⭐ {d}" for d in display_list if d in mru_set]) + non_mru_repos = sorted([d for d in display_list if d not in mru_set]) + return mru_repos + non_mru_repos - # If MRU list is empty, just return sorted repos - if [ -z "$mru_list" ]; then - echo "$all_repos" | sort - return - fi - # Output MRU items first with indicator - echo "$mru_list" | sed 's/^/⭐ /' +def find_window_by_app_id(app_id): + debug(f"Searching for window with app-id: {app_id}") + try: + result = subprocess.run(["niri", "msg", "--json", "windows"], capture_output=True, text=True, check=False) + windows_json = result.stdout.strip() + if not windows_json or windows_json == "[]": + return None + windows = json.loads(windows_json) + for window in windows: + if window.get("app_id", "").lower() == app_id.lower(): + return str(window.get("id")) + return None + except (json.JSONDecodeError, subprocess.SubprocessError, KeyError): + return None - # Output non-MRU items using comm (fast set difference) - temp_file=$(mktemp) - echo "$all_repos" | sort >"$temp_file.all" - echo "$mru_list" | sort >"$temp_file.mru" - comm -23 "$temp_file.all" "$temp_file.mru" 2>/dev/null || cat "$temp_file.all" - rm -f "$temp_file.all" "$temp_file.mru" -} -# Find window by app-id in niri -find_window_by_app_id() { - local app_id="$1" - debug "Searching for window with app-id: $app_id" +def focus_or_launch_alacritty(class_name, title, working_dir, session_name): + debug(f"focus_or_launch_alacritty: class={class_name}, title={title}, dir={working_dir}") + window_id = find_window_by_app_id(class_name) + + if window_id: + debug("Found existing window, focusing...") + subprocess.run(["niri", "msg", "action", "focus-window", "--id", window_id], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + debug("No existing window found, launching alacritty...") + editor = os.getenv("EDITOR", "nvim") + tmux_cmd = f"""if tmux has-session -t '{session_name}' 2>/dev/null; then + tmux attach -t '{session_name}'; +else + tmux new-session -c '{working_dir}' -s '{session_name}' -d '{editor}'; + tmux split-window -h -c '{working_dir}' -t '{session_name}'; + tmux select-pane -t '{session_name}:0.0'; + tmux attach -t '{session_name}'; +fi""" + subprocess.Popen( + ["alacritty", f"--class={class_name}", f"--title={title}", + f"--working-directory={working_dir}", "-e", "bash", "-c", tmux_cmd], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True, + ) - # Check if jq is available - if ! command -v jq >/dev/null 2>&1; then - debug "jq not available, falling back to grep method" - # Get windows list and search for matching app-id - niri msg windows 2>/dev/null | grep -A 1 "App ID: \"$app_id\"" | head -1 | grep -q "Window ID" || return 1 - return 0 - fi - # Get windows list from niri as JSON - local windows_json - windows_json=$(niri msg -j windows 2>/dev/null || echo "[]") +def main(): + parser = argparse.ArgumentParser(description="Niri Dev Launcher") + parser.add_argument("dev_dir", nargs="?", default=os.path.expanduser("~/Dev"), help="Development directory") + parser.add_argument("--no-cache", action="store_true", help="Bypass cache and rescan repositories") + parser.add_argument("--clear-cache", action="store_true", help="Clear cache and MRU files") + args = parser.parse_args() + + debug(f"Starting niri-dev-launcher with args: {sys.argv[1:]}") + debug(f"DEV_DIR: {args.dev_dir}") + cache_file, mru_file = get_cache_files(args.dev_dir) + debug(f"CACHE_FILE: {cache_file}") + debug(f"MRU_FILE: {mru_file}") + + if args.clear_cache: + if cache_file.exists(): + cache_file.unlink() + if mru_file.exists(): + mru_file.unlink() + subprocess.run(["notify-send", "Niri Dev Launcher", "Cache cleared"], check=False) + sys.exit(0) + + repos = find_git_repos(args.dev_dir, args.no_cache) + if not repos: + debug("No repositories found!") + subprocess.run(["notify-send", "Niri Dev Launcher", f"No git repositories found in {args.dev_dir}"], check=False) + sys.exit(1) + + debug("Found repositories, proceeding...") + display_to_path = {} + display_list = [] + for repo in repos: + display = str(Path(repo).relative_to(args.dev_dir)) if repo.startswith(args.dev_dir) else repo + display_list.append(display) + display_to_path[display] = repo + + debug(f"Mapped {len(display_to_path)} projects") + debug("Sorting by MRU...") + sorted_list = sort_by_mru(display_list, args.dev_dir) + debug("Presenting in fuzzel...") + + # Use default font to avoid subprocess call - can be overridden via env if needed + fuzzel_process = subprocess.Popen( + ["fuzzel", "--prompt", "💀 Poison: ", "--dmenu", "--width", "30", "--lines", "20", + "--border-width", "2", "--background-color", "#191724ff", + "--text-color", "#e0def4ff", "--match-color", "#31748fff", "--selection-color", "#1f1d2eff", + "--selection-text-color", "#31748fff", "--selection-match-color", "#31748fff", "--prompt-color", "#f6c177ff"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, + ) + + stdout, _ = fuzzel_process.communicate(input="\n".join(sorted_list)) + selected_display = stdout.strip() + debug(f"Selected display: '{selected_display}'") + + if not selected_display: + debug("No selection, exiting") + sys.exit(0) + + selected_display_clean = selected_display.replace("⭐ ", "", 1) + selected = display_to_path.get(selected_display_clean, "") + debug(f"Selected display (clean): '{selected_display_clean}'") + debug(f"Selected path: '{selected}'") + + if not selected or not Path(selected).is_dir(): + debug("Invalid selection, exiting") + sys.exit(0) + + debug("Updating MRU...") + update_mru(selected_display_clean, args.dev_dir) + + project_name = get_project_name(selected) + session_name = f"dev-{project_name}" + class_name = f"com.mzunino.dev.{project_name}" + debug(f"Project name: {project_name}") + debug(f"Session name: {session_name}") + debug(f"Class name: {class_name}") + debug("Focusing or launching alacritty...") + + focus_or_launch_alacritty(class_name, project_name, selected, session_name) - if [ -z "$windows_json" ] || [ "$windows_json" = "[]" ]; then - return 1 - fi - # Use jq to find matching window - local window_id - window_id=$(echo "$windows_json" | jq -r --arg app_id "$app_id" ' - .[] | select(.app_id | ascii_downcase == ($app_id | ascii_downcase)) | .id - ' | head -1) - - if [ -n "$window_id" ] && [ "$window_id" != "null" ]; then - echo "$window_id" - return 0 - fi - - return 1 -} - -# Focus or launch alacritty window -focus_or_launch_alacritty() { - local class_name="$1" - local title="$2" - local working_dir="$3" - local session_name="$4" - - debug "focus_or_launch_alacritty: class=$class_name, title=$title, dir=$working_dir" - - # Try to find existing window - local window_id - window_id=$(find_window_by_app_id "$class_name" || echo "") - - if [ -n "$window_id" ]; then - debug "Found existing window, focusing..." - niri msg action focus-window --id "$window_id" >/dev/null 2>&1 - else - debug "No existing window found, launching alacritty..." - alacritty \ - --class="$class_name" \ - --title="$title" \ - --working-directory="$working_dir" \ - -e bash -c "if tmux has-session -t '$session_name' 2>/dev/null; then \ - tmux attach -t '$session_name'; \ - else \ - tmux new-session -c '$working_dir' -s '$session_name' -d '$EDITOR'; \ - tmux split-window -h -c '$working_dir' -t '$session_name'; \ - tmux select-pane -t '$session_name:0.0'; \ - tmux attach -t '$session_name'; \ - fi" >/dev/null 2>&1 & - disown - fi -} - -# Main execution -REPOS=$(find_git_repos) - -if [ -z "$REPOS" ]; then - debug "No repositories found!" - notify-send "Niri Dev Launcher" "No git repositories found in $DEV_DIR" 2>/dev/null || true - exit 1 -fi -debug "Found repositories, proceeding..." - -# Build display list (relative paths) and mapping -DISPLAY_LIST=$(echo "$REPOS" | sed "s|^${DEV_DIR}/||") -declare -A DISPLAY_TO_PATH - -debug "Building display mapping..." -while IFS=$'\t' read -r repo display; do - DISPLAY_TO_PATH["$display"]="$repo" -done < <(paste <(echo "$REPOS") <(echo "$DISPLAY_LIST")) -debug "Mapped ${#DISPLAY_TO_PATH[@]} projects" - -# Sort by MRU and present in fuzzel -debug "Sorting by MRU..." -SORTED_LIST=$(sort_by_mru "$DISPLAY_LIST") -debug "Presenting in fuzzel..." - -# Configure fuzzel with similar style to original tofi -SELECTED_DISPLAY=$(echo "$SORTED_LIST" | fuzzel \ - --prompt "💀 Poison: " \ - --dmenu \ - --width 30 \ - --lines 20 \ - --border-width 2 \ - --font "$(fc-match -f '%{family}:size=16' 2>/dev/null || echo 'sans:size=16')" \ - --background-color '#191724ff' \ - --text-color '#e0def4ff' \ - --match-color '#31748fff' \ - --selection-color '#1f1d2eff' \ - --selection-text-color '#31748fff' \ - --selection-match-color '#31748fff' \ - --prompt-color '#f6c177ff') - -debug "Selected display: '$SELECTED_DISPLAY'" -[ -z "$SELECTED_DISPLAY" ] && debug "No selection, exiting" && exit 0 - -# Remove star indicator and lookup path -SELECTED_DISPLAY_CLEAN=$(echo "$SELECTED_DISPLAY" | sed 's/^⭐ //') -SELECTED="${DISPLAY_TO_PATH[$SELECTED_DISPLAY_CLEAN]:-}" - -debug "Selected display (clean): '$SELECTED_DISPLAY_CLEAN'" -debug "Selected path: '$SELECTED'" - -[ -z "$SELECTED" ] || [ ! -d "$SELECTED" ] && debug "Invalid selection, exiting" && exit 0 - -# Update MRU and launch -debug "Updating MRU..." -update_mru "$SELECTED_DISPLAY_CLEAN" - -PROJECT_NAME=$(get_project_name "$SELECTED") -SESSION_NAME="dev-${PROJECT_NAME}" -CLASS_NAME="com.mzunino.dev.${PROJECT_NAME}" - -debug "Project name: $PROJECT_NAME" -debug "Session name: $SESSION_NAME" -debug "Class name: $CLASS_NAME" -debug "Focusing or launching alacritty..." - -focus_or_launch_alacritty "$CLASS_NAME" "$PROJECT_NAME" "$SELECTED" "$SESSION_NAME" +if __name__ == "__main__": + main() diff --git a/local-bin/.local/bin/niri-launch-or-focus b/local-bin/.local/bin/niri-launch-or-focus index eafb734..0fabaef 100755 --- a/local-bin/.local/bin/niri-launch-or-focus +++ b/local-bin/.local/bin/niri-launch-or-focus @@ -1,109 +1,108 @@ -#!/usr/bin/env bash -set -euo pipefail +#!/usr/bin/env python3 +""" +Simple script to launch an application or focus it if already running in niri +Usage: niri-launch-or-focus [--class CLASS] [--title TITLE] [args...] -# Simple script to launch an application or focus it if already running in niri -# Usage: niri-launch-or-focus [--class CLASS] [--title TITLE] [args...] -# -# Examples: -# niri-launch-or-focus firefox -# niri-launch-or-focus code --class Code --title "My Project" -# niri-launch-or-focus kitty --class kitty +Examples: + niri-launch-or-focus firefox + niri-launch-or-focus code --class Code --title "My Project" + niri-launch-or-focus kitty --class kitty +""" -CLASS="" -TITLE="" -ARGS=() +import argparse +import json +import subprocess +import sys -# Parse arguments -while [[ $# -gt 0 ]]; do - case $1 in - --class) - CLASS="$2" - shift 2 - ;; - --title) - TITLE="$2" - shift 2 - ;; - *) - ARGS+=("$1") - shift - ;; - esac -done -if [ ${#ARGS[@]} -eq 0 ]; then - echo "Usage: $0 [--class CLASS] [--title TITLE] [args...]" >&2 - exit 1 -fi +def find_window(class_name=None, title=None, app_name=None): + """Find window by app_id (class), title, or app name.""" + try: + result = subprocess.run( + ["niri", "msg", "--json", "windows"], + capture_output=True, + text=True, + check=False, + ) + windows_json = result.stdout.strip() + + if not windows_json or windows_json == "[]": + return None + + windows = json.loads(windows_json) + + for window in windows: + app_id = window.get("app_id", "").lower() + + if class_name: + if app_id == class_name.lower(): + return window.get("id") + elif title: + if window.get("title") == title: + return window.get("id") + elif app_name: + if app_id == app_name.lower(): + return window.get("id") + + return None + except (json.JSONDecodeError, subprocess.SubprocessError, KeyError): + return None -COMMAND="${ARGS[0]}" -APP_NAME=$(basename "$COMMAND") -unset 'ARGS[0]' -ARGS=("${ARGS[@]}") -# If class/title were provided as flags, add them to command args (for apps that support them) -if [ -n "$CLASS" ]; then - ARGS=("--class=$CLASS" "${ARGS[@]}") -fi -if [ -n "$TITLE" ]; then - ARGS=("--title=$TITLE" "${ARGS[@]}") -fi +def focus_window(window_id): + """Focus a window by ID.""" + subprocess.run( + ["niri", "msg", "action", "focus-window", "--id", str(window_id)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) -# Find window by app_id (niri uses app-id), title, or app name -find_window() { - local class="$1" - local title="$2" - local app="$3" - # Check if jq is available - if ! command -v jq >/dev/null 2>&1; then - echo "Error: jq is required but not found. Please install jq." >&2 - return 1 - fi +def launch_command(command, args): + """Launch a command in the background.""" + subprocess.Popen( + [command] + args, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True, + ) - # Get windows list from niri as JSON - local windows_json - windows_json=$(niri msg -j windows 2>/dev/null || echo "[]") - if [ -z "$windows_json" ] || [ "$windows_json" = "[]" ]; then - return 1 - fi +def main(): + parser = argparse.ArgumentParser( + description="Launch an application or focus it if already running in niri" + ) + parser.add_argument("command", help="Command to run") + parser.add_argument("--class", dest="class_name", help="Window class (app_id)") + parser.add_argument("--title", help="Window title") + parser.add_argument("args", nargs=argparse.REMAINDER, help="Additional arguments for command") + + args = parser.parse_args() + + # Extract app name from command (basename) + app_name = args.command.split("/")[-1] + + # Try to find existing window + window_id = find_window( + class_name=args.class_name, + title=args.title, + app_name=app_name if not args.class_name and not args.title else None, + ) + + # Build command arguments + cmd_args = [] + if args.class_name: + cmd_args.append(f"--class={args.class_name}") + if args.title: + cmd_args.append(f"--title={args.title}") + cmd_args.extend(args.args) + + # Focus if found, otherwise launch + if window_id: + focus_window(window_id) + else: + launch_command(args.command, cmd_args) - # Use jq to find matching window - local window_id - if [ -n "$class" ]; then - # Match by class (app_id) - window_id=$(echo "$windows_json" | jq -r --arg class "$class" ' - .[] | select(.app_id | ascii_downcase == ($class | ascii_downcase)) | .id - ' | head -1) - elif [ -n "$title" ]; then - # Match by title - window_id=$(echo "$windows_json" | jq -r --arg title "$title" ' - .[] | select(.title == $title) | .id - ' | head -1) - else - # Match by app name (basename of command) - window_id=$(echo "$windows_json" | jq -r --arg app "$app" ' - .[] | select(.app_id | ascii_downcase == ($app | ascii_downcase)) | .id - ' | head -1) - fi - - if [ -n "$window_id" ] && [ "$window_id" != "null" ]; then - echo "$window_id" - return 0 - fi - - return 1 -} - -# Try to find existing window -WINDOW_ID=$(find_window "$CLASS" "$TITLE" "$APP_NAME" || echo "") - -# Focus if found, otherwise launch -if [ -n "$WINDOW_ID" ]; then - niri msg action focus-window --id "$WINDOW_ID" >/dev/null 2>&1 -else - "$COMMAND" "${ARGS[@]}" >/dev/null 2>&1 & - disown -fi +if __name__ == "__main__": + main() diff --git a/local-bin/.local/bin/nscratch b/local-bin/.local/bin/nscratch new file mode 100755 index 0000000..ee7aace --- /dev/null +++ b/local-bin/.local/bin/nscratch @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# Adapted from the many ideas shared at: https://github.com/YaLTeR/niri/discussions/329 + +import argparse +import json +import os +import subprocess +import sys + +# the found scratchpad window (id, workspace_id, is_focused, is_floating) +scratch_window = {} +# the focused workspace data +focused_workspace = {} +# the scratchpad workspace name +scratch_workspace = os.getenv("NS_WORKSPACE", "scratch") + +def niri_cmd(cmd_args): + subprocess.run(["niri", "msg", "action"] + cmd_args) + +def move_window_to_scratchpad(window_id, animations): + niri_cmd(["move-window-to-workspace", "--window-id", str(window_id), scratch_workspace, "--focus=false"]) + if animations: + niri_cmd(["move-window-to-tiling", "--id", str(window_id)]) + +def bring_scratchpad_window_to_focus(window_id, args): + niri_cmd(["move-window-to-workspace", "--window-id", str(window_id), str(focused_workspace["idx"])]) + if args.animations and not scratch_window["is_floating"]: + niri_cmd(["move-window-to-floating", "--id", str(window_id)]) + niri_cmd(["focus-window", "--id", str(window_id)]) + +def find_scratch_window(args, windows): + for window in windows: + if window["app_id"] == args.app_id: + scratch_window["id"] = window["id"] + scratch_window["workspace_id"] = window["workspace_id"] + scratch_window["is_focused"] = window["is_focused"] + scratch_window["is_floating"] = window["is_floating"] + break + +def fetch_focused_workspace(): + props = subprocess.run( + ["niri", "msg", "--json", "workspaces"], + capture_output=True, + text=True, + ) + workspaces = json.loads(props.stdout) + + # get the focused workspace + for workspace in workspaces: + if workspace["is_focused"]: + focused_workspace["idx"] = workspace["idx"] + focused_workspace["output"] = workspace["output"] + return workspace["id"] + +def ns(parser): + args = parser.parse_args() + + props = subprocess.run( + ["niri", "msg", "--json", "windows"], + capture_output=True, + text=True, + ) + windows = json.loads(props.stdout) + + find_scratch_window(args, windows) + + # scratchpad does not yet exist, spawn? + if not scratch_window: + if args.spawn: + niri_cmd(["spawn", "--"] + args.spawn.split(' ')) + sys.exit(0) + else: + parser.print_help() + sys.exit(1) + + window_id = scratch_window["id"] + + # the scratchpad window exists and it's focused + if not scratch_window["is_focused"]: + workspace_id = fetch_focused_workspace() + # the window is not in the focused workspace + if scratch_window["workspace_id"] != workspace_id: + bring_scratchpad_window_to_focus(window_id, args) + return + + move_window_to_scratchpad(window_id, args.animations) + +def main(): + parser = argparse.ArgumentParser(prog='nscratch', description='Niri Scratchpad support') + parser.add_argument('-id', '--app-id', required=True, help='The application identifier') + parser.add_argument('-s', '--spawn', help='The process name to spawn when non-existing') + parser.add_argument('-a', '--animations', action='store_true', help='Enable animations') + + ns(parser) + +if __name__ == "__main__": + main() diff --git a/niri/.config/niri/config.d/binds.kdl b/niri/.config/niri/config.d/binds.kdl index 189e5a5..5ee6dfa 100644 --- a/niri/.config/niri/config.d/binds.kdl +++ b/niri/.config/niri/config.d/binds.kdl @@ -4,11 +4,11 @@ binds { Mod+Return { spawn "alacritty"; } Mod+T { spawn-sh "~/.local/bin/niri-dev-launcher ~/Dev"; } Mod+Shift+T { spawn-sh "~/.local/bin/niri-dev-launcher --no-cache ~/Dev"; } - Mod+U { spawn-sh "~/test.py -id 'uy.com.mzunino'"; } - Mod+Shift+U { spawn-sh "alacritty --class=uy.com.mzunino"; } + Mod+U { spawn-sh "~/.local/bin/nscratch -id 'uy.com.mzunino' -s 'alacritty --class=uy.com.mzunino'"; } Mod+D hotkey-overlay-title="Run an Application: fuzzel" { spawn "fuzzel"; } Mod+N { spawn-sh "~/.local/bin/sdm-ui.sh dmenu"; } Mod+E { spawn-sh "~/.local/bin/niri-launch-or-focus thunar"; } + Mod+Shift+N { spawn-sh "~/.local/bin/dotedit"; } Super+Alt+L hotkey-overlay-title="Lock the Screen: swaylock" { spawn "swaylock"; } Super+Alt+S allow-when-locked=true hotkey-overlay-title=null { spawn-sh "pkill orca || exec orca"; } diff --git a/niri/.config/niri/config.d/startup.kdl b/niri/.config/niri/config.d/startup.kdl index 35b461c..40d5217 100644 --- a/niri/.config/niri/config.d/startup.kdl +++ b/niri/.config/niri/config.d/startup.kdl @@ -10,5 +10,4 @@ spawn-sh-at-startup "gtk-launch com.microsoft.Edge.flextop.msedge-cifhbcnohmdccb spawn-sh-at-startup "gtk-launch com.microsoft.Edge.flextop.msedge-faolnafnngnfdaknnbpnkhgohbobgegn-Default" // spawn-sh-at-startup "ghostty --gtk-single-instance=true --quit-after-last-window-closed=false --initial-window=false" // spawn-sh-at-startup "ghostty --class=uy.com.mzunino" -spawn-sh-at-startup "alacritty --class=uy.com.mzunino" spawn-at-startup "niri" "msg" "action" "focus-workspace" "2" diff --git a/niri/.config/niri/noctalia/binds.kdl b/niri/.config/niri/noctalia/binds.kdl index 363dd43..484dd7b 100644 --- a/niri/.config/niri/noctalia/binds.kdl +++ b/niri/.config/niri/noctalia/binds.kdl @@ -1,8 +1,4 @@ binds { - Mod+Space hotkey-overlay-title="Application Launcher" { - spawn "noctalia-shell" "ipc" "call" "launcher" "toggle"; - } - Mod+Y hotkey-overlay-title="Clipboard Manager" { spawn "noctalia-shell" "ipc" "call" "launcher" "clipboard"; } @@ -11,11 +7,6 @@ binds { spawn "noctalia-shell" "ipc" "call" "settings" "toggle"; } - Mod+Shift+N hotkey-overlay-title="Notification Center" { - spawn "noctalia-shell" "ipc" "call" "notifications" "toggleHistory"; - } - - // Audio XF86AudioRaiseVolume allow-when-locked=true { spawn "noctalia-shell" "ipc" "call" "volume" "increase"; diff --git a/tmux/.config/tmux/tmux.conf.backup.20251026_165838 b/tmux/.config/tmux/tmux.conf.backup.20251026_165838 deleted file mode 100644 index e0d320e..0000000 --- a/tmux/.config/tmux/tmux.conf.backup.20251026_165838 +++ /dev/null @@ -1,154 +0,0 @@ -# ============================================== -# === TMUX Configuration === -# ============================================== - -# General Settings -# ---------------- -set -g prefix C-a # Use CTRL+a as our tmux command prefix -unbind C-b # Unbind default prefix -set -g base-index 1 # Start windows numbering at 1 -setw -g pane-base-index 1 # Start pane numbering at 1 -set -g renumber-windows on # Renumber windows when a window is closed -set -s escape-time 1 # Lower the default tmux delay -set -g mouse on # Enable mouse support -setw -g aggressive-resize on # Only resize screen if smaller screen is active -set -g history-limit 10000 # Store 10k lines of history -set-option -g allow-rename off # Disable automatic window renaming -set-option -s set-clipboard on # Enable clipboard support -setw -g mode-keys vi # Use vi keys in copy mode - - -# Key Bindings -# ------------ -bind r source-file $XDG_CONFIG_HOME/tmux/tmux.conf \; display "tmux reloaded!" # Reload config -bind v split-window -h -c "#{pane_current_path}" # Split vertically -bind s split-window -v -c "#{pane_current_path}" # Split horizontally - -# Vim-like pane navigation -bind h select-pane -L -bind j select-pane -D -bind k select-pane -U -bind l select-pane -R - -# Vim-like pane resizing -bind -r H resize-pane -L 5 -bind -r J resize-pane -D 5 -bind -r K resize-pane -U 5 -bind -r L resize-pane -R 5 - -# Vim-like copy mode navigation -bind-key -T copy-mode-vi h send-keys -X cursor-left -bind-key -T copy-mode-vi j send-keys -X cursor-down -bind-key -T copy-mode-vi k send-keys -X cursor-up -bind-key -T copy-mode-vi l send-keys -X cursor-right -bind-key -T copy-mode-vi w send-keys -X next-word -bind-key -T copy-mode-vi b send-keys -X previous-word -bind-key -T copy-mode-vi 0 send-keys -X start-of-line -bind-key -T copy-mode-vi $ send-keys -X end-of-line -bind-key -T copy-mode-vi G send-keys -X history-bottom -bind-key -T copy-mode-vi g send-keys -X history-top -bind-key -T copy-mode-vi / command-prompt -T search -I "#{pane_current_path}" "send -X search-forward \"%%\"" -bind-key -T copy-mode-vi ? command-prompt -T search -I "#{pane_current_path}" "send -X search-backward \"%%\"" -bind-key -T copy-mode-vi n send-keys -X search-again -bind-key -T copy-mode-vi N send-keys -X search-reverse -bind-key -T copy-mode-vi v send-keys -X begin-selection -bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel -bind-key -T copy-mode-vi Escape send-keys -X cancel - -# Move pane to a different window -bind-key m choose-window -F "#{window_index}: #{window_name}" "join-pane -h -t %%" -bind-key M choose-window -F "#{window_index}: #{window_name}" "join-pane -v -t %%" - -# Visual Settings -# --------------- -set -g status-interval 2 -set-option -g visual-activity off -set-option -g visual-bell off -set-option -g visual-silence off -set-window-option -g monitor-activity off -set-option -g bell-action none - -# Terminal Settings -# ----------------- -set -gq allow-passthrough on -set -g visual-activity off - -set -ga update-environment TERM -set -ga update-environment TERM_PROGRAM -set -g default-terminal "tmux-256color" -set -g terminal-overrides ',xterm-256color:Tc' - -# Plugins -# ------- -set -g @plugin 'tmux-plugins/tpm' -set -g @plugin 'tmux-plugins/tmux-sensible' -set -g @plugin 'tmux-plugins/tmux-yank' -set -g @plugin 'catppuccin/tmux#v0.2.0' -set -g @plugin 'tmux-plugins/tmux-battery' # Re-enabled with our fixes -set -g @plugin 'MaximilianGaedig/tmux-filter' - -# Plugin Settings -# --------------- -set -g @yank_selection_mouse 'clipboard' -set -g @yank_selection 'clipboard' -set -g @yank_action 'copy-pipe' # or 'copy-pipe-and-cancel' for the default - -# Catppuccin Theme Configuration -set -g status-position top -set -g @catppuccin_window_left_separator "█" -set -g @catppuccin_window_right_separator "█" -set -g @catppuccin_window_number_position "left" -set -g @catppuccin_window_middle_separator "█ " -set -g @catppuccin_window_default_fill "number" -set -g @catppuccin_window_current_fill "number" -set -g @catppuccin_window_current_text "#W#{?window_zoomed_flag,(🔍),}" -set -g @catppuccin_window_default_text "#W" - -# Determine status modules based on hostname -run-shell "if [ $(hostname) = 'fw' ]; then \ - tmux set -g @catppuccin_status_modules_right 'date_time battery session'; \ - else \ - tmux set -g @catppuccin_status_modules_right 'date_time session'; \ - fi" - -set -g @catppuccin_status_left_separator "" -set -g @catppuccin_status_right_separator " " -set -g @catppuccin_status_right_separator_inverse "yes" -set -g @catppuccin_status_fill "all" -set -g @catppuccin_status_connect_separator "yes" -set -g @catppuccin_date_time_text "%Y-%m-%d %H:%M:%S" - -# Rose Pine Color Scheme -set -g @catppuccin_pane_color "#1e1e2e" -set -g @catppuccin_pane_background_color "#181825" -set -g @catppuccin_window_current_color "#eb6f92" -set -g @catppuccin_window_current_background_color "#f5e0dc" -set -g @catppuccin_window_default_color "#ebbcba" -set -g @catppuccin_window_default_background_color "#f5e0dc" -set -g @catppuccin_session_color "#9ccfd8" -set -g @catppuccin_session_background_color "#f5e0dc" -set -g @catppuccin_directory_color "#c4a7e7" -set -g @catppuccin_directory_background_color "#f5e0dc" -set -g @catppuccin_date_time_color "#f6c177" -set -g @catppuccin_date_time_background_color "#f5e0dc" - -# Battery Icons (Glyphs) -set -g @batt_icon_charge_tier8 '' -set -g @batt_icon_charge_tier7 '' -set -g @batt_icon_charge_tier6 '' -set -g @batt_icon_charge_tier5 '' -set -g @batt_icon_charge_tier4 '' -set -g @batt_icon_charge_tier3 '' -set -g @batt_icon_charge_tier2 '' -set -g @batt_icon_charge_tier1 '' - -# Plugin Manager -set-environment -g TMUX_PLUGIN_MANAGER_PATH "$XDG_DATA_HOME/tmux/plugins" - -# Install TPM if not already installed -if "test ! -d ~/.local/share/tmux/plugins/tpm" \ - "run 'git clone https://github.com/tmux-plugins/tpm ~/.local/share/tmux/plugins/tpm'" - -# Initialize TPM (keep this line at the very bottom of tmux.conf) -run '~/.local/share/tmux/plugins/tpm/tpm' - diff --git a/tmux/.config/tmux/tmux.conf.backup.20251027_115806 b/tmux/.config/tmux/tmux.conf.backup.20251027_115806 deleted file mode 100644 index f28a4d9..0000000 --- a/tmux/.config/tmux/tmux.conf.backup.20251027_115806 +++ /dev/null @@ -1,139 +0,0 @@ -# ============================================== -# === TMUX Configuration === -# ============================================== - -# General Settings -# ---------------- -set -g prefix C-a # Use CTRL+a as our tmux command prefix -unbind C-b # Unbind default prefix -set -g base-index 1 # Start windows numbering at 1 -setw -g pane-base-index 1 # Start pane numbering at 1 -set -g renumber-windows on # Renumber windows when a window is closed -set -s escape-time 1 # Lower the default tmux delay -set -g mouse on # Enable mouse support -setw -g aggressive-resize on # Only resize screen if smaller screen is active -set -g history-limit 10000 # Store 10k lines of history -set-option -g allow-rename off # Disable automatic window renaming -set-option -s set-clipboard on # Enable clipboard support -setw -g mode-keys vi # Use vi keys in copy mode - - -# Key Bindings -# ------------ -bind r source-file $XDG_CONFIG_HOME/tmux/tmux.conf \; display "tmux reloaded!" # Reload config -bind v split-window -h -c "#{pane_current_path}" # Split vertically -bind s split-window -v -c "#{pane_current_path}" # Split horizontally - -# Vim-like pane navigation -bind h select-pane -L -bind j select-pane -D -bind k select-pane -U -bind l select-pane -R - -# Vim-like pane resizing -bind -r H resize-pane -L 5 -bind -r J resize-pane -D 5 -bind -r K resize-pane -U 5 -bind -r L resize-pane -R 5 - -# Vim-like copy mode navigation -bind-key -T copy-mode-vi h send-keys -X cursor-left -bind-key -T copy-mode-vi j send-keys -X cursor-down -bind-key -T copy-mode-vi k send-keys -X cursor-up -bind-key -T copy-mode-vi l send-keys -X cursor-right -bind-key -T copy-mode-vi w send-keys -X next-word -bind-key -T copy-mode-vi b send-keys -X previous-word -bind-key -T copy-mode-vi 0 send-keys -X start-of-line -bind-key -T copy-mode-vi $ send-keys -X end-of-line -bind-key -T copy-mode-vi G send-keys -X history-bottom -bind-key -T copy-mode-vi g send-keys -X history-top -bind-key -T copy-mode-vi / command-prompt -T search -I "#{pane_current_path}" "send -X search-forward \"%%\"" -bind-key -T copy-mode-vi ? command-prompt -T search -I "#{pane_current_path}" "send -X search-backward \"%%\"" -bind-key -T copy-mode-vi n send-keys -X search-again -bind-key -T copy-mode-vi N send-keys -X search-reverse -bind-key -T copy-mode-vi v send-keys -X begin-selection -bind-key -T copy-mode-vi y send-keys -X copy-selection-and-cancel -bind-key -T copy-mode-vi Escape send-keys -X cancel - -# Move pane to a different window -bind-key m choose-window -F "#{window_index}: #{window_name}" "join-pane -h -t %%" -bind-key M choose-window -F "#{window_index}: #{window_name}" "join-pane -v -t %%" - -# Visual Settings -# --------------- -set -g status-interval 2 -set-option -g visual-activity off -set-option -g visual-bell off -set-option -g visual-silence off -set-window-option -g monitor-activity off -set-option -g bell-action none - -# Terminal Settings -# ----------------- -set -gq allow-passthrough on -set -g visual-activity off - -set -ga update-environment TERM -set -ga update-environment TERM_PROGRAM -set -g default-terminal "tmux-256color" -set -g terminal-overrides ',xterm-256color:Tc' - -# Plugins -# ------- -set -g @plugin 'tmux-plugins/tpm' -set -g @plugin 'tmux-plugins/tmux-sensible' -set -g @plugin 'tmux-plugins/tmux-yank' -set -g @plugin 'rose-pine/tmux' -set -g @plugin 'tmux-plugins/tmux-battery' # Re-enabled with our fixes -set -g @plugin 'MaximilianGaedig/tmux-filter' - -# Plugin Settings -# --------------- -set -g @yank_selection_mouse 'clipboard' -set -g @yank_selection 'clipboard' -set -g @yank_action 'copy-pipe' # or 'copy-pipe-and-cancel' for the default - -# Rosé Pine Theme Configuration -# ----------------------------- -set -g status-position top -set -g @rose_pine_variant 'main' # Options are 'main', 'moon' or 'dawn' - -# Enable extra modules -set -g @rose_pine_host 'on' # Enables hostname in the status bar -set -g @rose_pine_hostname_short 'on' # Makes the hostname shorter by using tmux's '#h' format -set -g @rose_pine_date_time '%Y-%m-%d %H:%M:%S' # Date/time format -set -g @rose_pine_user 'on' # Turn on the username component in the statusbar -set -g @rose_pine_directory 'on' # Turn on the current folder component in the status bar -set -g @rose_pine_bar_bg_disable 'on' # Disables background color, for transparent terminal emulators - -# Battery module - determine if battery is available -run-shell "if [ -d /sys/class/power_supply/BAT* ]; then \ - tmux set -g @rose_pine_status_right_append_section '#{tmux_battery_status_bg}'; \ - fi" - -# Battery Icons (Glyphs) -set -g @batt_icon_charge_tier8 '' -set -g @batt_icon_charge_tier7 '' -set -g @batt_icon_charge_tier6 '' -set -g @batt_icon_charge_tier5 '' -set -g @batt_icon_charge_tier4 '' -set -g @batt_icon_charge_tier3 '' -set -g @batt_icon_charge_tier2 '' -set -g @batt_icon_charge_tier1 '' - -# Optional customization options -# set -g @rose_pine_left_separator ' > ' -# set -g @rose_pine_right_separator ' < ' -# set -g @rose_pine_field_separator ' | ' -set -g @rose_pine_window_separator ' #{?window_zoomed_flag, ,}' # Replaces the default `:` between the window number and name - -# Plugin Manager -set-environment -g TMUX_PLUGIN_MANAGER_PATH "$XDG_DATA_HOME/tmux/plugins" - -# Install TPM if not already installed -if "test ! -d ~/.local/share/tmux/plugins/tpm" \ - "run 'git clone https://github.com/tmux-plugins/tpm ~/.local/share/tmux/plugins/tpm'" - -# Initialize TPM (keep this line at the very bottom of tmux.conf) -run '~/.local/share/tmux/plugins/tpm/tpm' - diff --git a/zsh/.config/zsh/init.zsh b/zsh/.config/zsh/init.zsh index 1291c57..f36e9c1 100644 --- a/zsh/.config/zsh/init.zsh +++ b/zsh/.config/zsh/init.zsh @@ -74,7 +74,7 @@ zsh-defer source "$PLUGIN_DIR/zsh-history-substring-search/zsh-history-substring source "$PLUGIN_DIR/minimal/minimal.zsh" source "$ZDOTDIR/opts.zsh" -source "$ZDOTDIR/rose-pine.sh" +# source "$ZDOTDIR/rose-pine.sh" source "$ZDOTDIR/tmux.zsh" @@ -104,11 +104,11 @@ _compile_zsh_files() { "$ZDOTDIR/pnpm.zsh" "$ZDOTDIR/mise.zsh" ) - + for func_file in "$ZDOTDIR/functions"/*.zsh; do files+=("$func_file") done - + for file in "${files[@]}"; do [[ ! -f "$file" ]] && continue local zwc="${file}.zwc"