diff options
Diffstat (limited to 'hypr/scripts/waybar_peek.py')
| -rwxr-xr-x | hypr/scripts/waybar_peek.py | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/hypr/scripts/waybar_peek.py b/hypr/scripts/waybar_peek.py new file mode 100755 index 0000000..227b425 --- /dev/null +++ b/hypr/scripts/waybar_peek.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +""" +Waybar Peek - Auto-hide for Hyprland (Multi-monitor) +Shows waybar when cursor is at top edge, hides when workspace has windows. + +Toggle auto-hide: pkill -HUP -f waybar_peek +""" + +import json +import os +import signal +import socket +import time +from pathlib import Path + +# Configuration +PIXEL_THRESHOLD = 5 # Show bar when within 5px of top +PIXEL_THRESHOLD_HIDE = 50 # Hide when cursor goes below 50px +POLL_INTERVAL = 0.1 # Poll every 100ms + +class WaybarPeek: + def __init__(self): + self.xdg_runtime = os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}") + self.hypr_sig = os.environ.get("HYPRLAND_INSTANCE_SIGNATURE", "") + self.socket_path = f"{self.xdg_runtime}/hypr/{self.hypr_sig}/.socket.sock" + + self.cursor_at_top = False + self.last_visibility = None + self.waybar_pid = None + self.enabled = True # Auto-hide enabled by default + + # Setup signal handler for toggle + signal.signal(signal.SIGHUP, self.toggle_handler) + + def toggle_handler(self, signum, frame): + """Handle SIGHUP to toggle auto-hide on/off""" + self.enabled = not self.enabled + status = "enabled" if self.enabled else "disabled" + print(f"Auto-hide {status}") + + if not self.enabled: + # When disabled, always show the bar + self.set_waybar_visible(True) + self.last_visibility = True + else: + # When re-enabled, force state recalculation + self.last_visibility = None + + def hypr_query(self, cmd: str) -> str: + """Query Hyprland via socket""" + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(self.socket_path) + sock.send(cmd.encode()) + response = b"" + while True: + chunk = sock.recv(4096) + if not chunk: + break + response += chunk + sock.close() + return response.decode() + except Exception: + return "" + + def get_cursor_pos(self) -> tuple: + """Get cursor position as (x, y)""" + try: + data = json.loads(self.hypr_query("j/cursorpos")) + return (data.get("x", 0), data.get("y", 0)) + except Exception: + return (0, 0) + + def get_monitors(self) -> list: + """Get all monitors with their bounds""" + try: + return json.loads(self.hypr_query("j/monitors")) + except Exception: + return [] + + def check_windows(self) -> bool: + """Check if active workspace has windows""" + try: + data = json.loads(self.hypr_query("j/activeworkspace")) + return data.get("windows", 0) > 0 + except Exception: + return False + + def find_waybar_pid(self) -> int: + """Find waybar process ID""" + try: + for entry in Path("/proc").iterdir(): + if not entry.is_dir() or not entry.name.isdigit(): + continue + comm_file = entry / "comm" + if comm_file.exists(): + comm = comm_file.read_text().strip() + if comm in ("waybar", ".waybar-wrapped"): + return int(entry.name) + except Exception: + pass + return None + + def set_waybar_visible(self, visible: bool) -> bool: + """Send signal to waybar to show/hide""" + if self.waybar_pid is None: + self.waybar_pid = self.find_waybar_pid() + + if self.waybar_pid is None: + return False + + try: + sig = signal.SIGUSR2 if visible else signal.SIGUSR1 + os.kill(self.waybar_pid, sig) + return True + except ProcessLookupError: + self.waybar_pid = self.find_waybar_pid() + if self.waybar_pid: + try: + sig = signal.SIGUSR2 if visible else signal.SIGUSR1 + os.kill(self.waybar_pid, sig) + return True + except Exception: + pass + except Exception: + pass + return False + + def is_cursor_at_top(self) -> bool: + """Check if cursor is at top edge of any monitor""" + cursor_x, cursor_y = self.get_cursor_pos() + monitors = self.get_monitors() + + for m in monitors: + mx, my = m.get("x", 0), m.get("y", 0) + mw, mh = m.get("width", 0), m.get("height", 0) + + # Check if cursor is on this monitor + if mx <= cursor_x <= mx + mw and my <= cursor_y <= my + mh: + # Calculate local Y position relative to this monitor + local_y = cursor_y - my + + # Use different thresholds based on current state + threshold = PIXEL_THRESHOLD_HIDE if self.cursor_at_top else PIXEL_THRESHOLD + return local_y <= threshold + + return False + + def run(self): + """Main loop""" + print(f"waybar-peek started (PID: {os.getpid()})") + print(f"Socket: {self.socket_path}") + print(f"Toggle auto-hide: pkill -HUP -f waybar_peek") + + # Initial state + windows_opened = self.check_windows() + self.last_visibility = not windows_opened + + while True: + try: + # Skip auto-hide logic if disabled + if not self.enabled: + time.sleep(POLL_INTERVAL) + continue + + # Check cursor position + new_cursor_at_top = self.is_cursor_at_top() + if new_cursor_at_top != self.cursor_at_top: + self.cursor_at_top = new_cursor_at_top + + # Check windows + windows_opened = self.check_windows() + + # Determine visibility: show if cursor at top OR no windows + should_be_visible = self.cursor_at_top or not windows_opened + + # Update waybar if state changed + if should_be_visible != self.last_visibility: + self.set_waybar_visible(should_be_visible) + self.last_visibility = should_be_visible + + time.sleep(POLL_INTERVAL) + + except KeyboardInterrupt: + print("\nExiting...") + break + except Exception as e: + print(f"Error: {e}") + time.sleep(1) + +if __name__ == "__main__": + app = WaybarPeek() + app.run() |
