fix: Hyprland config update, eww bar integration #15

Merged
aniva merged 15 commits from wayland/hypr into main 2024-09-03 13:15:08 -07:00
6 changed files with 218 additions and 159 deletions
Showing only changes of commit 9ca97466a1 - Show all commits

View File

@ -22,8 +22,7 @@ recommended.
* Status Bar and Tray * Status Bar and Tray
- ~waybar~: Status bar, use ~waybar-hyprland-git~ AUR repo to enable clicking! - ~eww-wayland~: Requires ~socat~ to listen for hyprland events.
- ~eww-wayland~: Another status bar, requires ~socat~ to listen for hyprland events.
* Sway * Sway
@ -57,7 +56,6 @@ This is another choice of a compositor.
Note: Setup does not work with NVIDIA GPU without some tweaks. Note: Setup does not work with NVIDIA GPU without some tweaks.
- [[https://wiki.archlinux.org/title/Hyprland][hyprland]]: window manager - [[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 - ~hyprpaper~: Wallpaper engine
The command to set a wallpaper: The command to set a wallpaper:
@ -79,12 +77,14 @@ convert $IN \
#+end_src #+end_src
- ~swaync~: Notification server - ~swaync~: Notification server
- ~wofi~: finding programs, drop in replacement for ~rofi~ - ~wofi~: finding programs, drop in replacement for ~rofi~
- ~grimblast~: Screenshot engine
- ~dolphin~: File explorer - ~dolphin~: File explorer
- ~swaylock-effects~: A simple lockscreen - ~swaylock-effects~: A simple lockscreen
- ~wl-clipboard~: Provides copying - ~wl-clipboard~: Provides copying
- ~blueman~: Bluetooth connector - ~blueman~: Bluetooth connector
- ~grim~: Screenshot engine
- ~flameshot~: Screenshot utility; On Arch Linux, install ~flameshot-git~.
** Configuration ** Configuration
- ~lxappearance~: Used to configure GTK3 themes - ~lxappearance~: Used to configure GTK3 themes

View File

@ -32,7 +32,7 @@ $temperature_critical: #ff6666;
$battery: #b6b2b3; $battery: #b6b2b3;
* { all: unset; } //* { all: unset; }
.vbar { .vbar {
background-color: transparent; background-color: transparent;
@ -52,13 +52,16 @@ $battery: #b6b2b3;
} }
.launcher { .launcher {
all: unset;
font-size: 40px; font-size: 40px;
margin: -7px -5px 15px -5px; margin: -2px -5px 5px -3px;
} }
box .workspaces { box .workspaces {
all: unset;
border-radius: 10px; border-radius: 10px;
button { button {
all: unset;
font-size: 30px; font-size: 30px;
padding-left: 2px; padding-left: 2px;
margin: -5px -5px -5px -5px; margin: -5px -5px -5px -5px;
@ -111,7 +114,13 @@ box .weather {
color: mix($weather_end, $weather, 100%); color: mix($weather_end, $weather, 100%);
} }
} }
.audio .audio-scale { .audio-scale {
color: $audio;
}
.audio {
all: unset;
border: none;
background-color: transparent;
color: $audio; color: $audio;
} }
scale { scale {
@ -144,6 +153,8 @@ box .clock {
font-family: "Noto Serif"; font-family: "Noto Serif";
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
min-height: 15px;
min-width: 15px;
color: $media; color: $media;
border: none; border: none;
} }

View File

@ -15,9 +15,12 @@
(centerbox (centerbox
:orientation "v" :orientation "v"
(box :orientation "v" :space-evenly false :valign "start" (box :orientation "v" :space-evenly false :valign "start"
(box :class "launcher" :valign "start" :width "10px" :height "10px" "☯") ;(box :class "launcher" :valign "start" :width "10px" :height "10px" "☯")
;(label :class "launcher" :valign "start" :width "20px" :height "20px" :unindent true (label
; :text "☯") :class "launcher" :valign "start" :width "20px" :height "20px"
:unindent true
:tooltip active-window-title
:text "☯")
(widget-workspaces) (widget-workspaces)
) )
(widget-media) (widget-media)
@ -66,7 +69,9 @@
"${workspace.text}")) "${workspace.text}"))
)) ))
(deflisten workspaces :initial "[]" (deflisten workspaces :initial "[]"
"scripts/get-workspaces") "scripts/compositor-control workspaces")
(deflisten active-window-title :initial ""
"scripts/compositor-control active-title")
; Weather ; Weather
@ -103,35 +108,33 @@
:class "media" :class "media"
:width 20 :width 20
:height 20 :height 20
:thickness 4 :thickness 5
:tooltip {music_status_listener} ;:tooltip {music_listener_position}
:value {music_position} :tooltip "${music_position} / ${music_length}"
:visible {music_listener != ""} :value {100.0 * music_position / (music_length / 1000000.0)}
:visible {music_listener != "" && music_length != 1}
) )
; Add play and pause buttons here?
(label (label
:angle 90 :angle 270
:xalign 0.5 :xalign 0.5
:yalign 0.5 :yalign 0.5
:text {music_listener != "" ? "『${music_listener}』" : ""}) :justify "center"
:text {music_listener != "" ? "『${music_listener}』" : "『』"})
)) ))
(deflisten music_listener :initial "" (deflisten music_listener :initial ""
"playerctl --follow metadata --format '{{ artist }} / {{album}} / {{ title }}' || true") "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") "playerctl --follow metadata --format '{{duration(position)}} / {{duration(mpris:length)}}' || true")
(deflisten music_position :initial 0.5 (defpoll music_position :interval "1s"
"playerctl --follow metadata --format '{{100.0 * position / mpris:length}}' || true") "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 [] (defwidget widget-audio []
(eventbox (eventbox
:class "audio"
:onhover "${eww} update show_audio=true" :onhover "${eww} update show_audio=true"
:onhoverlost "${eww} update show_audio=false" :onhoverlost "${eww} update show_audio=false"
(box (box
@ -145,12 +148,15 @@
:value current-volume :value current-volume
:orientation "v" :orientation "v"
:flipped true :flipped true
:marks true
:tooltip "Volume: ${current-volume}%" :tooltip "Volume: ${current-volume}%"
:max 101 :max 101
:min 0 :min 0
:onchange "amixer -D pulse sset Master {}%" )) :onchange "amixer -D pulse sset Master {}%" ))
(button (button
:class "audio"
:onclick "scripts/popup audio" :onclick "scripts/popup audio"
:tooltip "Volume: ${current-volume}%"
:class "volume-icon" "")))) :class "volume-icon" ""))))
(defpoll current-volume :interval "1s" "amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'") (defpoll current-volume :interval "1s" "amixer -D pulse sget Master | grep 'Left:' | awk -F'[][]' '{ print $2 }' | tr -d '%'")
(defvar show_audio false) (defvar show_audio false)

View File

@ -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)

View File

@ -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()

View File

@ -11,7 +11,7 @@ monitor=,preferred,auto,auto
# See https://wiki.hyprland.org/Configuring/Keywords/ for more # See https://wiki.hyprland.org/Configuring/Keywords/ for more
# waybar is executed after eww to have the intersection effect at top left # 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 a file (multi-file configs)
# source = ~/.config/hypr/myColors.conf # 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 #bind = $mainMod, X, exec, hyprctl switchxkblayout wooting-wootingtwo next
# code:107 = PrintSc # code:107 = PrintSc
bind = $mainMod, code:107, exec, grimblast copy screen bind = $mainMod, code:107, exec, flameshot gui
bind = $mainMod SHIFT, code:107, exec, grimblast copy area #bind = $mainMod SHIFT, code:107, exec, grimblast copy area
# Move focus with mainMod + arrow keys # Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l 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 # Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow bindm = $mainMod, mouse:273, resizewindow
bind = SUPER,Tab,cyclenext, # change focus to another window
bind = SUPER,Tab,bringactivetotop, # bring it to the top