Всё как у больших. Автозагрузка приложений в оконных менеджерах linux

Kate

Administrator
Команда форума
Вывод systemd-analyze dot --user ‘i3.service’ | dot -Tpng | imv -


Вывод systemd-analyze dot --user ‘i3.service’ | dot -Tpng | imv -

Как-то раз, листая сообщения в профильном systemd чате, в телеграм, я наткнулся на следующий кусок man systemd.special…


xdg-desktop-autostart.target
The XDG specification defines a way to autostart applications using XDG desktop files.
systemd ships systemd-xdg-autostart-generator(8) for the XDG desktop files in autostart
directories. Desktop Environments can opt-in to use this service by adding a Wants=dependency
on xdg-desktop-autostart.target.

О как интересно, подумалось мне. Можно реализовать функционал полноценныхDesktop Environments, по автоматическому запуску приложений, при старте. А у меня как раз i3wm, который таковым не является и которому такой функционал не помешал бы. Надо это дело исследовать. Тогда я ещё не знал во что ввязался. Как оказалось, не всё так просто.


Переменные XDG, freedesktop.org, desktop-файлы и autostart​


Пользователям полноценных линуксовых графических окружений (KDE, Gnome, Mate etc) прекрасно известна возможность автозапуска приложений при логине пользователя в систему, разработанную инициативной группой Freedesktop.org (ранее X Desktop Group, или XDG), подобная той, что существует, например, в Windows. Данный функционал обеспечивается обычными *.desktop файлами, но лежащими по определённым путям:


  • $XDG_CONFIG_DIRS/autostart/ (/etc/xdg/autostart/ по умолчанию) — общесистемная директория, для всех пользователей. Туда, обычно, попадают десктоп файлы при установке софта пакетным менеджером.
  • $XDG_CONFIG_HOME/autostart/ ($HOME/.config/autostart/ по умолчанию) — Пользовательская директория, имеющая больший приоритет, нежели общесистемная, то есть если в пользовательской лежит десктоп файл с таким же именем, что и в общесистемной, будет использован именно пользовательский файл.

Если в этих переменных семейства XDG directories не указано иное, или эти переменные отсутствуют (так происходит в большинстве классических дистрибутивов, привет NixOS!), будут использованы значения по умолчанию.


Итак, с директориями определились. Файлы в них можно:


  • Симлинкнуть из стандартных путей: $XDG_DATA_DIRS/applications/ (/usr/share/applications/ по умолчанию) или из пользовательского $XDG_DATA_HOME/applications/ (~/.local/share/applications по умолчанию), куда, кстати, любят класть свои файлы Steam, Itch.io или Wine.
  • Можно создать самому, написав десктоп файлы руками.
  • Можно нажать галочку «Запускать при старте системы», в каком-нибудь софте, например, в телеграм клиенте и тогда уже софт сам создаст в $XDG_CONFIG_HOME/autostart/ свой файл.

Всё хорошо. Одно плохо. Это не работает, как минимум, в Leftwm, Spectrwm, xmonad, bspwm, dwm (без патчей точно) и, разумеется, в любимом i3wm. Просто потому, что у них отсутствует session manager. И вот тут мы переходим к самому интересному. Встречайте! systemd!


Systemd как спасательный круг тайловых (и не очень) оконных менеджеров​


Эта глава будет самой объёмной. Тут мы разберёмся кто и как может помочь разобрать залежи desktop файлов, кто, как и когда их запустит, и при чём тут вообще systemd. Поехали!


▍ Developers, developers, developers! Генераторы, генераторы, генераторы!​


Systemd, как известно, это не только система инициализации, логгирования событий, но и набор готовых дополнительных утилит, готовых сервисов с их юнитами, система управления сетью, and more… Среди прочего systemd может выступать в качестве системного менеджера для пользовательских сервисов — юнитов, работающих в пространстве пользователя. То есть после логина пользователя в систему запускается ещё один экземпляр /usr/lib/systemd только уже от пользователя и позволяет запускать юниты в пространстве пользователя, с наследованием его окружения и правами.

Среди других интересных и полезных вещей в systemd есть такая штука, как генераторы. Маленькие утилиты запускаемые на раннем этапе загрузки системы или сразу после логина пользователя и выполняющие динамическую генерацию юнитов и/или их конфигов. Например, есть генератор, который читает /etc/fstab и на его основе генерирует *.mount юниты. Или генератор, который вычитывает файлы *.conf из /etc/environment.d/ и $HOME/.config/environment.d/ и на их основе собирает переменные которые пользователь видит набирая команду env и которые наследуются всеми пользовательскими юнитами. Среди прочего есть и генератор, который пробегает по $XDG_CONFIG_DIRS/autostart и $XDG_CONFIG_HOME/autostart, вычитывает *.desktop файлы, генерирует пользовательские *.service юниты и кладёт их в /run/user/<UID>/systemd/generator.late.

