-
-
Notifications
You must be signed in to change notification settings - Fork 725
Module: Custom
The custom
module displays either the output of a script or static text. To display static text, specify only the format
field.
Addressed by custom/<name>
option | typeof | default | description |
---|---|---|---|
exec |
string | ||
exec-if |
string | The path to a script, which determines if the script in exec should be executed.exec will be executed if the exit code of exec-if equals 0. |
|
exec-on-event |
bool | true |
If an event command is set (e.g. on-click or on-scroll-up ) then re-execute the script after executing the event command. |
return-type |
string | See return-type
|
|
interval |
integer | The interval (in seconds) in which the information gets polled. Use once if you want to execute the module only on startup. You can update it manually with a signal. If no interval is defined, it is assumed that the out script loops it self. |
|
restart-interval |
integer | The restart interval (in seconds). Can't be used with the interval option, so only with continuous scripts. Once the script exit, it'll be re-executed after the restart-interval. |
|
signal |
integer | The signal number used to update the module. The number is valid between 1 and N, where SIGRTMIN+N = SIGRTMAX . |
|
format |
string | {} |
The format, how information should be displayed. On {} data gets inserted. |
format-icons |
array/object/string | If the type is an array, then based on the set percentage, the corresponding icon gets selected (low to high). If the type is an object, then the icon will be selected according to alt string from the output.If the type is a string, it will be pasted as is. NOTE: Arrays can be nested into objects. Icons will be selected first according to alt then percentage. |
|
rotate |
integer | Positive value to rotate the text label. | |
max-length |
integer | The maximum length in character the module should display. | |
on-click |
string | Command to execute when clicked on the module. | |
on-click-middle |
string | Command to execute when you middle clicked on the module using mousewheel. | |
on-click-right |
string | Command to execute when you right clicked on the module. | |
on-scroll-up |
string | Command to execute when scrolling up on the module. | |
on-scroll-down |
string | Command to execute when scrolling down on the module. | |
smooth-scrolling-threshold |
double | Threshold to be used when scrolling. | |
tooltip |
bool | true |
Option to enable tooltip on hover. |
escape |
bool | false |
Option to enable escaping of script output |
- When
return-type
is set tojson
, Waybar expects theexec
-script to output its data in JSON format. This should look like this:{"text": "$text", "alt": "$alt", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage }
. This means the output should also be on a single line. This can be achieved by piping the output of your script throughjq --unbuffered --compact-output
. Theclass
parameter also accepts an array of strings. - If nothing or an invalid option is specified Waybar expects i3blocks style output, where values are
newline
separated. This should look like this:$text\n$tooltip\n$class
class
is a CSS class, to apply different styles in style.css
The exec
script may be continuous (i.e. contain some kind of infinite loop). The display will be updated for each new line of data on stdout (following the chosen return-type
).
The interval
option does not work with continuous script (no need to call it several times… as it will continuously run). However you might want to set the restart-interval
to start again the script if it stops after some time.
Be warned that some technologies use a buffer for their output. If your module displays nothing even if your script is working as expected, the output might be hold temporarily in a buffer. Look for the correct way to flush the output buffer for your language of choice.
For example, in ruby you can do the following thing:
loop do
puts { text: 'My module text', class: 'class', … }.to_json
$stdout.flush
sleep 5
end
string | replacement |
---|---|
{} |
Output of the script. |
{percentage} |
Percentage which can be set via a json return-type. |
{icon} |
An icon from 'format-icons' according to percentage. |
#custom-<name>
-
#custom-<name>.<class>
-
<class>
can be set by the script. For more information seereturn-type
-
If your module is self-looping, and it doesn't even show up in the bar, check that:
- Its configuration do not include an
interval
parameter - Output to stdout is not buffered (see #2358)
~/.config/waybar/config
"custom/dunst": {
"exec": "~/.config/waybar/scripts/dunst.sh",
"on-click": "dunstctl set-paused toggle",
"restart-interval": 1,
}
~/.config/waybar/scripts/dunst.sh
#!/bin/bash
COUNT=$(dunstctl count waiting)
ENABLED=
DISABLED=
if [ $COUNT != 0 ]; then DISABLED=" $COUNT"; fi
if dunstctl is-paused | grep -q "false" ; then echo $ENABLED; else echo $DISABLED; fi
Or if you want a version that reacts to dbus events instead:
#!/usr/bin/env bash
set -euo pipefail
readonly ENABLED=' '
readonly DISABLED=' '
dbus-monitor path='/org/freedesktop/Notifications',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged' --profile |
while read -r _; do
PAUSED="$(dunstctl is-paused)"
if [ "$PAUSED" == 'false' ]; then
CLASS="enabled"
TEXT="$ENABLED"
else
CLASS="disabled"
TEXT="$DISABLED"
COUNT="$(dunstctl count waiting)"
if [ "$COUNT" != '0' ]; then
TEXT="$DISABLED ($COUNT)"
fi
fi
printf '{"text": "%s", "class": "%s"}\n' "$TEXT" "$CLASS"
done
Supports vlc, mpv, RhythmBox, web browsers, cmus, mpd, spotify and others.
"custom/media": {
"format": "{icon} {}",
"escape": true,
"return-type": "json",
"max-length": 40,
"on-click": "playerctl play-pause",
"on-click-right": "playerctl stop",
"smooth-scrolling-threshold": 10, // This value was tested using a trackpad, it should be lowered if using a mouse.
"on-scroll-up": "playerctl next",
"on-scroll-down": "playerctl previous",
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null", // Script in resources/custom_modules folder
}
"custom/spotify": {
"format": "{icon} {}",
"escape": true,
"return-type": "json",
"max-length": 40,
"interval": 30, // Remove this if your script is endless and write in loop
"on-click": "playerctl -p spotify play-pause",
"on-click-right": "killall spotify",
"smooth-scrolling-threshold": 10, // This value was tested using a trackpad, it should be lowered if using a mouse.
"on-scroll-up" : "playerctl -p spotify next",
"on-scroll-down" : "playerctl -p spotify previous",
"exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null", // Script in resources/custom_modules folder
"exec-if": "pgrep spotify"
}
"custom/mpd": {
"format": "♪ {}",
//"max-length": 15,
"interval": 10,
"exec": "mpc current",
"exec-if": "pgrep mpd",
"on-click": "mpc toggle",
"on-click-right": "sonata"
}
"custom/cmus": {
"format": "♪ {}",
//"max-length": 15,
"interval": 10,
"exec": "cmus-remote -C \"format_print '%a - %t'\"", // artist - title
"exec-if": "pgrep cmus",
"on-click": "cmus-remote -u", //toggle pause
"escape": true //handle markup entities
}
"custom/media": {
"format": "{icon}{}",
"return-type": "json",
"format-icons": {
"Playing": " ",
"Paused": " ",
},
"max-length":70,
"exec": "playerctl -a metadata --format '{\"text\": \"{{playerName}}: {{artist}} - {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F",
"on-click": "playerctl play-pause",
}
Uses Wireplumber
~/.config/waybar/config
"custom/pipewire": {
"tooltip": false,
"max-length": 6,
"exec": "$HOME/.config/waybar/scripts/pipewire.sh",
"on-click": "pavucontrol",
"on-click-right": "qpwgraph"
}
~/.config/waybar/scripts/pipewire.sh
#!/bin/bash
set -e
# https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever
snore() {
local IFS
[[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:)
read -r ${1:+-t "$1"} -u $_snore_fd || :
}
DELAY=0.2
while snore $DELAY; do
WP_OUTPUT=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
if [[ $WP_OUTPUT =~ ^Volume:[[:blank:]]([0-9]+)\.([0-9]{2})([[:blank:]].MUTED.)?$ ]]; then
if [[ -n ${BASH_REMATCH[3]} ]]; then
printf "MUTE\n"
else
VOLUME=$((10#${BASH_REMATCH[1]}${BASH_REMATCH[2]}))
ICON=(
""
""
""
)
if [[ $VOLUME -gt 50 ]]; then
printf "%s" "${ICON[0]} "
elif [[ $VOLUME -gt 25 ]]; then
printf "%s" "${ICON[1]} "
elif [[ $VOLUME -ge 0 ]]; then
printf "%s" "${ICON[2]} "
fi
printf "$VOLUME%%\n"
fi
fi
done
exit 0
"custom/pacman": {
"format": "{} ",
"interval": "once",
"exec": "pacman_packages",
"on-click": "update-system",
"signal": 8
}
//alternate
"custom/pacman": {
"format": "{} ",
"interval": 3600, // every hour
"exec": "checkupdates | wc -l", // # of updates
"exec-if": "exit 0", // always run; consider advanced run conditions
"on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system
"signal": 8
}
You can use the signal and update the number of available packages with pkill -RTMIN+8 waybar
.
"custom/deadbeef": {
"format": " {}",
"max-length": 50,
"interval": 10,
"exec": "deadbeef --nowplaying-tf '{\"text\": \"%title%\", \"tooltip\":\"%artist% - %title%\",\"class\":\"$if(%isplaying%,playing,not-playing)\"}'",
"return-type": "json",
"exec-if": "pgrep deadbeef",
"on-click": "deadbeef --toggle-pause"
}
(the indicator is quite silly and only checks whether a tunnel exists or not)
"custom/vpn": {
"format": "VPN ",
"exec": "echo '{\"class\": \"connected\"}'",
"exec-if": "test -d /proc/sys/net/ipv4/conf/tun0",
"return-type": "json",
"interval": 5
}
"custom/github": {
"format": "{} ",
"return-type": "json",
"interval": 60,
"exec": "$HOME/.config/waybar/github.sh",
"on-click": "xdg-open https://github.com/notifications"
}
- Make sure
jq
is installed. - Create
notifications.token
, a personal access token, withnotifications
in scope at https://github.com/settings/tokens. - Create
github.sh
with the contents below, replacingusername
with your own.
#!/bin/bash
token=`cat ${HOME}/.config/github/notifications.token`
count=`curl -u username:${token} https://api.github.com/notifications | jq '. | length'`
if [[ "$count" != "0" ]]; then
echo '{"text":'$count',"tooltip":"$tooltip","class":"$class"}'
fi
Replace Berlin+Germany
with your own city.
"custom/weather": {
"exec": "$XDG_CONFIG_HOME/waybar/get_weather.sh Berlin+Germany",
"return-type": "json",
"format": "{}",
"tooltip": true,
"interval": 3600
}
#!/usr/bin/env bash
# get_weather.sh
for i in {1..5}
do
text=$(curl -s "https://wttr.in/$1?format=1")
if [[ $? == 0 ]]
then
text=$(echo "$text" | sed -E "s/\s+/ /g")
tooltip=$(curl -s "https://wttr.in/$1?format=4")
if [[ $? == 0 ]]
then
tooltip=$(echo "$tooltip" | sed -E "s/\s+/ /g")
echo "{\"text\":\"$text\", \"tooltip\":\"$tooltip\"}"
exit
fi
fi
sleep 2
done
echo "{\"text\":\"error\", \"tooltip\":\"error\"}"
Requires jq
Get all the scratchpad nodes. Shows the count as module text and the window class/app_id, id, and name on hover, and doesn't display anything if there are no nodes in the scratchpad.
"custom/scratchpad-indicator": {
"interval": 3,
"return-type": "json",
"exec": "swaymsg -t get_tree | jq --unbuffered --compact-output '(recurse(.nodes[]) | select(.name == \"__i3_scratch\") | .focus) as $scratch_ids | [.. | (.nodes? + .floating_nodes?) // empty | .[] | select(.id |IN($scratch_ids[]))] as $scratch_nodes | if ($scratch_nodes|length) > 0 then { text: \"\\($scratch_nodes | length)\", tooltip: $scratch_nodes | map(\"\\(.app_id // .window_properties.class) (\\(.id)): \\(.name)\") | join(\"\\n\") } else empty end'",
"format": "{} 🗗",
"on-click": "exec swaymsg 'scratchpad show'",
"on-click-right": "exec swaymsg 'move scratchpad'"
}
A simpler version, that only shows the number of windows when there is at least one (hidden when there are 0). Shows no additional info on hover.
"custom/scratchpad_indicator": {
"interval": 3,
"exec": "swaymsg -t get_tree | jq 'recurse(.nodes[]) | first(select(.name==\"__i3_scratch\")) | .floating_nodes | length | select(. >= 1)'",
"format": "{} ",
"on-click": "swaymsg 'scratchpad show'",
"on-click-right": "swaymsg 'move scratchpad'"
}
"custom/output-scale": {
"format": "{icon} {}",
"return-type": "json",
"format-icons": { // These are FontAwesome 4 icons. Update them as needed.
"scale": " \uf0b2",
"noscale": "\uf066"
},
"exec-on-event": true,
"interval": "once",
"exec": "( swaymsg -r -t get_outputs | jq '.[0].scale' | xargs test 1 == ) && echo '{\"alt\": \"noscale\"}' || echo '{\"alt\":\"scale\"}'",
"exec-if": "sleep 0.1", // Give enough time for `sway output` command changes to propagate so we can read them in the next `exec`
"on-click": "( swaymsg -r -t get_outputs | jq '.[0].scale' | xargs test 1 = ) && swaymsg output DP-1 scale 1.4 || swaymsg output DP-1 scale 1"
}
- Change the desired scaling parameter in
on-click
configuration. - Update the correct output from
DP-1
to the one you have. - Change the index
[0]
inexec
andon-click
if you have more than one output, and need to adjust non-zero output.
"custom/pulseaudio-cycle": {
"return-type": "json",
"exec-on-event": true,
"interval": "5s",
"exec" "pactl --format=json list sinks | jq -cM --unbuffered \"map(select(.name == \\\"$(pactl get-default-sink)\\\"))[0].properties | [.\\\"media.name\\\",.\\\"alsa.name\\\",.\\\"node.nick\\\",.\\\"alsa.long_card_name\\\"] | map(select(length>0))[0] | {text:.}\"",
"exec-if": "sleep 0.1", // Give enough time for `pactl get-default-sink` to update
"on-click": "pactl --format=json list sinks short | jq -cM --unbuffered \"[.[].name] | .[((index(\\\"$(pactl get-default-sink)\\\")+1)%length)]\" | xargs pactl set-default-sink"
}
Requires plann
#!/usr/bin/env bash
PLANN=$HOME/.pyenv/versions/plann/bin/plann
printf '{"text":"'
printf " $(date +'%m-%d (%a)') "
printf " $(date +'%H:%M')"
printf '",'
printf '"tooltip":"%s"' "$($PLANN --caldav-url CALDAV_URL --caldav-username CALDAV_USER --caldav-password CALDAV_PASSWORD --calendar-name 'CALDAV_CALENDAR_NAME' agenda | head --lines -1 | sed 's/$/\\n/' | tr -d '\n' | head --bytes -2)"
printf '}'
Remove --calendar-name
option to displays the last events across all calendars.
- Next calendar events (using khal)
- Chromecast currently playing (using catt)
- Weather with detailed forecast (using wttr.in)
- Another Weather module (using weather.com)
- External screen brightness (using ddcutil)
- Another external screen brightness (using ddcutil too)
- Screen brightness without external scripts (still ddcutil)
- Reminders (using remind)
- WireGuard (using NetworkManager/nmcli)
-
GPU usage monitor (using data from
/sys/class/hwmon/
) - UPower (Customizable energy/battery information using UPower dbus (battery, bluetooth, line-power etc))
- Screenrecorder (record your screen into a file)
- Sway WM adaptive sync toggle (using Sway IPC)
- Pacman updates
- Pacman and AUR updates
- Notifications for Arch Linux package updates
- RI - Rest your Eyes and Self
- raffaem's waybar-mediaplayer (mediaplayer with progress bar, album art, tooltip and notifications)
- raffaem's waybar-stopwatch
- raffaem's waybar-screenrecorder
- ponas-niekas's waybar-timer
- jbirnick's waybar-timer (interactive)
- Todoist
- Home
- Installation
- Configuration
- Styling
- Examples
- FAQ
- Modules:
- Backlight/Slider
- Backlight
- Battery
- Bluetooth
- CPU
- Cava
- Clock
- Custom
- DWL
- Disk
- Gamemode
- Group
- Hyprland
- Idle Inhibitor
- Image
- JACK
- Keyboard State
- Language
- Load
- MPD
- MPRIS
- Memory
- Network
- Niri
- Power Profiles Daemon
- Privacy
- PulseAudio/Slider
- PulseAudio
- River
- Sndio
- Sway
- Systemd failed units
- Taskbar
- Temperature
- Tray
- UPower
- User
- WirePlumber
- Workspaces
- Writing Modules