Linux 07 Wrzesień 2026

Błyskawiczne przełączanie monitorów w KDE (`kscreen-doctor` + aliasy)

Dostępne też w:

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_out jest 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-doctor wywala się na braku wl_display. Potrzebuje XDG_RUNTIME_DIR, WAYLAND_DISPLAY i DBUS_SESSION_BUS_ADDRESS z działającej sesji. W zwykłym terminalu wszystko to jest już ustawione.
  • Część opcji jest wyłącznie Waylandowa — --dpms oraz 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.1 przenosi 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ąć.