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

от автора

Вывод 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, то вполне:

Многабукв и ничего интересного 😉

  UNIT                                                LOAD   ACTIVE SUB     DESCRIPTION                                                                          basic.target                                        loaded active active  Basic System                                                                         default.target                                      loaded active active  Main User Target                                                                     gnome-session-initialized.target                    loaded active active  GNOME Session is initialized                                                         gnome-session-manager.target                        loaded active active  GNOME Session Manager is ready                                                       gnome-session-pre.target                            loaded active active  Tasks to be run before GNOME Session starts                                          gnome-session-x11-services.target                   loaded active active  GNOME session X11 services                                                           gnome-session-x11.target                            loaded active active  GNOME X11 Session                                                                    gnome-session-x11@zorin.target                      loaded active active  GNOME X11 Session (session: gnome)                                                   gnome-session.target                                loaded active active  GNOME Session                                                                        gnome-session@gnome.target                          loaded active active  GNOME Session (session: gnome)                                                       graphical-session-pre.target                        loaded active active  Session services which should run early before the graphical session is brought up   graphical-session.target                            loaded active active  Current graphical user session                                                       org.gnome.SettingsDaemon.A11ySettings.target        loaded active active  GNOME accessibility target                                                           org.gnome.SettingsDaemon.Color.target               loaded active active  GNOME color management target                                                        org.gnome.SettingsDaemon.Datetime.target            loaded active active  GNOME date & time target                                                             org.gnome.SettingsDaemon.Housekeeping.target        loaded active active  GNOME maintenance of expirable data target                                           org.gnome.SettingsDaemon.Keyboard.target            loaded active active  GNOME keyboard configuration target                                                  org.gnome.SettingsDaemon.MediaKeys.target           loaded active active  GNOME keyboard shortcuts target                                                      org.gnome.SettingsDaemon.Power.target               loaded active active  GNOME power management target                                                        org.gnome.SettingsDaemon.PrintNotifications.target  loaded active active  GNOME printer notifications target                                                   org.gnome.SettingsDaemon.Rfkill.target              loaded active active  GNOME RFKill support target                                                          org.gnome.SettingsDaemon.ScreensaverProxy.target    loaded active active  GNOME FreeDesktop screensaver target                                                 org.gnome.SettingsDaemon.Sharing.target             loaded active active  GNOME file sharing target                                                            org.gnome.SettingsDaemon.Smartcard.target           loaded active active  GNOME smartcard target                                                               org.gnome.SettingsDaemon.Sound.target               loaded active active  GNOME sound sample caching target                                                    org.gnome.SettingsDaemon.Wacom.target               loaded active active  GNOME Wacom tablet support target                                                    org.gnome.SettingsDaemon.XSettings.target           loaded active active  GNOME XSettings target                                                               org.gnome.Shell.target                              loaded active active  GNOME Shell                                                                          paths.target                                        loaded active active  Paths                                                                                sockets.target                                      loaded active active  Sockets                                                                              timers.target                                       loaded active active  Timers                                                                              LOAD   = Reflects whether the unit definition was properly loaded. ACTIVE = The high-level unit activation state, i.e. generalization of SUB. SUB    = The low-level unit activation state, values depend on unit type.  31 loaded units listed. Pass --all to see loaded but inactive units, too. To show all installed unit files use 'systemctl list-unit-files'. 

И что же со всем этим делать и как быть? Как получить заветный 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 Ну и, чтоб не копипастить, ссылка на гитхаб.


ссылка на оригинал статьи https://habr.com/ru/articles/583328/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *