From 9ca97466a17d243ec76ddabee679b4d8bf53a3b2 Mon Sep 17 00:00:00 2001 From: Leni Aniva Date: Sun, 2 Jun 2024 06:14:20 -0700 Subject: [PATCH] feat: Display hyprland title in eww. Doc update 1. Replaced `grimblast` with `flameshot` 2. Updated document and removed waybar 3. Updated eww stylesheet --- README-Wayland.org | 8 +- eww/bar/eww.scss | 17 ++- eww/bar/eww.yuck | 48 +++++---- eww/bar/scripts/compositor-control | 167 +++++++++++++++++++++++++++++ eww/bar/scripts/get-workspaces | 128 ---------------------- hypr/hyprland.conf | 9 +- 6 files changed, 218 insertions(+), 159 deletions(-) create mode 100755 eww/bar/scripts/compositor-control delete mode 100755 eww/bar/scripts/get-workspaces diff --git a/README-Wayland.org b/README-Wayland.org index 876bff3..e26602b 100644 --- a/README-Wayland.org +++ b/README-Wayland.org @@ -22,8 +22,7 @@ recommended. * Status Bar and Tray -- ~waybar~: Status bar, use ~waybar-hyprland-git~ AUR repo to enable clicking! -- ~eww-wayland~: Another status bar, requires ~socat~ to listen for hyprland events. +- ~eww-wayland~: Requires ~socat~ to listen for hyprland events. * Sway @@ -57,7 +56,6 @@ This is another choice of a compositor. Note: Setup does not work with NVIDIA GPU without some tweaks. - [[https://wiki.archlinux.org/title/Hyprland][hyprland]]: window manager - Until [[https://github.com/elkowar/eww/issues/111][tray support]] has been added to ~eww~ in a stable manner, tray will be handled with waybar. - ~hyprpaper~: Wallpaper engine The command to set a wallpaper: @@ -79,12 +77,14 @@ convert $IN \ #+end_src - ~swaync~: Notification server - ~wofi~: finding programs, drop in replacement for ~rofi~ -- ~grimblast~: Screenshot engine - ~dolphin~: File explorer - ~swaylock-effects~: A simple lockscreen - ~wl-clipboard~: Provides copying - ~blueman~: Bluetooth connector +- ~grim~: Screenshot engine +- ~flameshot~: Screenshot utility; On Arch Linux, install ~flameshot-git~. + ** Configuration - ~lxappearance~: Used to configure GTK3 themes diff --git a/eww/bar/eww.scss b/eww/bar/eww.scss index 2ca1a14..9ae4ecd 100644 --- a/eww/bar/eww.scss +++ b/eww/bar/eww.scss @@ -32,7 +32,7 @@ $temperature_critical: #ff6666; $battery: #b6b2b3; -* { all: unset; } +//* { all: unset; } .vbar { background-color: transparent; @@ -52,13 +52,16 @@ $battery: #b6b2b3; } .launcher { + all: unset; font-size: 40px; - margin: -7px -5px 15px -5px; + margin: -2px -5px 5px -3px; } box .workspaces { + all: unset; border-radius: 10px; button { + all: unset; font-size: 30px; padding-left: 2px; margin: -5px -5px -5px -5px; @@ -111,7 +114,13 @@ box .weather { color: mix($weather_end, $weather, 100%); } } -.audio .audio-scale { +.audio-scale { + color: $audio; +} +.audio { + all: unset; + border: none; + background-color: transparent; color: $audio; } scale { @@ -144,6 +153,8 @@ box .clock { font-family: "Noto Serif"; padding-left: 5px; padding-right: 5px; + min-height: 15px; + min-width: 15px; color: $media; border: none; } diff --git a/eww/bar/eww.yuck b/eww/bar/eww.yuck index 99d1fed..884a2db 100644 --- a/eww/bar/eww.yuck +++ b/eww/bar/eww.yuck @@ -15,9 +15,12 @@ (centerbox :orientation "v" (box :orientation "v" :space-evenly false :valign "start" - (box :class "launcher" :valign "start" :width "10px" :height "10px" "☯") - ;(label :class "launcher" :valign "start" :width "20px" :height "20px" :unindent true - ; :text "☯") + ;(box :class "launcher" :valign "start" :width "10px" :height "10px" "☯") + (label + :class "launcher" :valign "start" :width "20px" :height "20px" + :unindent true + :tooltip active-window-title + :text "☯") (widget-workspaces) ) (widget-media) @@ -66,7 +69,9 @@ "${workspace.text}")) )) (deflisten workspaces :initial "[]" - "scripts/get-workspaces") + "scripts/compositor-control workspaces") +(deflisten active-window-title :initial "" + "scripts/compositor-control active-title") ; Weather @@ -103,35 +108,33 @@ :class "media" :width 20 :height 20 - :thickness 4 - :tooltip {music_status_listener} - :value {music_position} - :visible {music_listener != ""} + :thickness 5 + ;:tooltip {music_listener_position} + :tooltip "${music_position} / ${music_length}" + :value {100.0 * music_position / (music_length / 1000000.0)} + :visible {music_listener != "" && music_length != 1} ) + ; Add play and pause buttons here? (label - :angle 90 + :angle 270 :xalign 0.5 :yalign 0.5 - :text {music_listener != "" ? "『${music_listener}』" : ""}) + :justify "center" + :text {music_listener != "" ? "『${music_listener}』" : "『』"}) )) (deflisten music_listener :initial "" "playerctl --follow metadata --format '{{ artist }} / {{album}} / {{ title }}' || true") -(deflisten music_status_listener :initial "" +(deflisten music_listener_position :initial "" "playerctl --follow metadata --format '{{duration(position)}} / {{duration(mpris:length)}}' || true") -(deflisten music_position :initial 0.5 - "playerctl --follow metadata --format '{{100.0 * position / mpris:length}}' || true") +(defpoll music_position :interval "1s" + "playerctl position") +(deflisten music_length :initial 1 + "playerctl --follow metadata mpris:length") -(defwidget widget-audio [] - (scale - :class "audio" - :orientation "v" - :min 0 - :max 100 - :value 23 - )) (defwidget widget-audio [] (eventbox + :class "audio" :onhover "${eww} update show_audio=true" :onhoverlost "${eww} update show_audio=false" (box @@ -145,12 +148,15 @@ :value current-volume :orientation "v" :flipped true + :marks true :tooltip "Volume: ${current-volume}%" :max 101 :min 0 :onchange "amixer -D pulse sset Master {}%" )) (button + :class "audio" :onclick "scripts/popup audio" + :tooltip "Volume: ${current-volume}%" :class "volume-icon" "")))) (defpoll current-volume :interval "1s" "amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'") (defvar show_audio false) diff --git a/eww/bar/scripts/compositor-control b/eww/bar/scripts/compositor-control new file mode 100755 index 0000000..2b02113 --- /dev/null +++ b/eww/bar/scripts/compositor-control @@ -0,0 +1,167 @@ +#!/usr/bin/env python + +""" +Utility to generate the workspace buttons for eww bar widget. + +Currently it fetches information from monitor 0. If all monitors have +synchronised workspaces this should not be a problem. +""" +import os, sys, socket, json, subprocess +from typing import Optional, Self, override + +WORKSPACE_ICONS = [ + (1, "☱"), + (2, "☲"), + (3, "☳"), + (4, "☴"), + (5, "☵"), + (6, "☶"), + (7, "☷"), + (8, "☰"), +] +EXTRA_WORKSPACE_ICONS = [ + (0, "⚌"), + (1, "⚍"), + (2, "⚎"), + (3, "⚏"), +] + +Workspaces = dict[int, bool] + +def get_widgets(workspaces: Workspaces) -> str: + """ + Create widget sexp from workspace information + """ + def get_class(k): + key = workspaces.get(k, None) + if key is None: + return "inactive" + elif key: + return "focused" + else: + return "active" + + buttons = [ + f'"id": "{k}", "text": "{icon}", "class": "{get_class(k)}"' + for k, icon in WORKSPACE_ICONS + ] + buttons = ", ".join(["{" + b + "}" for b in buttons]) + return "[" + buttons + "]" + +class CompositorHandler: + @staticmethod + def create() -> Optional[Self]: + """ + Main entry point, detects the type of compositor used + """ + if signature := os.environ.get("HYPRLAND_INSTANCE_SIGNATURE", None): + # See https://wiki.hyprland.org/IPC/ + xdg_runtime_dir = os.environ["XDG_RUNTIME_DIR"] + socket_addr = f"/{xdg_runtime_dir}/hypr/{signature}/.socket2.sock" + return HyprlandHandler(socket_addr) + if signature := os.environ.get("SWAYSOCK", None): + return SwayHandler() + return None + + def listen_workspaces(self): + pass + def listen_active_window_title(self): + pass + +class HyprlandHandler(CompositorHandler): + def __init__(self, socket_addr): + self.socket_addr = socket_addr + + def listen(self, callback): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + sock.connect(self.socket_addr) + except Exception as e: + print(f"Could not connect to {self.socket_addr}", file=sys.stderr) + raise e + + callback(data=None) + while sock: + data = sock.recv(1024) + callback(data) + + @staticmethod + def get_workspace_info() -> Workspaces: + workspaces = json.loads(os.popen("hyprctl workspaces -j").read()) + monitors = json.loads(os.popen("hyprctl monitors -j").read()) + if not monitors: + print("No monitor found!", file=sys.stderr) + return [] + + monitor, active = [ + (m["name"], m["activeWorkspace"]["id"]) for m in monitors if m["id"] == 0 + ][0] + workspaces = { + w["id"]: w["id"] == active + for w in workspaces if w["monitor"] == monitor + } + return workspaces + + @override + def listen_workspaces(self): + def callback(data): + if data is None or b"workspace" in data: + workspace_info = HyprlandHandler.get_workspace_info() + print(get_widgets(workspace_info), flush=True) + self.listen(callback) + + @override + def listen_active_window_title(self): + def callback(data): + if data is None: + active_window = json.loads(os.popen("hyprctl activewindow -j").read()) + print(active_window["title"], flush=True) + return + + prefix = b"activewindow>>" + if not data.startswith(prefix): + return + title = data[len(prefix):] + print(title, flush=True) + self.listen(callback) + + +class SwayHandler(CompositorHandler): + @staticmethod + def get_workspace_info() -> Workspaces: + workspaces = json.loads(os.popen("swaymsg --raw -t get_workspaces").read()) + # REVIEW: This has not been tested on a multi-monitor setup. + workspaces = { + int(w["num"]): w["focused"] + for w in workspaces + } + return workspaces + + def listen_workspaces(self): + with subprocess.Popen(["swaymsg", "-t", "subscribe", "-m", '["workspace"]'], stdout=subprocess.PIPE) as proc: + workspace_info = SwayHandler.get_workspace_info() + print(get_widgets(workspace_info), flush=True) + while line := proc.stdout.readline(): + # FIXME: Use the swaymsg subscribe output + # Not needed + #info = json.loads(line) + workspace_info = SwayHandler.get_workspace_info() + print(get_widgets(workspace_info), flush=True) + + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("Usage: wm-control workspaces|title") + + handler = CompositorHandler.create() + if handler is None: + print("No compositor found.", file=sys.stderr) + + key = sys.argv[1] + + if key == "workspaces": + handler.listen_workspaces() + elif key == "active-title": + handler.listen_active_window_title() + else: + print(f"Unknown key: {key}", file=sys.stderr) diff --git a/eww/bar/scripts/get-workspaces b/eww/bar/scripts/get-workspaces deleted file mode 100755 index 649bd9b..0000000 --- a/eww/bar/scripts/get-workspaces +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python - -""" -Utility to generate the workspace buttons for eww bar widget. - -Currently it fetches information from monitor 0. If all monitors have -synchronised workspaces this should not be a problem. -""" -import os, sys, socket, json, subprocess -from typing import Optional - -WORKSPACE_ICONS = [ - (1, "☱"), - (2, "☲"), - (3, "☳"), - (4, "☴"), - (5, "☵"), - (6, "☶"), - (7, "☷"), - (8, "☰"), -] -EXTRA_WORKSPACE_ICONS = [ - (0, "⚌"), - (1, "⚍"), - (2, "⚎"), - (3, "⚏"), -] - -Workspaces = dict[int, bool] - -def get_widgets(workspaces: Workspaces) -> str: - """ - Create widget sexp from workspace information - """ - def get_class(k): - key = workspaces.get(k, None) - if key is None: - return "inactive" - elif key: - return "focused" - else: - return "active" - - buttons = [ - f'"id": "{k}", "text": "{icon}", "class": "{get_class(k)}"' - for k, icon in WORKSPACE_ICONS - ] - buttons = ", ".join(["{" + b + "}" for b in buttons]) - return "[" + buttons + "]" - - -# Compositor specific information - -def hypr_get_workspace_info() -> Workspaces: - workspaces = json.loads(os.popen("hyprctl workspaces -j").read()) - monitors = json.loads(os.popen("hyprctl monitors -j").read()) - if not monitors: - print("No monitor found!", file=sys.stderr) - return [] - - monitor, active = [ - (m["name"], m["activeWorkspace"]["id"]) for m in monitors if m["id"] == 0 - ][0] - workspaces = { - w["id"]: w["id"] == active - for w in workspaces if w["monitor"] == monitor - } - return workspaces - - -def hypr_listen(socket_addr: str): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - sock.connect(socket_addr) - except: - print(f"Could not connect to {socket_addr}", file=sys.stderr) - raise - - # The flush here is very important since python by default buffers! - workspace_info = hypr_get_workspace_info() - print(get_widgets(workspace_info), flush=True) - while sock: - data = sock.recv(1024) - if b"workspace" in data: - workspace_info = hypr_get_workspace_info() - print(get_widgets(workspace_info), flush=True) - - -def sway_get_workspace_info() -> Workspaces: - workspaces = json.loads(os.popen("swaymsg --raw -t get_workspaces").read()) - # REVIEW: This has not been tested on a multi-monitor setup. - workspaces = { - int(w["num"]): w["focused"] - for w in workspaces - } - return workspaces - -def sway_listen(): - proc = subprocess.Popen( - ["swaymsg", "-t", "subscribe", "-m", '["workspace"]'], stdout=subprocess.PIPE - ) - workspace_info = sway_get_workspace_info() - print(get_widgets(workspace_info), flush=True) - while line := proc.stdout.readline(): - # Not needed - info = json.loads(line) - workspace_info = sway_get_workspace_info() - print(get_widgets(workspace_info), flush=True) - - -def detect(): - """ - Main entry point, detects the type of compositor used - """ - if signature := os.environ.get("HYPRLAND_INSTANCE_SIGNATURE", None): - # See https://wiki.hyprland.org/IPC/ - xdg_runtime_dir = os.environ["XDG_RUNTIME_DIR"] - socket_addr = f"/{xdg_runtime_dir}/hypr/{signature}/.socket2.sock" - return hypr_listen(socket_addr) - if signature := os.environ.get("SWAYSOCK", None): - return sway_listen() - - print("No compositor found.", file=sys.stderr) - return None - - -if __name__ == "__main__": - detect() diff --git a/hypr/hyprland.conf b/hypr/hyprland.conf index 355431c..4f4d002 100644 --- a/hypr/hyprland.conf +++ b/hypr/hyprland.conf @@ -11,7 +11,7 @@ monitor=,preferred,auto,auto # See https://wiki.hyprland.org/Configuring/Keywords/ for more # waybar is executed after eww to have the intersection effect at top left -exec-once = hyprpaper & swaync & fcitx5 & eww --config ~/.config/eww/bar open vbar +exec-once = hyprpaper & swaync & fcitx5 & flameshot & eww --config ~/.config/eww/bar open vbar # Source a file (multi-file configs) # source = ~/.config/hypr/myColors.conf @@ -131,8 +131,8 @@ bind = $mainMod, space, exec, wofi --show drun -I -G #bind = $mainMod, X, exec, hyprctl switchxkblayout wooting-wootingtwo next # code:107 = PrintSc -bind = $mainMod, code:107, exec, grimblast copy screen -bind = $mainMod SHIFT, code:107, exec, grimblast copy area +bind = $mainMod, code:107, exec, flameshot gui +#bind = $mainMod SHIFT, code:107, exec, grimblast copy area # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -195,3 +195,6 @@ bind = $mainMod, mouse_up, workspace, e-1 # Move/resize windows with mainMod + LMB/RMB and dragging bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow + +bind = SUPER,Tab,cyclenext, # change focus to another window +bind = SUPER,Tab,bringactivetotop, # bring it to the top