В дефолтном конфиге sway есть блок, отвечающий за отображение статус-бара. Выглядит он так:
#
# Status Bar:
#
# Read `man 5 sway-bar` for more information about this section.
bar {
position bottom
# When the status_command prints a new line to stdout, swaybar updates.
# The default just shows the current date and time.
status_command while date +'%Y-%m-%d %X'; do sleep 1; done
# отключить трей (по умолчанию он включён):
tray_output none
# например, qbittorrent при установке пихает в трей значок
colors {
statusline #ffffff
background #323232
inactive_workspace #32323200 #32323200 #5c5c5c
}
}
По умолчанию в статус-баре отображается год, месяц, дата и время. И за их отображение отвечает конкретно вот эта строка:
status_command while date +'%Y-%m-%d %X'; do sleep 1; done
statusline #FFFFFF – дефолтный белый цвет слишком яркий. #BAC3BC – более приятный для глаза.
В status_command можно вписывать любую bash-команду. Но она должна быть вечной и не завершаться. Если я, например, хочу, чтобы у меня в статус-баре отображалась строка "Какашка" и я напишу вот такую команду:
status_command echo "Какашка"
то она не сработает. Вместо неё мы увидим в статус-баре сообщение об ошибке. Правильно будет написать вот так:
status_command while true; do echo "Какашка"; sleep 1; done
Вывод: swaybar требует бесконечного цикла. Любой статус-командный скрипт должен работать постоянно и время от времени печатать новые строки.
Очень удобно, когда мы видим в статус-баре отображение текущей раскладки. Для этого создадим скрипт ~/.config/sway/status.sh:
!!! Важно !!! Обратим внимание на утилиту jq! Она нужна для корректной работы скрипта. В моём случае после переустановки Arch этот скрипт перестал работать, потому что я забыл установить jq.
#!/bin/bash
while true; do
# Получаем раскладку
layout=$(swaymsg -t get_inputs | jq -r '.[] | select(.type=="keyboard") | .xkb_active_layout_name' | head -n 1)
# Преобразуем в RU/EN
case "$layout" in
"Russian") layout="RU" ;;
"English (US)") layout="EN" ;;
esac
# Вывод строки в бар
echo "$layout | $(date +'%Y-%m-%d %H:%M:%S')"
sleep 1
done
Сделаем этот файл исполняемым:
chmod +x ~/.config/sway/status.sh
И меняем status_command в конфиге на:
status_command ~/.config/sway/status.sh
Теперь можно перезагрузить конфиг (Mod+Shift+C) и в статус баре появится отображение раскладки.
Отобразить в статус-баре строку "DeaDBeeF", когда проигрыватель DeaDBeeF запущен и не отображать её, когда DeaDBeeF закрыт:
#!/usr/bin/env bash
echo '{"version":1}'
echo '['
while true; do
if pgrep deadbeef >/dev/null; then
txt='DeaDBeeF'
sep=9 # ширина текста
else
txt=''
sep=0 # скрыть блок
fi
echo "[{\"full_text\":\"$txt\", \"separator_block_width\": $sep}],"
sleep 2
done
Команда pgrep deadbeef ищет процесс по имени и выводит его PID.
Можно заметить, что в этом скрипте нет закрывающей квадратной скобки. Она и не нужна. Всё потому что Swaybar (как и i3bar) использует streaming JSON — это не обычный JSON-файл, а бесконечный поток. Сначала мы видим заголовок:
{"version":1}
[
а потом идёт бесконечная серия JSON-массивов, каждый на одной строке, например:
[{"full_text":"DeaDBeeF"}],
# или:
[{"full_text":""}],
Тут есть нюанс с шебангом. Пример выше не работает, если указать ему в качестве шебанга:
#!/bin/bash
или:
#!/usr/bin/bash
или:
#!/usr/sh
Вернее, работает, но неправильно. Надпись "DeaDBeeF" отображается постоянно, вне зависимости от того, запущен проигрыватель DeaDBeeF, или нет. Но если указать вот такой шебанг:
#!/usr/bin/env bash
то всё работает нормально.
В последнем примере запускается обычный bash. А вот в предыдущих примерах запускается bash в POSIX-режиме.
У нас /usr/bin/sh является ссылкой на /usrb/bin/bash. Но сам bash, когда видит, что он запущен как sh, то он как бы запускается в POSIX-совместимом режиме. Когда я пишу шебанг #!/bin/sh или запускаю скрипт вот так:
/bin/sh script.sh
Bash видит, что он был запущен как sh, и ограничивает свои возможности, соблюдая POSIX-стандарт.
Тоже работает:
#!/usr/bin/env bash
echo '{"version":1}'
echo '['
while true; do
if pgrep deadbeef >/dev/null; then
txt='DeaDBeeF'
else
txt='' # обязательно пустая строка
fi
# Даже если txt пустой — выводим блок!
echo "[{\"full_text\":\"$txt\"}],"
sleep 2
done
Я хочу, чтобы в статус-баре отображался PID-процесса, когда запущен проигрыватель DeaDBeeF, и чтобы, когда проигрыватель закрыт, оторажалась строка: "DeaDBeeF закрыт". Вот пример кода:
#!/bin/bash
while true; do
# Ищем именно DeaDBeeF, а не всё что содержит "deadbeef"
result=$(pgrep -f '^(/usr/bin/)?deadbeef( |$)')
# Здесь мы ищем строку, которая начинается с /usr/bin/deadbeef
# или с deadbeef, а потом следует либо пробел, либо конец строки
if [ -n "$result" ]; then
txt="$result"
else
txt='DeaDBeeF закрыт'
fi
echo "$txt"
sleep 2
done
# Вот эта команда как раз
# отображает PID процесса проигрывателя DeaDBeeF:
pgrep -f '^/usr/bin/deadbeef( |$)'
# 304080
# А эта ищет и /usr/bin/deadbeef и просто deadbeef.
# (/usr/bin/)? означает необязательную часть
pgrep -f '^(/usr/bin/)?deadbeef( |$)'
# 304080
# другие попытки запустить команду.
# Вот эта находит два процесса:
pgrep deadbeef
# 294375
# 304080
# Вот эта находит три процесса:
pgrep -f deadbeef
# 285013
# 294375
# 304080
# вот эта находит один процесс:
pgrep -f /usr/bin/deadbeef
# 304080
# Но всё-таки последняя команда не защищена от нахождения ...
# ... процессов типа: /usr/bin/deadbeef-someplugin, например, ...
# ... т.к. ищется подстрока
Разъяснение регулярного выражения:
if [ -n "$result" ]; then эквивалентен if (result != "") {.
[ – в bash это не оператор, это команда test.
# Проверяем пустая строка, или нет:
test -n STRING
# то же самое:
[ -n STRING ]
# опция -n означает:
# вернуть статус 0 (true), если длина строки ненулевая,
# вернуть статус 1 (false), если строка пустая.
Теперь можно переписать предыдущий пример таким образом, чтобы в статус-баре отображалась строка "DeaDBeeF", если проигрыватель DeaDBeeF запущен и чтобы ничего не отображалось, когда проигрыватель не запущен.
#!/bin/bash
while true; do
if pgrep -f '^/usr/bin/deadbeef( |$)' >/dev/null; then
txt='DeaDBeeF'
else
txt=''
fi
echo "$txt"
sleep 1
done
Пусть у нас будет скрипт status.sh главным файлом, а deadbeef-status.sh мы будем подгружать в него. Тогда перепишем deadbeef-status.sh таким образом, чтобы он был без цикла. Ведь мы будем его вызывать из цикла другого файла. Поэтому здесь он должен иметь одиночный вызов:
#!/bin/bash
if pgrep -f '^/usr/bin/deadbeef( |$)' >/dev/null; then
echo "DeaDBeeF"
else
echo ""
fi
А главный файл ~/.config/sway/status.sh, в который мы будем подгружать статус DeaDBeeF, будет выглядеть примерно так:
#!/bin/bash
while true; do
# Получаем раскладку
layout=$(swaymsg -t get_inputs | jq -r '.[] | select(.type=="keyboard") | .xkb_active_layout_name' | head -n 1)
case "$layout" in
"Russian") layout="RU" ;;
"English (US)") layout="EN" ;;
esac
# Получаем статус DeaDBeeF (одиночный запуск второго скрипта)
deadbeef_status=$("$HOME/.config/sway/deadbeef-status.sh")
# Собираем строку
if [ -n "$deadbeef_status" ]; then
echo "$deadbeef_status | $layout | $(date +'%Y-%m-%d %H:%M:%S')"
else
echo "$layout | $(date +'%Y-%m-%d %H:%M:%S')"
fi
sleep 1
done
#!/bin/bash
prev_pos="" # переменная, куда мы сохараним пред. позицию
while true; do
# Если DeaDBeeF запущен, то ...
if pgrep -f '^/usr/bin/deadbeef( |$)' >/dev/null; then
deadbeef_status=$(deadbeef --nowplaying '%e' 2>/dev/null)
# Если ничего не проигрывается, то:
if [[ -z "$deadbeef_status" || "$deadbeef_status" == nothing* ]]; then
echo "DeaDBeeF ■ "
prev_pos=""
# а если проигрывается, надо понять, пауза сейчас или нет:
else
# Если предыдущая позиция равна текущей, тогда пауза:
if [[ "$deadbeef_status" == "$prev_pos" ]]; then
echo "DeaDBeeF ⏸ "
# Иначе – не пауза:
else
echo "DeaDBeeF ▶"
fi
fi
# Обновляем предыдущую позицию:
prev_pos="$deadbeef_status"
else
# Если DeaDBeef выклчен, то стираем его из статус-бара
echo ''
fi
sleep 1
done
В следующем примере мы добавим переменную prev_track, которая будет хранить предыдущее имя трэка. Это нужно, чтобы при каждой новой проверке убедиться, что играет тот же трек, иначе "пауза" может появиться если на первой секунде переключать треки – всегда будет сравниваться 0:00 и 0:00 и тогда скрипт распознает это как пауза.
#!/bin/bash
prev_pos="" # предыдущая позиция
prev_track="" # предыдущий трэк
while true; do
if pgrep -f '^/usr/bin/deadbeef( |$)' >/dev/null; then
nowplaying=$(deadbeef --nowplaying '%e|%t' 2>/dev/null)
IFS='|' read -r deadbeef_status current_track <<< "$nowplaying"
# IFS – Internal Field Separator, переменная окружения
if [[ -z "$deadbeef_status" || "$deadbeef_status" == nothing* ]]; then
echo "DeaDBeeF ■ "
prev_pos=""
prev_track=""
else
if [[ "$deadbeef_status" == "$prev_pos" && "$current_track" == "$prev_track" ]]; then
echo "DeaDBeeF ⏸ "
else
echo "DeaDBeeF ▶"
fi
fi
prev_pos="$deadbeef_status"
prev_track="$current_track"
else
echo ''
fi
sleep 1
done
Выносим определение статуса в отдельную функцию:
#!/bin/bash
prev_pos=""
prev_track=""
get_deadbeef_status() {
local prev="$1" prev_t="$2"
local nowplaying
local status_to_return
local current_pos current_track
nowplaying=$(deadbeef --nowplaying '%e|%t' 2>/dev/null)
IFS="|" read -r current_pos current_track <<< "$nowplaying"
if [[ -z "$nowplaying" || "$nowplaying" == nothing* ]]; then
status_to_return="■ "
current_pos=""
else
if [[ "$current_pos" == "$prev" && "$current_track" == "$prev_t" ]]; then
status_to_return="⏸ "
else
status_to_return="▶"
fi
fi
printf "%s|%s|%s\n" "$current_pos" "$current_track" "$status_to_return"
}
while true; do
result=""
if pgrep -f '^/usr/bin/deadbeef( |$)' >/dev/null; then
result="$(get_deadbeef_status "$prev_pos" "$prev_track")"
IFS="|" read -r prev_pos prev_track deadbeef_status <<< "$result"
echo "DeaDBeeF $deadbeef_status"
else
echo ''
fi
sleep 1
done
Переменная POSIXLY_CORRECT показывает используется ли в данный момент POSIX-совместимый режим.
bash --posix -c 'echo $POSIXLY_CORRECT'
# y
bash -c 'echo $POSIXLY_CORRECT'
# скорее всего ничего не выводит