Błyskawiczne przełączanie monitorów w KDE (`kscreen-doctor` + aliasy)
Sytuacja przy moim biurku wygląda tak: laptop wpięty w stację dokującą, do niej dwa monitory zewnętrzne, plus ekran samego laptopa — razem trzy. Do tych samych monitorów podłączony jest również drugi komputer, stacjonarny. Czasem muszę pracować na obu naraz, więc przerzucam jeden monitor na PC (przyciskiem wyboru wejścia), a laptop musi się do tego dostosować. Klawiatura i mysz są sparowane z oboma maszynami, więc tam wystarczy jeden przycisk.
Zostawało jedno wąskie gardło: przeklikiwanie Ustawień ekranu w Plasmie. Wejdź w ustawienia, wyłącz monitor, przeciągnij prostokąty, zastosuj, potwierdź. Kilkanaście sekund i trochę irytacji, kilka razy dziennie. Teraz otwieram terminal, wpisuję d_major i po sprawie.
Co to daje
Cztery aliasy pokrywające wszystkie układy, jakich używam:
| Alias | Efekt |
|---|---|
d_set |
Pełny układ — wszystkie trzy ekrany dla laptopa. |
d_major |
Ultrawide zostaje przy laptopie, boczny monitor oddaję PC-towi. |
d_minor |
Odwrotnie — ultrawide idzie do PC-ta, laptop dostaje boczny monitor. |
d_disable |
Oba zewnętrzne wyłączone, zostaje sam ekran laptopa. |
Kluczowe jest to, że po oddaniu monitora drugiemu komputerowi laptop musi przestać go widzieć. Inaczej okna wciąż będą się otwierać na ekranie, którego nie widzę, kursor będzie uciekał w pustkę, a panel Plasmy zostanie na nieistniejącym monitorze. Sama zmiana wejścia w monitorze tego nie załatwia — laptop dalej trzyma tam aktywny pulpit.
Dlaczego kscreen-doctor, a nie xrandr
Odruchowo sięga się po xrandr, ale to ślepa uliczka: xrandr działa tylko na X11, a Plasma 6 domyślnie startuje na Wayland. Pod Waylandem xrandr widzi co najwyżej warstwę XWayland i nie przestawi fizycznych ekranów.
kscreen-doctor to natomiast oficjalne narzędzie CLI z biblioteki KScreen — rozmawia bezpośrednio z KWin i działa tak samo na X11 i na Wayland. Jest w pakiecie libkscreen-bin, więc w praktyce masz je już zainstalowane razem z Plasmą.
Druga jego zaleta jest ważniejsza, niż się wydaje: konfigurację aplikuje atomowo. Wszystkie ustawienia podane w jednym wywołaniu wchodzą w życie razem, jednym przeliczeniem. Gdyby robić to po kolei, po drodze powstawałyby stany pośrednie z nachodzącymi się lub rozjechanymi ekranami, a Plasma przy każdym kroku przerzucałaby okna.
Poznaj swoje wyjścia
Punkt wyjścia zawsze jest ten sam:
kscreen-doctor -o
Dostajesz listę wyjść z nazwą, statusem, listą trybów i bieżącą geometrią. U mnie w skrócie:
Output: 1 eDP-1 enabled connected priority 1 Panel Geometry: 1520,1440 1920x1200
Output: 2 DP-5 enabled connected priority 2 DisplayPort Geometry: 3440,0 1920x1080
Output: 3 DP-6 enabled connected priority 3 DisplayPort Geometry: 0,0 3440x1440
Trzy rzeczy, które warto z tego wyłuskać:
- nazwa wyjścia (
eDP-1,DP-5,DP-6) — to nią adresujesz ekran w komendach, Geometry— pozycja lewego górnego rogu plus rozdzielczość,- lista
Modes— dokładne rozdzielczości i odświeżania, które dany ekran przyjmie.
Jest jeszcze kscreen-doctor -j, które zwraca to samo jako JSON — przyda się na końcu.
Aliasy
Cały układ mieści się w kilkunastu linijkach w ~/.bash_aliases:
# DP-6 = ultrawide 3440x1440@100, DP-5 = boczny 1920x1080@60, eDP-1 = panel laptopa 1920x1200@60
alias d_set='kscreen-doctor \
output.DP-6.enable output.DP-6.position.0,0 output.DP-6.mode.3440x1440@100 \
output.DP-5.enable output.DP-5.position.3440,0 output.DP-5.mode.1920x1080@60 \
output.eDP-1.enable output.eDP-1.position.1520,1440 output.eDP-1.mode.1920x1200@60'
alias d_major='kscreen-doctor \
output.DP-6.enable output.DP-6.position.0,0 output.DP-6.mode.3440x1440@100 \
output.DP-5.disable \
output.eDP-1.enable output.eDP-1.position.1520,1440 output.eDP-1.mode.1920x1200@60'
alias d_minor='kscreen-doctor \
output.DP-6.disable \
output.DP-5.enable output.DP-5.position.0,0 output.DP-5.mode.1920x1080@60 \
output.eDP-1.enable output.eDP-1.position.0,1080 output.eDP-1.mode.1920x1200@60'
alias d_disable='kscreen-doctor output.DP-6.disable output.DP-5.disable'
alias d_off="echo 'Wygaszę ekran za 2 sek.'; sleep 2; kscreen-doctor --dpms off"
Po edycji wystarczy source ~/.bash_aliases albo nowy terminal.
Jak to działa
Składnia jest jednolita: output.<nazwa>.<ustawienie>[.<wartość>], a ustawień podajesz tyle, ile trzeba, w jednym wywołaniu.
Pozycje to jedna wspólna przestrzeń
position.X,Y to współrzędne lewego górnego rogu ekranu we wspólnym układzie wszystkich monitorów. Początek 0,0 jest umowny — to po prostu róg tego ekranu, który postawisz jako pierwszy. Mój domyślny układ wygląda tak:
x=0 x=3440 x=5360
+---------------------------------+--------------+ y=0
| | DP-5 |
| DP-6 3440x1440 @100 | 1920x1080 |
| (ultrawide) | @60 |
| +--------------+ y=1080
| |
+----------------+----------------+ y=1440
| eDP-1 |
| 1920x1200 @60 |
| (laptop) |
+----------------+ y=2640
x=1520 x=3440
Laptop stoi fizycznie pod ultrawide, stąd y=1440 — czyli dokładnie wysokość ekranu nad nim. x=1520 wyrównuje go do prawej krawędzi ultrawide (1520 + 1920 = 3440), bo tak stoi na biurku; gdybyś chciał go wyśrodkować, byłoby to (3440 − 1920) / 2 = 760. Warto liczyć te wartości świadomie: jeśli zostawisz między ekranami dziurę, kursor będzie się na jej krawędzi zatrzymywał.
To zresztą był realny błąd w mojej starszej wersji aliasów. W d_minor wyłączałem ultrawide, ale boczny monitor zostawał na pozycji 3440,0 — czyli w pustce po ekranie, którego już nie ma. Stąd w wersji powyżej boczny ląduje na 0,0, a laptop pod nim.
Tryby przyjmują zaokrąglone odświeżanie
mode.3440x1440@100 działa, mimo że -o raportuje ten tryb jako 3440x1440@99.98. kscreen-doctor dopasowuje tryb po zaokrąglonej częstotliwości, więc nie trzeba przepisywać ułamków. Jeśli tryb nie zostanie znaleziony, komenda po prostu zgłosi błąd i nie zmieni niczego — atomowość działa też na korzyść przy pomyłkach.
enable trzeba podać jawnie
Samo ustawienie pozycji i trybu nie włączy wyłączonego ekranu. Dlatego w każdym wariancie jest jawne output.<nazwa>.enable — także dla panelu laptopa. Bez tego po d_disable (albo po ręcznym wyłączeniu panelu przy zadokowaniu) aliasy nie potrafiłyby go przywrócić.
Wygaszanie
kscreen-doctor --dpms off gasi ekrany do pierwszego ruchu myszą. Uwaga: --dpms działa tylko na Wayland. Na X11 trzeba po staremu, przez xset dpms force off.
Pułapka: nazwy wyjść potrafią się zmienić
Na tym się przejechałem. Moje aliasy przestały działać z dnia na dzień, bo monitory zmieniły nazwy: DP-3-2 stało się DP-6, a DP-3-1-6 — DP-5.
Nazwa wyjścia to nazwa konektora nadana przez sterownik grafiki, a nie identyfikator monitora. Potrafi się zmienić po przesiadce na inną stację dokującą lub inny port, po aktualizacji jądra bądź sterownika, a przy monitorach łączonych przez DisplayPort MST (typowe w dokach) dochodzi jeszcze numeracja gałęzi łańcucha — stąd te wieloczłonowe DP-3-1-6. Naprawa jest banalna, ale trzeba wiedzieć, gdzie patrzeć: kscreen-doctor -o i przepisanie nazw.
Jeśli wolisz się na to uodpornić, można nazwę wyliczać z natywnej rozdzielczości monitora, która jest znacznie stabilniejsza niż nazwa konektora:
# Zwraca nazwę wyjścia, którego tryb natywny ma podaną rozdzielczość.
d_out() {
kscreen-doctor -j | jq -r --arg res "$1" '
.outputs[] | select(.connected) | . as $o
| ($o.preferredModes[0] // $o.currentModeId) as $pm
| select([$o.modes[] | select(.id == $pm) | "\(.size.width)x\(.size.height)"] | index($res))
| $o.name' | head -1
}
Alias zamienia się wtedy w funkcję, która ustala nazwy dopiero w momencie wywołania — i dzięki temu przetrwa zmianę nazw konektorów:
d_major() {
local uw lcd side
uw=$(d_out 3440x1440); lcd=$(d_out 1920x1200); side=$(d_out 1920x1080)
[ -n "$uw" ] && [ -n "$lcd" ] || { echo "display: brakuje ekranu" >&2; return 1; }
kscreen-doctor \
output."$uw".enable output."$uw".position.0,0 output."$uw".mode.3440x1440@100 \
${side:+output."$side".disable} \
output."$lcd".enable output."$lcd".position.1520,1440 output."$lcd".mode.1920x1200@60
}
Sprawdzenie pustych nazw nie jest ozdobnikiem. Gdyby d_out nic nie znalazł, bez niego powstałby argument output..enable i kscreen-doctor odrzuciłby całe wywołanie mało mówiącym błędem parsowania. Lepiej powiedzieć wprost, którego ekranu brakuje. Monitor boczny zostaje opcjonalny dzięki ${side:+...} — jeśli akurat nie jest podłączony, to po prostu nie ma czego wyłączać.
Funkcja nie może nazywać się tak jak alias
Na tę pułapkę trafiłem, przechodząc z aliasów na funkcje. Jeśli w pliku zostanie alias d_major=..., a niżej dopiszesz funkcję o tej samej nazwie, sourcowanie wysypie się w miejscu, które niewiele tłumaczy:
bash: .bashrc: linia 136: błąd składni przy nieoczekiwanym znaczniku `('
bash: .bashrc: linia 136: `d_major() {'
Powodem jest to, że bash rozwija aliasy przed parsowaniem. Token d_major zostaje podmieniony na treść aliasu, więc parser widzi coś w rodzaju kscreen-doctor output... () { — i słusznie protestuje przy nawiasie. Komunikat wskazuje na funkcję, choć winowajcą jest alias kilkadziesiąt linii wyżej.
I tu jest druga warstwa tej pułapki, na którą nadziałem się zaraz potem. Usunięcie aliasów z pliku nie wystarcza, bo aliasy wczytane przez poprzednią wersję pliku żyją dalej w otwartej powłoce. Plik był już czysty, a source ~/.bashrc w tym samym terminalu nadal się wywalał — tym razem na d_set() {. Nowy terminal działał bez zarzutu, co jest cechą mylącą, bo sugeruje problem z plikiem, a nie ze stanem sesji.
Dlatego linijka porządkująca powinna stać w pliku na stałe, nad definicjami, a nie tylko na czas migracji:
unalias d_set d_major d_minor d_disable d_off d_show 2>/dev/null
Działa to, ponieważ bash sourcując plik czyta i wykonuje polecenia po kolei — unalias zdąży się wykonać, zanim parser dojdzie do definicji funkcji niżej. Efektem ubocznym jest przyjemna idempotencja: plik można sourcować dowolną liczbę razy w tej samej sesji.
Nazwy pomocnicze warto zacząć od podkreślenia (_d_out), żeby nie kolidowały z niczym w przyszłości.
Drobna ciekawostka na koniec: numer linii w komunikacie bywa przesunięty. Stare aliasy były wieloliniowe (z \ na końcach), a ich rozwinięcie wstrzykuje te linie do strumienia, który bash liczy — u mnie błąd raportowany był o trzy linie niżej, niż faktycznie stała definicja funkcji. Warto szukać po nazwie z komunikatu, nie po numerze.
Świadomie porównuję tylko tryb natywny (preferredModes), a nie całą listę trybów. Prawie każdy monitor obsługuje 1920x1080, więc szukanie po dowolnym trybie trafiałoby w pierwszy lepszy ekran. Rozdzielczość natywna jest w praktyce unikalna dla każdego z moich trzech ekranów. Ceną jest to, że dwa identyczne modele obok siebie i tak trzeba rozróżnić inaczej — na przykład po sizeMM albo po prostu po nazwie konektora.
Ograniczenia (świadome kompromisy)
- Nazwy wyjść nie są stabilne. Prosta wersja z aliasami jest czytelna, ale wymaga jednorazowej poprawki po zmianie doku czy sterownika. Wersja z
d_outjest odporna, lecz mniej oczywista w czytaniu. - Poza sesją graficzną trzeba podać środowisko. Uruchamiany z crona, z usługi systemd albo z narzędzia agentowego
kscreen-doctorwywala się na brakuwl_display. PotrzebujeXDG_RUNTIME_DIR,WAYLAND_DISPLAYiDBUS_SESSION_BUS_ADDRESSz działającej sesji. W zwykłym terminalu wszystko to jest już ustawione. - Część opcji jest wyłącznie Waylandowa —
--dpmsoraz skalowanie ułamkowe. Na X11 trzeba szukać zamienników. - To nie zastąpi profili KScreen. Plasma sama zapamiętuje układ per zestaw monitorów i przywraca go po podłączeniu doku. Aliasy są do świadomego, ręcznego przełączania w obrębie tego samego zestawu — czyli dokładnie tam, gdzie automat nie ma jak zgadnąć, czego chcę.
Dostosowanie
- Wybór ekranu głównego —
output.DP-6.priority.1przenosi tam panel i domyślne okna; pozostałe wyjścia dostają kolejne numery. - Skalowanie i obrót —
output.DP-5.scale.1.5,output.DP-5.rotation.left. Przydatne przy monitorze postawionym w pionie. - HDR i jasność —
output.DP-6.hdr.enable,output.DP-6.brightness.60. - Bez terminala — te same komendy można podpiąć pod skróty klawiszowe w Ustawieniach systemowych → Skróty → Dodaj polecenie. Wtedy układ ekranów zmienia się jednym chwytem klawiatury.
Pełną listę ustawień z przykładami daje kscreen-doctor --help.
Podsumowanie
Kilkanaście linijek w ~/.bash_aliases zamienia „wejdź w ustawienia i poprzeciągaj prostokąty" w jedno słowo w terminalu. Przy pracy na dwóch komputerach dzielących te same monitory to różnica między drobnym rytuałem kilka razy dziennie a czynnością, o której się nie myśli. A kscreen-doctor jest już na dysku każdego, kto ma Plasmę — wystarczy kscreen-doctor -o, żeby zacząć.