Всё хорошо и замечательно, но есть одно но. Если есть сервисы, их должен кто-то вовремя запустить. То есть запустить ровно тогда, когда будет запущена графическая оболочка… Если посмотреть, произвольный такой юнит, мы увидим там упоминание target-а graphical-session.target (Юнит на основе десктоп файла апплета управления Bluetooth cat /run/user/1000/systemd/generator.late/app-blueman@autostart.service):


# Automatically generated by systemd-xdg-autostart-generator

[Unit]
Documentation=man:systemd-xdg-autostart-generator(8)
SourcePath=/etc/xdg/autostart/blueman.desktop
PartOf=graphical-session.target

Description=Blueman Applet
After=graphical-session.target

[Service]
Type=exec
ExecStart=:/usr/bin/blueman-applet
Restart=no
TimeoutSec=5s
Slice=app.slice

Хорошо, но что это за graphical-session.target? В выводе systemctl --user --type=target, если выполнить команду из-под i3wm никакого такого таргета не наблюдается. А вот если запустить из-под, например, Gnome, то вполне:


Многабукв и ничего интересного ;-)



И что же со всем этим делать и как быть? Как получить заветный target?


▍ Графическая оболочка тоже сервис. Подсматриваем в Gnome​


Если в Gnome запустить systemctl --user --type=service можно заметить интересный сервис:


UNIT LOAD ACTIVE SUB DESCRIPTION
at-spi-dbus-bus.service loaded active running Accessibility services bus
dbus.service loaded active running D-Bus User Message Bus

...

gnome-shell-x11.service loaded active running GNOME Shell on X11

...

Становится всё интереснее. Значит Gnome запускается как systemd сервис (gnome-shell-x11.service). Ну а уж из сервиса можно реализовывать любые зависимости. В принципе ожидаемо. Но как реализовывать такое для произвольной графической оболочки, которая не заточена под такие тонкие извращения? Надеемся на то не должно быть сложно… Перво-наперво смотрим в юнит (systemctl cat --user gnome-shell-x11.service) и понимаем, что ничего не понимаем и что мы немножко попали…


# /usr/lib/systemd/user/gnome-shell-x11.service
[Unit]
Description=GNOME Shell on X11
# On X11, try to show the GNOME Session Failed screen
OnFailure=gnome-shell-disable-extensions.service gnome-session-failed.target
OnFailureJobMode=replace
CollectMode=inactive-or-failed
RefuseManualStart=on
RefuseManualStop=on

After=gnome-session-manager.target

Requisite=gnome-session-initialized.target
PartOf=gnome-session-initialized.target
Before=gnome-session-initialized.target

# The units already conflict because they use the same BusName
#Conflicts=gnome-shell-wayland.service

# Limit startup frequency more than the default
StartLimitIntervalSec=15s
StartLimitBurst=3

[Service]
Type=notify
ExecStart=/usr/bin/gnome-shell
# Exit code 1 means we are probably *not* dealing with an extension failure
SuccessExitStatus=1
# On X11 we want to restart on-success (Alt+F2 + r) and on-failure.
Restart=always

Ладно, чёрт с ним, идём смотреть в *.desktop файл xsessions(cat /usr/share/xsessions/gnome.desktop)…


[Desktop Entry]
Name=GNOME
Comment=This session logs you into GNOME
Exec=env GNOME_SHELL_SESSION_MODE=gnome /usr/bin/gnome-session --systemd --session=gnome
TryExec=/usr/bin/gnome-shell
Type=Application
DesktopNames=GNOME
X-GDM-SessionRegisters=true
X-Ubuntu-Gettext-Domain=gnome-session-3.0

… и понимаем, что попали несколько серьёзнее чем хотелось бы и что гном, в данном случае, нам мало чем поможет. Он изначально заточен под работу с systemd. Идём в эти наши интернеты.


▍ Выходим на финишную прямую. Пишем враппер, юнит и наконец удачно стартуем​


Не буду затягивать и утомлять читателя подробностями того, как и где приходилось выуживать информацию по крупицам. Это были и маны и ArchWiki и чёрт его знает что ещё. Лучше сразу приведу готовые, в меру откомментированные файлы.


Итак, копируем дефолтный /usr/share/xsessions/i3.desktop в /usr/share/xsessions/i3-systemd.desktop и немного модифицируем.


