Вернуться назад

swaybar

В дефолтном конфиге 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 требует бесконечного цикла. Любой статус-командный скрипт должен работать постоянно и время от времени печатать новые строки.

RU / EN

Очень удобно, когда мы видим в статус-баре отображение текущей раскладки. Для этого создадим скрипт ~/.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 запущен и не отображать её, когда 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

Статус DeaDBeeF

#!/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

POSIX

Переменная POSIXLY_CORRECT показывает используется ли в данный момент POSIX-совместимый режим.

bash --posix -c 'echo $POSIXLY_CORRECT'
   # y

bash -c 'echo $POSIXLY_CORRECT'
   # скорее всего ничего не выводит