diff --git a/config/scripts/.local/scripts/change-colortheme b/config/scripts/.local/scripts/change-colortheme index 9dcf4e6..f1a2774 100755 --- a/config/scripts/.local/scripts/change-colortheme +++ b/config/scripts/.local/scripts/change-colortheme @@ -7,12 +7,12 @@ # Requirements: # - colorthief (python3 package) # too lazy to implement color extraction myself :D -import os -import sys import argparse +import os import subprocess -from pathlib import Path +import sys from concurrent.futures import ThreadPoolExecutor +from pathlib import Path from threading import Lock MAX_WORKERS = 8 @@ -112,7 +112,7 @@ def extract_color(image_path: str) -> str: max_score = -1.0 for color in palette: - h, s, v = rgb2hsv(*color) + _, s, v = rgb2hsv(*color) # Filter out undesirable colors # Too dark @@ -140,7 +140,7 @@ def match_color(color: str, palette: dict[str, str]) -> str: """Match the given #rrggbb color to the closest flavor in the palette.""" color = color.lower().strip().removeprefix("#") target_rgb = hex2rgb(color) - target_h, target_s, target_v = rgb2hsv(*target_rgb) + target_h, target_s, _ = rgb2hsv(*target_rgb) # Warn if not representative (nearly grayscale) if target_s < 5: @@ -150,7 +150,7 @@ def match_color(color: str, palette: dict[str, str]) -> str: def get_weighted_distance(hex_val: str) -> float: p_rgb = hex2rgb(hex_val) - p_h, p_s, p_v = rgb2hsv(*p_rgb) + p_h, _, _ = rgb2hsv(*p_rgb) # RGB distance with weighting rmean = (target_rgb[0] + p_rgb[0]) / 2 diff --git a/config/scripts/.local/scripts/led-monitor b/config/scripts/.local/scripts/led-monitor index 146425e..c71ff86 100755 --- a/config/scripts/.local/scripts/led-monitor +++ b/config/scripts/.local/scripts/led-monitor @@ -1,21 +1,18 @@ #!/usr/bin/env python3 -import sys -import glob import argparse -import select +import glob import os +import select import struct +import sys -EVENT_FORMAT = '@llHHi' +EVENT_FORMAT = "@llHHi" EVENT_SIZE = struct.calcsize(EVENT_FORMAT) EV_LED = 0x11 -LED_CODES = { - 'numlock': 0x00, - 'capslock': 0x01, - 'scrolllock': 0x02 -} +LED_CODES = {"numlock": 0x00, "capslock": 0x01, "scrolllock": 0x02} + def get_led_state(led_type: str) -> int: pattern = f"/sys/class/leds/*::{led_type}/brightness" @@ -24,24 +21,26 @@ def get_led_state(led_type: str) -> int: return 0 for path in paths: try: - with open(path, 'r') as f: + with open(path, "r") as f: if int(f.read().strip()) > 0: return 1 - except (IOError, ValueError, OSError): + except (ValueError, OSError): continue return 0 + def has_led_capability(event_path: str) -> bool: try: basename = os.path.basename(event_path) cap_path = f"/sys/class/input/{basename}/device/capabilities/led" if os.path.exists(cap_path): - with open(cap_path, 'r') as f: + with open(cap_path, "r") as f: return f.read().strip() != "0" except OSError: pass return False + class DeviceMonitor: def __init__(self, target_led: str): self.target_led = target_led @@ -64,7 +63,7 @@ class DeviceMonitor: def scan_devices(self) -> None: current_fds = set(self.active_fds.keys()) - paths = glob.glob('/dev/input/event*') + paths = glob.glob("/dev/input/event*") found_fds = set() for path in paths: @@ -117,12 +116,17 @@ class DeviceMonitor: events_count = len(data) // EVENT_SIZE for i in range(events_count): chunk = data[i * EVENT_SIZE : (i + 1) * EVENT_SIZE] - _, _, ev_type, ev_code, ev_value = struct.unpack(EVENT_FORMAT, chunk) + _, _, ev_type, ev_code, ev_value = struct.unpack( + EVENT_FORMAT, chunk + ) - if ev_type == EV_LED and ev_code == self.target_led_code: - if ev_value != self.last_state: - self.emit_state(ev_value) - self.last_state = ev_value + if ( + ev_type == EV_LED + and ev_code == self.target_led_code + and ev_value != self.last_state + ): + self.emit_state(ev_value) + self.last_state = ev_value except BlockingIOError: pass @@ -157,19 +161,22 @@ class DeviceMonitor: except Exception: pass + def main(): parser = argparse.ArgumentParser(description="Zero-polling keyboard LED monitor.") parser.add_argument( - '-l', '--led', + "-l", + "--led", type=str, - default='capslock', - choices=['capslock', 'numlock', 'scrolllock'], - help="Target LED to monitor" + default="capslock", + choices=["capslock", "numlock", "scrolllock"], + help="Target LED to monitor", ) args = parser.parse_args() monitor = DeviceMonitor(args.led) monitor.run() + if __name__ == "__main__": main() diff --git a/config/scripts/.local/scripts/mtu-test b/config/scripts/.local/scripts/mtu-test index 4734781..2892f9e 100755 --- a/config/scripts/.local/scripts/mtu-test +++ b/config/scripts/.local/scripts/mtu-test @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -import sys import subprocess +import sys def has_ping6() -> bool: diff --git a/config/scripts/.local/scripts/screenshot-script b/config/scripts/.local/scripts/screenshot-script index a771985..8453906 100755 --- a/config/scripts/.local/scripts/screenshot-script +++ b/config/scripts/.local/scripts/screenshot-script @@ -11,12 +11,12 @@ # - glib bindings for python import argparse +import fcntl import subprocess import time -import fcntl -from os import environ from datetime import datetime from enum import Enum +from os import environ from pathlib import Path from shutil import copy2 @@ -52,7 +52,7 @@ def take_screenshot(filepath: Path, typeStr: str): lockFD = open("/tmp/screenshot-script.lock", "w") try: fcntl.flock(lockFD, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError: + except OSError: lockFD.close() raise RuntimeError("Another screenshot is currently being taken.") @@ -66,11 +66,11 @@ def take_screenshot(filepath: Path, typeStr: str): ScreenshotType.AREA: f"hyprshot -z -m region -o {SCREENSHOT_DIR} -f ", ScreenshotType.WINDOW: f"hyprshot -z -m window -o {SCREENSHOT_DIR} -f ", } - process = subprocess.run(f"{cmd[type]}{filepath.name}", shell=True) - if process.returncode != 0: - raise RuntimeError("Failed to take screenshot: hyprshot command failed.") + subprocess.run(f"{cmd[type]}{filepath.name}", shell=True, check=True) if not wait_until_file_exists(filepath): - raise RuntimeError("Failed to take screenshot: output file not found after hyprshot command.") + raise RuntimeError( + "Failed to take screenshot: output file not found after hyprshot command." + ) elif "niri" in currentDesktop: niriScreenshotPath = SCREENSHOT_DIR / ".niri_screenshot.png" @@ -79,23 +79,24 @@ def take_screenshot(filepath: Path, typeStr: str): # and the selection ui is drawn inside of niri without its state exposed to external programs. # so we use grim + slurp for area mode and niri's built-in commands for others. ScreenshotType.FULL: "niri msg action screenshot-screen", - ScreenshotType.AREA: f" grim -g \"$(slurp)\" -t png {niriScreenshotPath} && cat {niriScreenshotPath} | wl-copy", + ScreenshotType.AREA: f' grim -g "$(slurp)" -t png {niriScreenshotPath} && cat {niriScreenshotPath} | wl-copy', ScreenshotType.WINDOW: "niri msg action screenshot-window", } if niriScreenshotPath.exists(): niriScreenshotPath.unlink() - process = subprocess.run(cmd[type], shell=True) - if process.returncode != 0: - print(process.returncode) - raise RuntimeError("Failed to take screenshot: niri screenshot command failed.") + subprocess.run(cmd[type], shell=True, check=True) if wait_until_file_exists(niriScreenshotPath): # niriScreenshotPath.rename(filepath) copy2(niriScreenshotPath, filepath) else: - raise RuntimeError("Failed to take screenshot: output file not found after niri command.") + raise RuntimeError( + "Failed to take screenshot: output file not found after niri command." + ) if not wait_until_file_exists(filepath): - raise RuntimeError("Failed to take screenshot: output file not found after copying.") + raise RuntimeError( + "Failed to take screenshot: output file not found after copying." + ) else: # print("Unsupported desktop environment.") @@ -106,12 +107,11 @@ def take_screenshot(filepath: Path, typeStr: str): def edit_screenshot(filepath: Path): - subprocess.run(f"gradia {filepath}", shell=True) - # subprocess.run(f"spectacle -l --edit-existing {filepath}", shell=True) + subprocess.run(f"gradia {filepath}", shell=True, check=True) def gen_file_name(prefix="screenshot", ext=".png"): - timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + timestamp = datetime.now(tz=datetime.now().astimezone().tzinfo).strftime("%Y-%m-%d_%H-%M-%S") return f"{prefix}_{timestamp}{ext}" @@ -135,7 +135,7 @@ if __name__ == "__main__": args = parser.parse_args() filepath: Path = Path() - if not args.type == ScreenshotType.EDIT.value: + if args.type != ScreenshotType.EDIT.value: # file path SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True) filename = gen_file_name() @@ -166,7 +166,6 @@ if __name__ == "__main__": # callback on close def close_callback(n): - global editing if not editing: loop.quit() @@ -175,6 +174,7 @@ if __name__ == "__main__": "Click to edit", str(filepath), ) + n.set_hint("transient", GLib.Variant("i", 1)) n.add_action( # so default action is used, which will be triggered on simply clicking the notification card "default", @@ -193,5 +193,6 @@ if __name__ == "__main__": n = Notify.Notification.new( "Screenshot Error", str(e), + history=False, ) n.show() diff --git a/config/scripts/.local/scripts/xgo b/config/scripts/.local/scripts/xgo index 0bb3e87..a617c56 100755 --- a/config/scripts/.local/scripts/xgo +++ b/config/scripts/.local/scripts/xgo @@ -7,8 +7,8 @@ import shlex import shutil import subprocess import sys -import tempfile import tarfile +import tempfile import zipfile from pathlib import Path @@ -20,14 +20,15 @@ SIZE_PATTERN = re.compile(r"^[1-9][0-9]*[KMGkmg]?$") def download_to(url: str, dest_dir: Path) -> Path: # Import when needed import requests - local_filename = url.split('/')[-1] + + local_filename = url.split("/")[-1] dest_path = dest_dir / local_filename print(f"Downloading '{url}' to '{dest_path}'...") try: with requests.get(url, stream=True) as r: r.raise_for_status() - with open(dest_path, 'wb') as f: + with open(dest_path, "wb") as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) except Exception as e: @@ -59,7 +60,9 @@ def get_strip_count(archive_path: Path, is_tar: bool, is_zip: bool) -> int: if is_tar: cmd = ["tar", "-tf", str(archive_path)] - with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, bufsize=1) as proc: + with subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, bufsize=1 + ) as proc: if proc is None or proc.stdout is None: sys.exit("Error: Failed to list archive contents.") for line in proc.stdout: @@ -67,7 +70,7 @@ def get_strip_count(archive_path: Path, is_tar: bool, is_zip: bool) -> int: if not line: continue - current_top = line.split('/', 1)[0] + current_top = line.split("/", 1)[0] if top_level is None: top_level = current_top @@ -78,9 +81,9 @@ def get_strip_count(archive_path: Path, is_tar: bool, is_zip: bool) -> int: proc.wait() elif is_zip: - with zipfile.ZipFile(archive_path, 'r') as zf: + with zipfile.ZipFile(archive_path, "r") as zf: for name in zf.namelist(): - current_top = name.split('/', 1)[0] + current_top = name.split("/", 1)[0] if top_level is None: top_level = current_top elif top_level != current_top: @@ -101,29 +104,56 @@ def extract_archive(archive_path: Path, dest_dir: Path, strip_components: int = elif zipfile.is_zipfile(archive_path): if strip_components > 0: with tempfile.TemporaryDirectory() as tmp_ext: - subprocess.run(["unzip", "-q", str(archive_path), "-d", tmp_ext], check=True) + subprocess.run( + ["unzip", "-q", str(archive_path), "-d", tmp_ext], check=True + ) top_dir = next(Path(tmp_ext).iterdir()) for item in top_dir.iterdir(): shutil.move(str(item), str(dest_dir)) else: - subprocess.run(["unzip", "-q", str(archive_path), "-d", str(dest_dir)], check=True) + subprocess.run( + ["unzip", "-q", str(archive_path), "-d", str(dest_dir)], check=True + ) def main(): - parser = argparse.ArgumentParser(description="Extract an archive to a directory and spawn a shell.") + parser = argparse.ArgumentParser( + description="Extract an archive to a directory and spawn a shell." + ) parser.add_argument("archive", type=str, help="Path to the tarball or zip file") - parser.add_argument("--exec", "-e", dest="cmd", default=DEFAULT_SHELL, - help=f"Command to spawn (default: '{DEFAULT_SHELL}')") - parser.add_argument("--target", "-t", type=Path, help="Target directory for extraction.") - parser.add_argument("--disable-tmpfs", "-d", action="store_true", help="Disable tmpfs mounting, extract directly to dir") - parser.add_argument("--size", "-s", default=DEFAULT_TMPFS_SIZE, - help=f"Size of the tmpfs if used (default: {DEFAULT_TMPFS_SIZE})") - parser.add_argument("--no-cleanup", "-n", action="store_true", help="Disable cleanup (unmount and remove dir)") + parser.add_argument( + "--exec", + "-e", + dest="cmd", + default=DEFAULT_SHELL, + help=f"Command to spawn (default: '{DEFAULT_SHELL}')", + ) + parser.add_argument( + "--target", "-t", type=Path, help="Target directory for extraction." + ) + parser.add_argument( + "--disable-tmpfs", + "-d", + action="store_true", + help="Disable tmpfs mounting, extract directly to dir", + ) + parser.add_argument( + "--size", + "-s", + default=DEFAULT_TMPFS_SIZE, + help=f"Size of the tmpfs if used (default: {DEFAULT_TMPFS_SIZE})", + ) + parser.add_argument( + "--no-cleanup", + "-n", + action="store_true", + help="Disable cleanup (unmount and remove dir)", + ) args = parser.parse_args() archive = args.archive.strip() - if archive.startswith(('https://', 'http://')): + if archive.startswith(("https://", "http://")): archive = download_to(archive, Path.cwd()) else: archive = Path(args.archive).resolve() @@ -132,7 +162,9 @@ def main(): sys.exit(f"Error: Archive '{archive}' does not exist or is not a file.") if not SIZE_PATTERN.match(args.size): - sys.exit(f"Error: Invalid size format '{args.size}'. Expected format like '4G', '500M'.") + sys.exit( + f"Error: Invalid size format '{args.size}'. Expected format like '4G', '500M'." + ) is_tar = tarfile.is_tarfile(archive) is_zip = zipfile.is_zipfile(archive) @@ -155,14 +187,26 @@ def main(): else: target_dir.mkdir(parents=True, exist_ok=True) - strip_components = strip_components = get_strip_count(archive, is_tar, is_zip) + strip_components = get_strip_count(archive, is_tar, is_zip) try: if use_tmpfs: print(f"Mounting tmpfs at '{target_dir}' with size {args.size}...") uid, gid = os.getuid(), os.getgid() mount_opts = f"size={args.size},uid={uid},gid={gid},mode=0700" - subprocess.run(["sudo", "mount", "-t", "tmpfs", "-o", mount_opts, "tmpfs", str(target_dir)], check=True) + subprocess.run( + [ + "sudo", + "mount", + "-t", + "tmpfs", + "-o", + mount_opts, + "tmpfs", + str(target_dir), + ], + check=True, + ) print(f"Extracting '{archive}' to '{target_dir}'...") extract_archive(archive, target_dir, strip_components) @@ -170,19 +214,26 @@ def main(): print(f"Spawning '{args.cmd}' in {target_dir}...") parsed_cmd = shlex.split(args.cmd) - subprocess.run(parsed_cmd, cwd=str(target_dir)) + subprocess.run(parsed_cmd, cwd=str(target_dir), check=True) finally: if do_cleanup: print("Cleaning up...") if use_tmpfs: - subprocess.run(["sudo", "umount", str(target_dir)], stderr=subprocess.DEVNULL) + subprocess.run( + ["sudo", "umount", str(target_dir)], + stderr=subprocess.DEVNULL, + check=True, + ) if not dir_existed_before: try: shutil.rmtree(target_dir) except PermissionError: - if shutil.which("sudo"): - subprocess.run(["sudo", "rm", "-rf", str(target_dir)], stderr=subprocess.DEVNULL) + subprocess.run( + ["sudo", "rm", "-rf", str(target_dir)], + stderr=subprocess.DEVNULL, + check=True, + ) else: print(f"Cleanup disabled. Extracted contents are left at: {target_dir}") diff --git a/config/shell/.config/zsh/conf.d/10-done.zsh b/config/shell/.config/zsh/conf.d/10-done.zsh index 8868785..93d566a 100644 --- a/config/shell/.config/zsh/conf.d/10-done.zsh +++ b/config/shell/.config/zsh/conf.d/10-done.zsh @@ -7,7 +7,7 @@ zmodload zsh/datetime : ${uy_done_min_cmd_duration:=10} -: ${uy_done_exclude:='^(nvim|helix|hx|vim|vi|nano|less|more|man|ssh|top|htop|btop|sudoedit)$'} +: ${uy_done_exclude:='^(nvim|helix|hx|vim|vi|nano|less|more|man|ssh|top|htop|btop|sudoedit|yazi)$'} # Returns the id in $REPLY, empty if there is none or niri did not answer. if zmodload zsh/net/socket 2>/dev/null; then diff --git a/config/yazi/.config/yazi/package.toml b/config/yazi/.config/yazi/package.toml index d304009..b17bf9c 100644 --- a/config/yazi/.config/yazi/package.toml +++ b/config/yazi/.config/yazi/package.toml @@ -1,11 +1,11 @@ [[plugin.deps]] use = "yazi-rs/plugins:git" -rev = "4c63ed3" +rev = "9014ed2" hash = "88e56a64b7ce7c4314427452343fef17" [[plugin.deps]] use = "yazi-rs/plugins:smart-enter" -rev = "4c63ed3" +rev = "9014ed2" hash = "187cc58ba7ac3befd49c342129e6f1b6" [[plugin.deps]]