Poor man's workspace indicator for Waybar #356
Replies: 15 comments 9 replies
-
Hey, that's pretty cool! Just keep in mind that the human-readable |
Beta Was this translation helpful? Give feedback.
-
In my infinite wisdom I tried to use The first script is now much simpler (and POSIX compatible): #!/bin/sh
set -e
monitor=$1
active=""
inactive=""
workspace_str=""
msg_out="$(niri msg -j workspaces | jq ".[] | select(.output == \"$monitor\") | .is_active")"
for ws in $msg_out; do
if "$ws"; then
workspace_str="${workspace_str}${active} "
else
workspace_str="${workspace_str}${inactive} "
fi
done
printf "%s" "$workspace_str" Thanks for the tip and your amazing work on Niri! |
Beta Was this translation helpful? Give feedback.
-
My edits and improvements: #!/usr/bin/env bash
case "$1" in
focus-workspace)
niri msg action "$@" && pkill -SIGRTMIN+8 waybar;;
up)
niri msg action focus-workspace-up && pkill -SIGRTMIN+8 waybar;;
down)
niri msg action focus-workspace-down && pkill -SIGRTMIN+8 waybar;;
*)
glyphs=""
workspace_str=" "
for ws in $(niri msg -j workspaces | jq ".[] | select(.output == \"$1\") | .is_active"); do
workspace_str="$workspace_str$( if "$ws"; then
echo "<big><span color='#1793d1'>${glyphs:0:1}</span></big>";
else echo "<small>${glyphs:1:1}</small>"; fi) "
done
name=$(niri msg -j workspaces | jq -r ".[] | select(.output == \"$1\" and .is_active == true) | .name")
echo -e "{\"text\":\"${workspace_str}\", \"tooltip\":\"Active workspace name: ${name}\"}"
esac
"custom/niri_workspaces": {
"format": "{}",
"interval": 2,
"return-type": "json",
"exec": "$HOME/.config/waybar/modules/workspaces.sh \"$WAYBAR_OUTPUT_NAME\"",
"on-click": "fuzzel",
"on-scroll-up": "$HOME/.config/waybar/modules/workspaces.sh up",
"on-scroll-down": "$HOME/.config/waybar/modules/workspaces.sh down",
"signal": 8
}, That's all, At niri config you can set:
On scroll up or down - switching to next or previous workspace, on click - launch |
Beta Was this translation helpful? Give feedback.
-
I shamelessly stole the code from both nebulosa2007 and matbme and combined them to achieve a simple and straightforward solution. Here's the result:
#!/usr/bin/env bash
case "$1" in
focus-workspace)
niri msg action "$@" && pkill -SIGRTMIN+8 waybar;;
*)
set -e
monitor=$1
active=""
inactive=""
workspace_str=""
msg_out="$(niri msg -j workspaces | jq ".[] | select(.output == \"$monitor\") | .is_active")"
for ws in $msg_out; do
if "$ws"; then
workspace_str="${workspace_str}${active} "
else
workspace_str="${workspace_str}${inactive} "
fi
done
printf "%s" "$workspace_str"
esac
"modules-left": [
"custom/workspaces",
],
"custom/workspaces": {
"exec": "$HOME/.config/waybar/modules/niri-workspaces.sh \"$WAYBAR_OUTPUT_NAME\"",
"signal": 8
},
|
Beta Was this translation helpful? Give feedback.
-
Just learned about this discussion after I implemented everything from scratch. I'm using nushell as my shell & preferred scripting tool, so might be a bit niche, but if anyone wants to use it, feel free:
#!/usr/bin/nu
let out = {
text: "",
alt: "none",
tooltip: "",
class: "none"
}
loop {
sleep 1sec;
print (
$out | update text (
niri msg -j workspaces
| from json
| each { |ws| if $ws.is_active { "" } else { "" } } | str join ' ')
| to json -r
)
}
#!/usr/bin/nu
let out = {
text: "",
alt: "none",
tooltip: "",
class: "none"
}
loop {
sleep 1sec;
let window = niri msg -j focused-window | from json;
if $window == null { print ($out | to json -r) } else {
print (
$out | update text (
$"($window.app_id): ($window.title)"
) | to json -r
)
}
} and finally in "modules-left": [
"custom/niri-workspaces",
"custom/niri-focused-window"
],
"custom/niri-workspaces": {
"return-type": "json",
"max-length": 40,
"escape": true,
"exec": "$HOME/.config/waybar/modules/get-niri-workspaces.nu"
},
"custom/niri-focused-window": {
"return-type": "json",
"max-length": 120,
"escape": true,
"exec": "$HOME/.config/waybar/modules/get-niri-focused-window.nu",
"format": " {}"
}, |
Beta Was this translation helpful? Give feedback.
-
Alright, this is getting fun. I was planning to translate the script to nushell this weekend, but now that freijon has set the bar so high, there's no point in just translating it. Instead, I've made some tweaks to their scripts to meet my own needs and give it a personal touch. Note: no need to change niri's default config.
#!/usr/bin/nu
def main [output_name] {
loop {
sleep 1sec
print (
niri msg -j workspaces
| from json
| where output == $output_name
| each {|ws| if $ws.is_active { '' } else { '' } }
| str join " "
)
}
}
#!/usr/bin/nu
let out = {
text: "",
tooltip: ""
}
loop {
sleep 1sec
let fw = niri msg -j focused-window | from json
print (
$out
| try { update text $fw.title | update tooltip $"($fw.title) | ($fw.app_id)" }
| to json -r
)
}
"modules-left": [
"custom/workspaces",
"custom/window"
],
"custom/workspaces": {
"exec": "$HOME/.config/waybar/modules/workspaces.nu \"$WAYBAR_OUTPUT_NAME\""
},
"custom/window": {
"return-type": "json",
"max-length": 20,
"escape": true,
"exec": "$HOME/.config/waybar/modules/window.nu",
"format": "<i>{}</i>"
}, Result: edit: refactor scripts to make them more idiomatic |
Beta Was this translation helpful? Give feedback.
-
would have been awesome to actually find any of this before rolling my own, but way less cool, version: #!/usr/bin/env node
const { spawnSync } = require('child_process');
let output = process.argv[2]
let { stdout, stderr } = spawnSync('niri', ['msg', '-j', 'workspaces'])
let items = JSON.parse(stdout.toString())
.filter(item => item.output == output)
.map(item => {
if (item.is_active) {
return `(${item.idx})`
} else {
return ` ${item.idx} `
}
})
.join(' | ');
console.log(`${items}`) implementation is very similar, with a waybar spawned for each detected display. |
Beta Was this translation helpful? Give feedback.
-
I wish I'd have seen this topic before reinventing my own ones... 😅 I was searching for ways to "subscribe" Niri's window/workspace change events so that scripts are only executed when something (focused window, active workspace) actually changes. But now it seems that we could only refresh our waybar modules by executing I think we could implement an event system by printing messages to stdout on events emitted:
or by binding actions to events (like keybindings):
|
Beta Was this translation helpful? Give feedback.
-
Is there a way to do something similar for keyboard layout? |
Beta Was this translation helpful? Give feedback.
-
How about adding one of the examples somewhere to the docs as a temporary solution for the version? |
Beta Was this translation helpful? Give feedback.
-
contributing my own vertical solution here in bash: script: #!/usr/bin/env bash
[[ -z $NIRI_SOCKET ]] && exit 1
while true; do
icons="$(niri msg --json workspaces | \
jq -c 'if .[].is_active then "" else "" end' | \
awk '{ printf "%s\\n", $0 }' | \
tr -d \" | \
sed 's/.\{2\}$//')"
echo "\"$icons\"" | jq -c '{ text: "\(.)" }'
sleep 1
done waybar config: "modules-left": [ "custom/niri-workspaces" ],
"custom/niri-workspaces": {
"exec": "</path/to/script>",
"return-type": "json"
}, |
Beta Was this translation helpful? Give feedback.
-
I am using some UTF-8 digits with circles to show the workspace numbers, but still be able to highlight the current one: #!/usr/bin/env bash
set -e
monitor=$1
niri msg -j workspaces \
| jq -cr '
["⓪", "①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"] as $map
| map(
select(.output == "'$monitor'")
| if(.is_active) then $map[.idx] else .idx end
) | join(" ")
' |
Beta Was this translation helpful? Give feedback.
-
Hi everyone, I just merged the event stream IPC, so latest git niri will work together with my Waybar workspaces PR: Alexays/Waybar#3551 Or you can write a custom script using the event stream IPC to avoid polling, for example: #!/usr/bin/python3
import json
import os
import sys
from socket import AF_UNIX, SHUT_WR, socket
niri_socket = socket(AF_UNIX)
niri_socket.connect(os.environ["NIRI_SOCKET"])
file = niri_socket.makefile("rw")
file.write('"EventStream"')
file.flush()
niri_socket.shutdown(SHUT_WR)
output = None
if len(sys.argv) > 1:
output = sys.argv[1]
workspaces = {}
def print_workspaces():
vals = workspaces.values()
if output is not None:
vals = filter(lambda ws: ws["output"] == output, vals)
vals = sorted(vals, key=lambda ws: ws["idx"])
text = "\n".join(("" if ws["is_active"] else "" for ws in vals))
print(json.dumps({"text": text}), flush=True)
for line in file:
event = json.loads(line)
if changed := event.get("WorkspacesChanged"):
workspaces = {ws["id"]: ws for ws in changed["workspaces"]}
print_workspaces()
elif activated := event.get("WorkspaceActivated"):
focused = activated["focused"]
ws_output = workspaces[activated["id"]]["output"]
for ws in workspaces.values():
got_activated = ws["id"] == activated["id"]
if ws["output"] == ws_output:
ws["is_active"] = got_activated
if focused:
ws["is_focused"] = got_activated
print_workspaces() |
Beta Was this translation helpful? Give feedback.
-
This is fantastic and I can't wait until the pull requests go through. In the meantime I have been working with scripts like those above: #!/usr/bin/env bash
case "$1" in
focus-workspace)
niri msg action "$@" && pkill -SIGRTMIN+8 waybar
;;
*)
set -e
monitor=$1
active_color="#00FFFF"
# Define the dictionary of workspace names and icons
declare -A workspace_icons=(["home"]="" ["firefox"]="" ["chromium"]=""
["dev"]="" ["discord"]="" ["pass"]="" ["signal"]=""
["notes"]="" ["monitor"]=""
#["games"]="" #["music"]=""
)
# Define dot patterns for window count indicators
declare -A windowdotsl=(
[0]=" " [1]="⠈" [2]="⠘" [3]="⠸" [4]="⢸" [5]="⣸"
[6]="⣹" [7]="⣾" [8]="⣿" [9]="█"
)
declare -A windowdotsr=(
[0]=" " [1]="⠁" [2]="⠃" [3]="⡆" [4]="⡇" [5]="⣇"
[6]="⣏" [7]="⣷" [8]="⣿" [9]="█"
)
# Get workspace information including window counts
workspace_info=$(niri msg -j workspaces | jq -r ".[] | select(.output == \"$monitor\") | \"\(.name) \(.is_active) \(.windows | length)\"")
# Initialize an empty output string
output=""
# Process each workspace
while read -r name is_active window_count; do
icon="${workspace_icons[$name]}"
# Calculate left and right dot indices
left_index=$((window_count > 9 ? 9 : window_count))
right_index=$((window_count > 18 ? 9 : (window_count > 9 ? window_count - 9 : 0)))
# Construct the workspace indicator
indicator="${windowdotsl[$left_index]} ${icon}${windowdotsr[$right_index]}"
if [ "$is_active" = "true" ]; then
output+="<span color='${active_color}'>"
fi
output+=" ${indicator} "
if [ "$is_active" = "true" ]; then
output+="</span>"
fi
done <<<"$workspace_info"
# Print the final output
printf "%s\n" "$output"
;;
esac I was wondering if there is any way to get the indicators for the number of windows or columns within each workspace? Thanks so much for this incredible package. |
Beta Was this translation helpful? Give feedback.
-
Question, do we still need a manual script given that Waybar has |
Beta Was this translation helpful? Give feedback.
-
Now that v0.1.6 is out with
niri msg workspaces
, we have the tools to write a (somewhat hacky) workspace indicator! I'll share how I implemented a simple indicator for Waybar.First and foremost, this is how it turned out (see top-left):
First, we will need a script that parses the output of msg workspaces. Save it somewhere inside your home dir (e.g.
~/niri-workspaces.sh
):Since we're not fancy enough to write a custom module, we also need a script to wrap niri actions and call
pkill
on waybar with a signal (I saved in myhome dir as~/exec_and_signal.sh
):Now, it's time to configure waybar:
Don't forget to restart waybar:
systemctl --user restart waybar.service
Lastly, open your niri config and replace all workspace-related binds with our wrapper:
Aaaaand now you should have a (very, very hacky) workspace switcher! :)
Beta Was this translation helpful? Give feedback.
All reactions