Problém: root vs. uživatel
Co se dozvíte v článku
Základní výzva při automatizaci desktopu na Linuxu je správa oprávnění. Udev (který detekuje připojení klávesnice) běží pod superuživatelem root a neví nic o vašem grafickém prostředí. Naopak příkazy pro nastavení monitorů ( xrandr, wlr-randr, kscreen-doctor) musejí běžet pod konkrétním uživatelem.
Budujeme robustní systém, který se skládá ze tří částí:
- Služba (Systemd Service): Běží na pozadí, hlídá akcelerometr a řídí logiku.
- Udev pravidlo: Dává signál službě, když se změní hardware (klávesnice).
- Most do user-space: Skript, který najde aktivního uživatele a inicializuje změnu nastavení.
- Agent v user-space: Skript, který za uživatele provede změnu nastavení.
Chytrý „most“ do sezení uživatele
Toto je nejdůležitější část celého řešení. Potřebujeme skript, který zjistí, kdo je přihlášený, a spustí finální příkaz pod jeho identitou. Neaktivní sezení ignorujeme. Nejprve se pokusíme najít pro daného uživatele běžícího agenta. Pokud se to podaří, pošleme mu signál a můžeme končit. Pokud není nalezen, přes sudo spustíme samotný finální skript.
Tento náhradní přístup je sice univerzální pro X11 i Wayland a lze použít i pro jiná řešení a jeho využití je proto obecnější, ale není to nečistější řešení s ohledem na bezpečnost. Pro náš účel je však také potřeba ignorovat přihlašovací obrazovku. Minimálně u SDDM narážíme na problém, protože tato sezení jsou specifická a běžné utility v nich nefungují. Skript je pro větší přehlednost redukovaný.
#!/bin/bash
# Soubor: /usr/bin/asus-check-keyboard-system.sh
# Získáme seznam všech sezení
sessions=$(loginctl list-sessions --no-legend | awk '{print $1}')
for sid in $sessions; do
# Zjistíme uživatele, typ sezení a další detaily
user=$(loginctl show-session "$sid" -p Name --value)
type=$(loginctl show-session "$sid" -p Type --value)
# Zjistíme stav sezení
state=$(loginctl show-session "$sid" -p State --value)
# Pokud sezení není aktivní (uživatel je na pozadí nebo je zamčeno), přeskočíme ho
if [[ "$state" != "active" ]]; then
echo "⚪ Sezení $sid (uživatel $user) není aktivní (stav: $state). Přeskakuji."
continue
fi
# Ignorujeme přihlašovací obrazovku (sddm/gdm)
if [[ "$user" == "sddm" ]]; then continue; fi
# 1. Zkusíme najít běžícího agenta pro daného uživatele
# -u: hledá procesy konkrétního uživatele
# -f: hledá v celé příkazové řádce (protože skript je argument pro bash/interpretr)
AGENT_PID=$(pgrep -u "$user" -f "asus-user-agent.sh" | head -n 1)
if [[ -n "$AGENT_PID" ]]; then
echo "🟢 Nalezen běžící agent (PID $AGENT_PID). Posílám signál SIGUSR1."
kill -SIGUSR1 "$AGENT_PID"
exit 0
fi
# 2. Agent neběží -> Fallback na "Most" (Sudo injection)
echo "⚠️ Agent neběží. Používám přímé volání přes sudo."
if [[ "$type" == "wayland" ]]; then
# Pro Wayland potřebujeme XDG_RUNTIME_DIR
USER_UID=$(loginctl show-session "$sid" -p User --value)
export XDG_RUNTIME_DIR="/run/user/$USER_UID"
# Spustíme finální skript jako nalezený uživatel
sudo -u "$user" \
env XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
/usr/bin/asus-check-keyboard-user.sh
exit 0
fi
# ... podobná logika pro X11 (export DISPLAY a XAUTHORITY) ...
done
Zvolil jsem toto řešení, protože jde o jednoduchý shellscript, snáze čitelný, transparentní, a také proto, že funguje i bez uživatelského agenta. Do budoucna však část se sudo nebude funkčnířešení kvůli bezpečnosti, kdy uživatelská sezení budou daleko lépe a přísněji izolována.
Služba jako centrální mozek
V minulém díle jsme si nastínili princip, zde budeme finalizovat a předchozí řešení si proto trochu zkomplikujeme. Proč pro detekci klávesnice nepoužít přímo udev `RUN+=`? Protože udev procesy zabíjí, pokud trvají moc dlouho. Místo toho využijeme Systemd službu. Ta má dvě funkce:
- Běží trvale a čte data z akcelerometru (pro rotaci).
- Umí přijmout signál
reload, který bezpečně spustí náš skript pro klávesnici.
[Unit] Description=ASUS Duo – ovládání obrazovek After=graphical.target Wants=graphical.target [Service] Type=simple # Hlavní proces: Sleduje rotaci v nekonečné smyčce ExecStart=/usr/bin/asus-check-rotation.sh # Trik: Při 'systemctl reload' se spustí kontrola klávesnice, aniž by se služba restartovala ExecReload=/usr/bin/asus-check-keyboard-system.sh # Důležité: Restart služby při chybě (např. pád monitor-sensor) Restart=on-failure RestartSec=3 [Install] WantedBy=graphical.target
Pravidlo pro Udev: jen „šťouchnout“ a jít
Podívejme se tedy na upravená pravidla. Díky tomu, že máme logiku ve službě, je udev pravidlo triviální. Pouze řekne systemd: Něco se změnilo, znovu načti konfiguraci.
Tím se vyhneme timeoutům i složitému skriptování v udevu. Jsou v nich již doplněny konkrétní hodnoty.
# /etc/udev/rules.d/99-asus-keyboard.rules
# Při připojení (add) i odpojení (remove) pouze reloadujeme službu
# Používáme ENV, aby pravidlo fungovalo i při odpojení, kdy už atributy neexistují
ACTION=="add", SUBSYSTEM=="usb", ENV{ID_VENDOR_ID}=="0b05", ENV{ID_MODEL_ID}=="19b6", RUN+="/bin/systemctl reload asus-bottom-screen-init.service"
ACTION=="remove", SUBSYSTEM=="usb", ATTRS{idVendor}=="${0b05}", ATTRS{idProduct}=="${19b6}", RUN+="/bin/systemctl reload asus-bottom-screen-init.service"
Bonus: Co když se notebook uspí?
Častý problém u senzorů na Linuxu je, že po probuzení ze spánku (suspend) přestanou posílat data. Jednoduchým řešením je restartovat naši službu po probuzení pomocí hooku v /lib/systemd/system-sleep/.
#!/bin/sh
# /lib/systemd/system-sleep/99-asus-screen-resume
if [ "${1}" = "post" ]; then
systemctl restart asus-bottom-screen-init.service
fi
Uživatelský agent
Poslední část je samotný uživatelský agent, služba, která běží na pozadí uživatelského sezení a čeká na signál. Po jeho příchodu spustí finální skript.
#!/bin/bash
# asus-user-agent.sh
### 1. Singleton Check (Robustní verze s flock)
# Otevřeme soubor zámku na deskriptoru 200
LOCKFILE="/tmp/asus-user-agent.lock"
exec 200> "$LOCKFILE"
# Pokusíme se získat exkluzivní zámek (-x) bez čekání (-n).
# Pokud to nejde (jiná instance už ho má), skončíme.
if ! flock -n -x 200; then
echo "Agent už běží (zámek je aktivní)."
exit 1
fi
# 2. Funkce pro reakci na signál
obsluha_signalu() {
echo "📩 Signál přijat! Spouštím rotaci..."
# Voláme existující uživatelský skript, který už máte hotový
/usr/bin/asus-check-keyboard-user.sh
}
# 3. Nastražení pastí
# SIGUSR1 = Spustí akci
# SIGTERM/SIGINT = Slušně ukončí skript (volitelné, ale dobré pro pořádek)
trap 'obsluha_signalu' SIGUSR1
trap 'exit 0' SIGTERM SIGINT
echo "Agent spuštěn (PID $$). Čekám na signál SIGUSR1..."
# 4. Nekonečná smyčka
# Použití 'wait' je trik, aby skript reagoval na signál okamžitě a nečekal na doběhnutí sleepu
while true; do
sleep 1 & wait $!
done
Automatizace instalace: od skriptu k balíčku .deb
Aby bylo řešení přenositelné i na jiné stroje (nebo pro případ, že ASUS změní ID hardwaru), není dobré psát USB identifikátory „natvrdo“ přímo do systémových souborů. Místo toho jsem vytvořil systém šablon a konfiguračního skriptu, který se zabalí do standardního balíčku .deb.
Generování pravidel na míru
Jádrem flexibility je skript asus-check-keyboard-genrules.sh. Ten neobsahuje žádná data, ale načte si konfiguraci z /etc/asus-check-keyboard.cfg a pomocí příkazu envsubst vyplní šablonu pravidla pro udev.
#!/bin/bash # /usr/bin/asus-check-keyboard-genrules.sh # 1. Načtení uživatelské konfigurace (VID, PID, názvy displejů) source /etc/asus-check-keyboard.cfg export VENDOR_ID PRODUCT_ID ... # 2. Vyplnění šablony a uložení do systémového adresáře udev envsubst < /usr/share/99-asus-keyboard.rules.template > /usr/lib/udev/rules.d/99-asus-keyboard.rules
Díky tomu může mít každý uživatel jiné ID klávesnice, aniž by musel ručně editovat systémová pravidla v /etc/udev/.
Co se děje při instalaci
Pro vytvoření balíčku využívám standardní adresářovou strukturu DEBIAN/. Nejdůležitější roli hraje poinstalační skript ( postinst), který zajišťuje interaktivitu:
- Interaktivní konfigurace: Pomocí
debconfse instalátor zeptá uživatele na identifikátory klávesnice a názvy displejů (např. eDP-1 vs eDP-2). - Vytvoření configu: Odpovědi uloží do
/etc/asus-check-keyboard.cfg. - Aktivace služeb: Zavolá generátor pravidel, restartuje udev (
udevadm trigger) a povolí systemd službu.
Soubor DEBIAN/control pak hlídá závislosti, aby měl uživatel nainstalované potřebné nástroje jako udev, systemd či kscreen.
Modulární systém nejen pro Zenbook
Tímto způsobem jsme vytvořili modulární systém. Udev řeší hardware, Systemd řeší běh na pozadí a skripty řeší logiku zobrazení. Tento princip můžete využít nejen na Zenbooku Duo, ale třeba i pro automatické dokování nebo změnu profilů napájení.
Celý projekt je záměrně napsán v čistých shellových skriptech. Nejen kvůli jednoduchosti a minimálním závislostem, ale především pro transparentnost a přenositelnost. Kód je čitelný, snadno auditovatelný a kdokoli si jej může upravit pro své potřeby. Díky tomu není řešení vázáno na konkrétní model.
Pokud v budoucnu vyjde Zenbook s jiným procesorem nebo budete chtít princip aplikovat na dvoudisplejový stroj jiného výrobce, logika zůstává stejná – stačí často jen změnit ID zařízení v konfiguraci.
Vše, co jsem vytvořil, případně ještě vytvářím, je dostupné na GitHubu.