[Desktop Entry]
### Было: ###
# Name=i3
### Стало: ###
Name=i3 via systemd
Comment=improved dynamic tiling window manager
### Было: ###
# Exec=i3
# TryExec=i3
### Стало: ###
Exec=i3-service
TryExec=i3-service
Type=Application
X-LightDM-DesktopName=i3
DesktopNames=i3
Keywords=tiling;wm;windowmanager;window;manager;

Теперь нам нужно написать враппер i3-service который будет подготавливать окружение и запускать i3wm в качестве сервиса. Ну и, разумеется, сам i3.service файл тоже должен быть написан. Итак враппер /usr/local/bin/i3-service:


#!/usr/bin/env sh

# Импортируем и загружаем в d-bus сессию переменные из логин менеджера.
/etc/X11/xinit/xinitrc.d/50-systemd-user.sh

systemctl --user import-environment XDG_SEAT XDG_VTNR XDG_SESSION_ID XDG_SESSION_TYPE XDG_SESSION_CLASS

if command -v dbus-update-activation-environment >/dev/null 2>&1; then
dbus-update-activation-environment XDG_SEAT XDG_VTNR XDG_SESSION_ID XDG_SESSION_TYPE XDG_SESSION_CLASS
fi

# Загружаем иксовые ресурсы.

userresources=$HOME/.Xresources
usermodmap=$HOME/.Xmodmap
sysresources=/etc/X11/xinit/.Xresources
sysmodmap=/etc/X11/xinit/.Xmodmap

if [ -f $sysresources ]; then
xrdb -merge $sysresources
fi

if [ -f $sysmodmap ]; then
xmodmap $sysmodmap
fi

if [ -f "$userresources" ]; then
xrdb -merge "$userresources"
fi

if [ -f "$usermodmap" ]; then
xmodmap "$usermodmap"
fi

# Запускаем xinitrc* скрипты.

if [ -d /etc/X11/xinit/xinitrc.d ] ; then
for f in /etc/X11/xinit/xinitrc.d/?*.sh ; do
[ -x "$f" ] && . "$f"
done
unset f
fi

# И собственно запускаем сервис.
exec systemctl --wait --user start i3.service

Ну и наконец вишенка на нашем торте, сам /etc/systemd/user/i3.service:


[Unit]
Description=i3wm tiling window manager for X
Documentation=man:i3(5)
Wants=graphical-session-pre.target
After=graphical-session-pre.target
# Самое главное, биндимся к таргету графической сессии..
BindsTo=graphical-session.target
PartOf=graphical-session.target
# ... и не забываем включить таргет, ради которого всё затевалось.
Before=xdg-desktop-autostart.target
Wants=xdg-desktop-autostart.target

[Service]
Type=simple
# Запускаем i3 через d-bus launcher. Мы же хотим, чтоб у нас работал d-bus?
ExecStart=/usr/bin/dbus-launch --sh-syntax --exit-with-session i3 --shmlog-size 0
Restart=on-failure
RestartSec=5
TimeoutStopSec=10

  • Записываемся.
  • Для проверки добавляем в автозагрузку, например, тот же клиент телеграма.
  • Идём на перезагрузку и в дисплейном менеджере при старте системы, выбираем пункт «i3 via systemd»

Что в итоге?​


  • Работает автозагрузка, прямо как в каком-нить гноме.
  • Бонусом получили graphical-session.target, к которому можно биндить сервисы, зависящие от запущенной графической оболочки. Например, до этого у меня падал, при загрузке юнит clipboard manager-а, в результате приходилось костылять таймаут… Теперь не падает.
  • Можно выкинуть из конфига i3 всё, что запускается при старте (Директива exec --no-startup-id и это вот всё) и упаковать в отдельные аккуратные пользовательские *.service и по-человечески рулить ими в процессе работы. Например, отключать и включать lockscreen простым systemctl --user start/stop
  • Для автозагружаемых юнитов, сгенерённых из *.desktop файлов, в самих этих файлах их можно отключать, добавив строчку Hidden=true

Ну и вообще, приятно быть первооткрывателем. Ибо в процессе гугления и чтения манов, готового рецепта обнаружено не было. Так что любители wm, не относящиеся к systemd хейтерам. Пробуйте. За месяц использования был замечен ровно один косяк. Не работает gvgs-* функционал в pcman-fm, если его запустить хоткеем из i3 Но если запустить из rofi, волшебным образом всё начинает работать. Возможно я забыл импортировать какую-то переменную в d-bus Ну и, чтоб не копипастить, ссылка на гитхаб.

 
Сверху