diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 59df7737c..fa693906d 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -4,6 +4,7 @@ packages: - eudev-dev - gdk-pixbuf-dev - json-c-dev + - lcms2-dev - libdisplay-info-dev - libevdev-dev - libinput-dev @@ -28,23 +29,28 @@ sources: tasks: - wlroots: | cd wlroots - meson --prefix=/usr build -Dexamples=false + meson setup --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - setup: | cd sway - meson build --fatal-meson-warnings -Dauto_features=enabled -Dtray=disabled + meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dtray=disabled - build: | cd sway ninja -C build - build-no-xwayland: | - cd sway + cd wlroots meson configure build -Dxwayland=disabled ninja -C build + sudo ninja -C build install + + cd ../sway + meson configure build --clearcache + ninja -C build - build-static: | cd sway mkdir subprojects ln -s ../../wlroots subprojects/wlroots rm -rf build - meson build --fatal-meson-warnings --default-library=static --force-fallback-for=wlroots + meson setup build --fatal-meson-warnings --default-library=static --force-fallback-for=wlroots ninja -C build diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 9972c01ad..2c8ffcf45 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -3,6 +3,7 @@ packages: - cairo - gdk-pixbuf2 - json-c + - lcms2 - libdisplay-info - libegl - libinput @@ -25,12 +26,12 @@ sources: tasks: - wlroots: | cd wlroots - meson --prefix=/usr build -Dexamples=false + meson setup --prefix=/usr build -Dexamples=false ninja -C build sudo ninja -C build install - setup: | cd sway - meson build --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=libsystemd + meson setup build --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=libsystemd - build: | cd sway ninja -C build diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index 29c6312af..a3df06e6d 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -8,6 +8,7 @@ packages: - devel/pkgconf - graphics/cairo - graphics/gdk-pixbuf2 +- graphics/lcms2 - graphics/wayland - graphics/wayland-protocols - textproc/scdoc @@ -26,7 +27,7 @@ packages: - x11/libX11 - x11/pixman - x11/xcb-util-wm -- x11-servers/xwayland-devel +- x11-servers/xwayland - misc/hwdata sources: - https://github.com/swaywm/sway @@ -38,7 +39,7 @@ tasks: cd subprojects ln -s ../../wlroots wlroots cd .. - meson build --fatal-meson-warnings -Dtray=enabled -Dsd-bus-provider=basu + meson setup build --fatal-meson-warnings -Dtray=enabled -Dsd-bus-provider=basu - build: | cd sway ninja -C build diff --git a/README.ar.md b/README.ar.md index 4f9bf828a..2f64f2b53 100644 --- a/README.ar.md +++ b/README.ar.md @@ -37,7 +37,7 @@ _\* Compile-time dep_ نفذ هذه الأوامر: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.az.md b/README.az.md new file mode 100644 index 000000000..50c8b0dec --- /dev/null +++ b/README.az.md @@ -0,0 +1,66 @@ +# sway + +sway [i3]-ə uyğun [Wayland] kompozitorudur. [Tez-tez verilən sualları] oxuyun. +[IRC kanalına] qoşulun ("irc.libera.chat"-da #sway). + +## Buraxılış İmzaları + +Buraxılışlar [E88F5E48] ilə imzalanıb və [GitHub-da][GitHub releases] dərc edilib. + +## Quraşdırma + +### Paketlərdən + +Sway bir çox distributivdə mövcuddur. Öz distributiviniz üçün +"sway" paketini quraşdırmağa çalışın. + +### Mənbə kodundan kompilyasiya + +Test və ya inkişaf üçün sway və wlroots-un HEAD-ini qurmaq istəyirsinizsə, +[bu viki səhifəsini][Development setup] nəzərdən keçirin. + +Asılılıqları quraşdırın: + +* meson \* +* [wlroots] +* wayland +* wayland-protocols \* +* pcre2 +* json-c +* pango +* cairo +* gdk-pixbuf2 (ixtiyari: sistem trayı üçün əlavə şəkil formatları) +* [swaybg] (ixtiyari: divar kağızı) +* [scdoc] (ixtiyari: man səhifələri) \* +* git (ixtiyari: versiya məlumatı) \* + +_\* Kompilyasiya asılılıqları_ + +Bu əmrləri icra edin: + + meson setup build/ + ninja -C build/ + sudo ninja -C build/ install + +## Konfiqurasiya + +Əgər artıq i3-dən istifadə edirsinizsə, i3 konfiqurasiyanızı `~/.config/sway/config` +ünvanına köçürün və o, dərhal işləyəcək. Əks halda, nümunə konfiqurasiya faylını +`~/.config/sway/config` ünvanına köçürün. O, adətən `/etc/sway/config` ünvanında yerləşir. +Konfiqurasiya haqqında məlumat üçün `man 5 sway` əmrini icra edin. + +## İşə Salma + +TTY-dan `sway`-ı işə salın. Bəzi ekran menecerləri işləyə bilər, lakin sway tərəfindən +dəstəklənmir (gdm-in kifayət qədər yaxşı işlədiyi məlumdur). + +[i3]: https://i3wm.org/ +[Wayland]: http://wayland.freedesktop.org/ +[Tez-tez verilən sualları]: https://github.com/swaywm/sway/wiki +[IRC kanalına]: https://web.libera.chat/gamja/?channels=#sway +[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 +[GitHub releases]: https://github.com/swaywm/sway/releases +[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots +[swaybg]: https://github.com/swaywm/swaybg/ +[scdoc]: https://git.sr.ht/~sircmpwn/scdoc diff --git a/README.cs.md b/README.cs.md index 41efba54a..0b4085e7f 100644 --- a/README.cs.md +++ b/README.cs.md @@ -1,25 +1,24 @@ # sway -[English][en] - **[Česky][cs]** - [Deutsch][de] - [Dansk][dk] - [Español][es] - [Français][fr] - [Svenska][sv] - [Ελληνικά][gr] - [हिन्दी][hi] - [Magyar][hu] - [فارسی][ir] - [Italiano][it] - [日本語][ja] - [한국어][ko] - [Nederlands][nl] - [Polski][pl] - [Português][pt] - [Română][ro] - [Русский][ru] - [Türkçe][tr] - [Українська][uk] - [中文-简体][zh-CN] - [中文-繁體][zh-TW] - -sway je s [i3] kompatibilní [Wayland] kompozitor. Přečtěte si [FAQ]. Připojte se na -[IRC kanál][IRC channel] \(#sway na irc.libera.chat). +sway je [waylandový][Wayland] kompozitor kompatibilní s [i3]. Přečtěte si +[FAQ (anglicky)][FAQ]. Připojte se na [IRC kanál (anglicky)][IRC channel] +\(#sway na irc.libera.chat). ## Podpisy vydání -Vydání jsou podepsána [E88F5E48] a publikována [na GitHubu][GitHub releases]. +Vydané verze jsou podepsány klíčem [E88F5E48] a publikovány +[na GitHubu][GitHub releases]. ## Instalace -### Z balíčků +### Z balíků -Sway je dostupný ve spoustě distribucí. Zkuste nainstalovat balíček "sway" ve vaší -distribuci. +Sway je dostupný v mnoha distribucích. Zkuste v té vaší nainstalovat balík "sway". ### Kompilace ze zdrojových kódů -Podívejte se na [tuto stránku wiki][Development setup], pokud chcete sestavit HEAD -sway a wlroots pro testování nebo vývoj. +Pokud chcete sestavit HEAD repozitáře sway a wlroots pro testování nebo vývoj, +použijte návod na [této stránce na wiki (anglicky)][Development setup]. Nainstalujte závislosti: @@ -31,15 +30,16 @@ Nainstalujte závislosti: * json-c * pango * cairo -* gdk-pixbuf2 (volitelné: oznamovací oblast) -* [scdoc] (volitelné: manuálové stránky) \* +* gdk-pixbuf2 (volitelné: dodatečné formáty ikon pro oznamovací oblast) +* [swaybg] (volitelné: tapeta plochy) +* [scdoc] (volitelné: man stránky) \* * git (volitelné: informace o verzi) \* -_\* Závislost pouze pro sestavení_ +_\* Závislost pouze pro kompilaci_ Spusťte tyto příkazy: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install @@ -52,16 +52,16 @@ Pro více informací o konfiguraci spusťte `man 5 sway`. ## Spuštění -Spusťte `sway` z TTY. Některé správce zobrazení mohou fungovat, ale nejsou -podporovány sway (je známo, že gdm funguje docela dobře). +Spusťte `sway` z TTY nebo ze správce displeje. [en]: https://github.com/swaywm/sway#readme +[ar]: README.ar.md [cs]: README.cs.md [de]: README.de.md [dk]: README.dk.md [es]: README.es.md [fr]: README.fr.md -[sv]: README.sv.md +[ge]: README.ge.md [gr]: README.gr.md [hi]: README.hi.md [hu]: README.hu.md @@ -70,10 +70,12 @@ podporovány sway (je známo, že gdm funguje docela dobře). [ja]: README.ja.md [ko]: README.ko.md [nl]: README.nl.md +[no]: README.no.md [pl]: README.pl.md [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md +[sv]: README.sv.md [tr]: README.tr.md [uk]: README.uk.md [zh-CN]: README.zh-CN.md @@ -86,4 +88,5 @@ podporovány sway (je známo, že gdm funguje docela dobře). [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots +[swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc diff --git a/README.de.md b/README.de.md index 68b411d95..0c0b18637 100644 --- a/README.de.md +++ b/README.de.md @@ -1,21 +1,21 @@ # Sway -Sway ist ein [i3](https://i3wm.org/)-kompatibler [Wayland](http://wayland.freedesktop.org/)-Compositor. Lies die [FAQ](https://github.com/swaywm/sway/wiki). Tritt dem [IRC Channel](https://web.libera.chat/gamja/?channels=#sway) bei (#sway on irc.libera.chat; Englisch). +Sway ist ein [i3]-kompatibler [Wayland]-Compositor. Lies die [FAQ]. Tritt dem [IRC Channel] bei (#sway on irc.libera.chat; Englisch). ## Signaturen -Jedes Release wird mit dem PGP-Schlüssel [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) signiert und [auf GitHub](https://github.com/swaywm/sway/releases) veröffentlicht. +Jeder Release wird mit dem PGP-Schlüssel [E88F5E48] signiert und [auf GitHub][GitHub releases] veröffentlicht. ## Installation ### Über die Paketverwaltung -Sway kann in vielen Distributionen direkt durch die Paketverwaltung installiert werden. Versuche einfach das Packet "sway" zu installieren. +Sway kann in vielen Distributionen direkt durch die Paketverwaltung installiert werden. Versuche einfach das Paket "sway" zu installieren. ### Quellcode selbst kompilieren sway benötigt die folgenden Pakete: -* meson\* -* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots) +* meson \* +* [wlroots] * wayland * wayland-protocols\* * pcre2 @@ -23,21 +23,34 @@ sway benötigt die folgenden Pakete: * pango * cairo * gdk-pixbuf2 (Optional, wird für das Benachrichtigungsfeld (System Tray) benötigt) -* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (Optional, wird für die Dokumentation (Man Pages) benötigt)\* +* [swaybg] (Optional, wird für das Setzen von Desktophintergrundbildern benötigt) +* [scdoc] (Optional, wird für die Dokumentation (Man Pages) benötigt)\* * git (Optional: Versionsinfo)\* -_\*Werden nur während des Kompilierens benötigt_ +_\*Werden nur für das Kompilieren benötigt_ Führe die folgenden Befehle aus: - meson build - ninja -C build - sudo ninja -C build install + meson setup build/ + ninja -C build/ + sudo ninja -C build/ install + +Schaue in das [Wiki][Development setup] (Englisch) für Informationen, falls du zum Testen oder Entwickeln den neuesten Stand (HEAD) von sway und wlroots kompilieren willst. ## Konfiguration Falls du von i3 migrierst, kannst du deine Konfigurationsdatei nach `~/.config/sway/config` kopieren und die Einstellungen sollten ohne Weiteres funktionieren. Ansonsten kannst du die Beispielkonfiguration, die normalerweise in `/etc/sway/config` liegt, nach `~/.config/sway/config` kopieren. Die Dokumentation zur Konfigurationsdatei findest du in `man 5 sway`. ## Sway starten -Sway kann einfach mit dem Befehl `sway` vom TTY gestartet werden. -Display-Manager werden nicht offiziell unterstützt. Es gibt aber durchaus einige, die mit Sway funktionieren (z.B. gdm). +Sway kann einfach mit dem Befehl `sway` vom TTY oder mithilfe eines Displaymanagers gestartet werden. + +[i3]: https://i3wm.org/ +[Wayland]: http://wayland.freedesktop.org/ +[FAQ]: https://github.com/swaywm/sway/wiki +[IRC channel]: https://web.libera.chat/gamja/?channels=#sway +[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 +[GitHub releases]: https://github.com/swaywm/sway/releases +[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots +[swaybg]: https://github.com/swaywm/swaybg +[scdoc]: https://git.sr.ht/~sircmpwn/scdoc diff --git a/README.dk.md b/README.dk.md index 5ce94cdef..8e4cc3724 100644 --- a/README.dk.md +++ b/README.dk.md @@ -41,7 +41,7 @@ _\*Kompileringsafhængighed_ Kør følgende kommandoer: - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -54,8 +54,7 @@ Hvis du allerede bruger i3 kan du bare kopiere din i3 konfiguration til ## Eksekvering -Kør `sway` fra en TTY. Nogle display managers kan fungere, men Sway yder ikke -support til dem (gdm er kendt for at fungere temmelig godt). +Kør `sway` fra en TTY eller fra en display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ diff --git a/README.es.md b/README.es.md index 1f1657dfa..3ad8e29e3 100644 --- a/README.es.md +++ b/README.es.md @@ -40,7 +40,7 @@ _\*Compile-time dep_ Desde su consola, ejecute las órdenes: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.fr.md b/README.fr.md index 7752fc70f..a751a52cb 100644 --- a/README.fr.md +++ b/README.fr.md @@ -47,7 +47,7 @@ _\* Requis uniquement pour la compilation_ Exécutez ces commandes : - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -57,13 +57,11 @@ Si vous utilisez déjà i3, copiez votre configuration i3 vers `~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la -documentation pour la configuration de sway. +documentation sur la configuration de sway. ## Exécution -Exécutez `sway` à partir d'un TTY. Certains gestionnaires d'affichage peuvent -fonctionner, mais ne sont pas supportés par Sway (gdm est réputé pour assez -bien fonctionner). +Exécutez `sway` à partir d'un TTY ou d'un gestionnaires d'affichage. [Wayland]: http://wayland.freedesktop.org/ [i3]: https://i3wm.org/ diff --git a/README.ge.md b/README.ge.md index bb8b9a34c..de75be837 100644 --- a/README.ge.md +++ b/README.ge.md @@ -35,7 +35,7 @@ _\* Compile-time dep_ გაუშვით ეს ბრძანებები: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.gr.md b/README.gr.md index d697f78e3..629465846 100644 --- a/README.gr.md +++ b/README.gr.md @@ -40,7 +40,7 @@ _\*Compile-time dep_ Τρέξτε αυτά τα commands: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.hi.md b/README.hi.md index eae5e90a5..eb792b790 100644 --- a/README.hi.md +++ b/README.hi.md @@ -44,7 +44,7 @@ _\* Compilation के समय आवश्यक_ ये commands चलाएं: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.hu.md b/README.hu.md index 82ca6785b..b66a24b23 100644 --- a/README.hu.md +++ b/README.hu.md @@ -1,10 +1,10 @@ # sway -A Sway egy [i3]-kompatibilis [Wayland] kompozitor. Olvasd el a [Gyarkan Ismételt Kérdéseket][FAQ]. Csatlakozz az [IRC csatornához][IRC channel] \(`#sway` az `irc.libera.chat`-en). +A Sway egy [i3]-kompatibilis [Wayland]-kompozitor. Olvasd el a [Gyarkan Ismételt Kérdéseket][FAQ]. Csatlakozz az [IRC-csatornához][IRC channel] \(`#sway` az `irc.libera.chat`-en). -## Csomag aláírások +## Csomagaláírások -A kiadott csomagok az [E88F5E48] kulccsal vannak aláírva és [GitHub-on][GitHub releases] publikálva. +A kiadott csomagok az [E88F5E48] kulccsal vannak aláírva, és [GitHubon][GitHub releases] publikálva. ## Telepítés @@ -13,12 +13,12 @@ A kiadott csomagok az [E88F5E48] kulccsal vannak aláírva és [GitHub-on][GitHu A Sway sok disztribúció csomagkezelőjéből elérhető, próbáld meg a "sway" csomagot telepíteni az általad használt eszközzel. -Ha szeretnél csomagot készíteni a saját disztribúciódhoz, ugorj be az IRC +Ha szeretnél csomagot készíteni a saját disztribúciódhoz, ugorj be az IRC- csatornára, vagy küldj levelet a sir@cmpwn.com címre tanácsokért. ### Fordítás forráskódból -Olvasd el [ezt a wiki oldalt][Development setup], ha szeretnéd tesztelési vagy +Olvasd el [ezt a wikioldalt][Development setup], ha szeretnéd tesztelési vagy fejlesztési célokból lefordítani az aktuális (HEAD) állapotát a `sway`-nek és a `wlroots`-nak. @@ -40,13 +40,13 @@ _\*Fordításidejű függőség_ Futtasd ezeket a parancsokat: - meson build + meson setup build ninja -C build sudo ninja -C build install ## Konfiguráció -Ha előzőleg i3-mat használtál, akkor átmásolhatod az i3 beállításaidat a +Ha előzőleg i3-at használtál, akkor átmásolhatod az i3-beállításaidat a `~/.config/sway/config` file-ba és ugyanúgy működni fognak. Egyéb esetben másold le kiindulási alapnak a mintát, ami általában az `etc/sway/config` elérési útvonalon található. @@ -55,7 +55,7 @@ kapcsolatban. ## Futtatás -Futtasd a `sway` parancsot egy TTY felületről. Néhány bejelentkezéskezelő +Futtasd a `sway` parancsot egy TTY-felületről. Néhány bejelentkezéskezelő (display manager) működhet, de alapvetően nem támogatottak a sway által. (A gdm-ről ismeretes, hogy egész jól működik.) diff --git a/README.ir.md b/README.ir.md index a485a4059..f7d00e803 100644 --- a/README.ir.md +++ b/README.ir.md @@ -41,7 +41,7 @@ _\*نیازمندی‌های زمان کامپایل برنامه_ این فرمان‌ها را اجرا کنید: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.it.md b/README.it.md index 82bb57837..30cdc21e0 100644 --- a/README.it.md +++ b/README.it.md @@ -1,7 +1,7 @@ # sway sway è un compositore di [Wayland] compatibile con [i3]. Leggi le [FAQ]. -Unisciti al [canale di IRC] \(#sway su irc.libera.chat). +Unisciti al [canale IRC] \(#sway su irc.libera.chat). ## Firma delle versioni @@ -38,7 +38,7 @@ _\* Dipendenza necessaria per la compilazione_ Esegui questi comandi: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install @@ -52,13 +52,12 @@ configurazione. ## Esecuzione -Lancia `sway` da un TTY. Alcuni gestori d'accesso potrebbero funzionare ma non -sono supportati da sway (gdm funziona abbastanza bene). +Lancia `sway` da un TTY o da un display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [FAQ]: https://github.com/swaywm/sway/wiki -[canale di IRC]: https://web.libera.chat/gamja/?channels=#sway +[canale IRC]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup diff --git a/README.ja.md b/README.ja.md index 4e9a9971d..d6a8202e3 100644 --- a/README.ja.md +++ b/README.ja.md @@ -42,7 +42,7 @@ _\*コンパイル時の依存_ 次のコマンドを実行してください: - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -52,5 +52,4 @@ _\*コンパイル時の依存_ ## 実行 -`sway`をTTYから実行してください。いくつかのディスプレイマネージャは動くかもしれませんが、Swayからサポートされていません(gdmは非常に良く動作することが知られています)。 - +`sway`をTTYまたはディスプレイマネージャから実行してください。 diff --git a/README.ko.md b/README.ko.md index e086c174c..c9f31beff 100644 --- a/README.ko.md +++ b/README.ko.md @@ -39,7 +39,7 @@ _\*컴파일 떄 필요_ 다음 명령을 실행하세요: - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -52,4 +52,4 @@ i3를 이미 사용 중이라면, i3 config을 `~/.config/sway/config`로 복사 ## 실행 -TTY에서 `sway`를 실행하세요. 일부 display manager는 작동하지만, sway로 부터 지원되지 않습니다(gdm은 상당히 잘 작동한다고 알려져 있습니다). +TTY나 display manager에서 `sway`를 실행하세요. diff --git a/README.md b/README.md index 15c7c0999..882db57ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # sway -**[English][en]** - [عربي][ar] - [Česky][cs] - [Deutsch][de] - [Dansk][dk] - [Español][es] - [Français][fr] - [ქართული][ge] - [Ελληνικά][gr] - [हिन्दी][hi] - [Magyar][hu] - [فارسی][ir] - [Italiano][it] - [日本語][ja] - [한국어][ko] - [Nederlands][nl] - [Norsk][no] - [Polski][pl] - [Português][pt] - [Română][ro] - [Русский][ru] - [Svenska][sv] - [Türkçe][tr] - [Українська][uk] - [中文-简体][zh-CN] - [中文-繁體][zh-TW] +**[English][en]** - [عربي][ar] - [Azərbaycanca][az] - [Česky][cs] - [Deutsch][de] - [Dansk][dk] - [Español][es] - [Français][fr] - [ქართული][ge] - [Ελληνικά][gr] - [हिन्दी][hi] - [Magyar][hu] - [فارسی][ir] - [Italiano][it] - [日本語][ja] - [한국어][ko] - [Nederlands][nl] - [Norsk][no] - [Polski][pl] - [Português][pt] - [Română][ro] - [Русский][ru] - [Српски][sr] - [Svenska][sv] - [Türkçe][tr] - [Українська][uk] - [中文-简体][zh-CN] - [中文-繁體][zh-TW] sway is an [i3]-compatible [Wayland] compositor. Read the [FAQ]. Join the [IRC channel] \(#sway on irc.libera.chat). @@ -40,7 +40,7 @@ _\* Compile-time dep_ Run these commands: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install @@ -53,11 +53,11 @@ Run `man 5 sway` for information on the configuration. ## Running -Run `sway` from a TTY. Some display managers may work but are not supported by -sway (gdm is known to work fairly well). +Run `sway` from a TTY or from a display manager. [en]: https://github.com/swaywm/sway#readme [ar]: README.ar.md +[az]: README.az.md [cs]: README.cs.md [de]: README.de.md [dk]: README.dk.md @@ -77,6 +77,7 @@ sway (gdm is known to work fairly well). [pt]: README.pt.md [ro]: README.ro.md [ru]: README.ru.md +[sr]: README.sr.md [sv]: README.sv.md [tr]: README.tr.md [uk]: README.uk.md diff --git a/README.nl.md b/README.nl.md index bf1ea9750..f2f7bb497 100644 --- a/README.nl.md +++ b/README.nl.md @@ -40,7 +40,7 @@ _\* Compileerafhankelijkheden_ Voer deze opdrachten uit: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.no.md b/README.no.md index 35d6e320f..77676062d 100644 --- a/README.no.md +++ b/README.no.md @@ -1,29 +1,25 @@ # Sway -Sway er en [i3]-kompatibel [Wayland] compositor. Les [Ofte stilte spørsmål]. -Delta på [IRC kanalen][IRC kanal] \(#sway på irc.libera.chat). +Sway er en [i3]-kompatibel [Wayland]-compositor. Les [Ofte stilte spørsmål]. +Delta på [IRC-kanalen][IRC-kanal] \(#sway på irc.libera.chat). -## Utgivelses Signaturer +## Signaturer -Utgivelser er signert med [E88F5E48] og publisert [på GitHub][GitHub -releases]. +Utgivelser er signert med [E88F5E48] og publisert [på GitHub][GitHub releases]. ## Installasjon -### Fra system pakker +### Fra systempakker -Sway er tilgjengelig i mange distribusjoner. Prøv å installere "sway" pakken +Sway er tilgjengelig i mange distribusjoner. Prøv å installere pakken "sway" fra din distro sine repoer. -Er du interessert i å pakke Sway for din distribusjon kan du ta turen innom -IRC-kanalen eller send en e-post til sir@cmpwn.com for råd. - ### Kompilering fra kildekode -Se [denne wiki-siden][Oppsetting for utvikling] hvis du vil bygge fra HEAD grenen av sway og -wlroots for testing eller utvikling. +Se [denne wiki-siden][Oppsetting for utvikling] hvis du vil bygge fra HEAD-grenen av +sway og wlroots for testing eller utvikling. -Installasjonsavhengigheter: +Installer avhengigheter: * meson \* * [wlroots] @@ -33,36 +29,37 @@ Installasjonsavhengigheter: * json-c * pango * cairo -* gdk-pixbuf2 (valgfritt: system tray) +* gdk-pixbuf2 (valgfritt: støtte for ekstra bildeformater i system tray) +* [swaybg] (valgfritt: bakgrunnsbilde) * [scdoc] (valgfritt: man pages) \* -* git \* +* git (valgfritt: versjonsinformasjon) \* -_\*Kompileringsavhengigheter_ +_\* Kompileringsavhengigheter_ Kjør følgende kommandoer: - meson build - ninja -C build - sudo ninja -C build install + meson setup build/ + ninja -C build/ + sudo ninja -C build/ install ## Konfigurasjon -Hvis du allerede bruker i3 kan du bare kopiere din i3 konfigurasjon til -`~/.config/sway/config`. Ellers skal du kopiere eksempel konfigurasjonsfilen til -`~/.config/sway/config`. Eksempel filen er normalt plasert i `/etc/sway/config`. Kjør -`man 5 sway` for å få oplysninger om konfigurasjonen. +Hvis du allerede bruker i3 kan du bare kopiere din i3-konfigurasjon til +`~/.config/sway/config`. Ellers skal du kopiere eksempelkonfigurasjonsfilen til +`~/.config/sway/config`. Eksempelfilen er normalt plasert i `/etc/sway/config`. +Kjør `man 5 sway` for å få opplysninger om konfigurasjonen. -## Utførelse +## Kjøring -Kjør `sway` fra en TTY. Noen display managers kan fungere, men Sway har ikke -støtte for dem (gdm er kjent for å fungere ganske bra). +Kjør `sway` fra en TTY eller fra en display manager. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ [Ofte stilte spørsmål]: https://github.com/swaywm/sway/wiki -[IRC kanal]: https://web.libera.chat/gamja/?channels=#sway +[IRC-kanal]: https://web.libera.chat/gamja/?channels=#sway [E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 [GitHub releases]: https://github.com/swaywm/sway/releases [Oppsetting for utvikling]: https://github.com/swaywm/sway/wiki/Development-Setup [wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots +[swaybg]: https://github.com/swaywm/swaybg/ [scdoc]: https://git.sr.ht/~sircmpwn/scdoc diff --git a/README.pl.md b/README.pl.md index 65b3c3a1b..0c12a2b86 100644 --- a/README.pl.md +++ b/README.pl.md @@ -40,7 +40,7 @@ _\*zależności kompilacji_ Wykonaj następujące polecenia: - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -53,5 +53,4 @@ Wykonaj polecenie `man 5 sway` aby uzyskać informacje dotyczące konfiguracji. ## Uruchamianie -Wykonaj polecenie `sway` z poziomu TTY. Niektóre menedżery wyświetlania mogą umożliwiać rozruch z ich -poziomu, ale nie jest to wspierane przez sway (w gdm podobno działa to całkiem nieźle). +Wykonaj polecenie `sway` z poziomu TTY lub menedżera wyświetlania. diff --git a/README.pt.md b/README.pt.md index c1611a31c..4660e0bed 100644 --- a/README.pt.md +++ b/README.pt.md @@ -42,7 +42,7 @@ _\*Dependência de tempo de compilação_ Execute esses comandos: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.ro.md b/README.ro.md index a3559a8bb..1f63b5955 100644 --- a/README.ro.md +++ b/README.ro.md @@ -38,7 +38,7 @@ Dependențe pentru instalare: Rulați aceste comenzi: ``` - meson build + meson setup build ninja -C build sudo ninja -C build install ``` diff --git a/README.ru.md b/README.ru.md index edc0eda78..5eee59efa 100644 --- a/README.ru.md +++ b/README.ru.md @@ -41,7 +41,7 @@ _\*Зависимости для сборки_ Выполните эти команды: - meson build + meson setup build ninja -C build sudo ninja -C build install @@ -54,8 +54,7 @@ _\*Зависимости для сборки_ ## Запуск -Выполните команду `sway` прямо из TTY. Некоторые дисплейные менеджеры могут работать, но они не поддерживаются со стороны -sway (gdm работает довольно неплохо). +Выполните команду `sway` прямо из TTY или дисплейного менеджера. [i3]: https://i3wm.org/ [Wayland]: http://wayland.freedesktop.org/ diff --git a/README.sr.md b/README.sr.md new file mode 100644 index 000000000..bd573566a --- /dev/null +++ b/README.sr.md @@ -0,0 +1,65 @@ +# sway + +sway је [i3]-компатибилан [Wayland] композитор. Прочитајте [FAQ]. Придружите се +[IRC каналу] \(#sway на irc.libera.chat). + +## Потписи Издања + +Издања су потписана са [E88F5E48] и објављена [на GitHub-у][GitHub releases]. + +## Инсталација + +### Из пакета + +Sway је доступан у многим дистрибуцијама. Покушајте да инсталирате "sway" пакет за +вашу. + +### Компајлирање из Извора + +Погледајте [ову вики страницу][Development setup], ако желите да компајлирате HEAD верзију +sway-а и wlroots-а за тестирање или развој. + +Инсталирајте зависности: + +* meson \* +* [wlroots] +* wayland +* wayland-protocols \* +* pcre2 +* json-c +* pango +* cairo +* gdk-pixbuf2 (опционо: додатни формати слика за системску траку) +* [swaybg] (опционо: позадина) +* [scdoc] (опционо: man странице) \* +* git (опционо: информације о верзији) \* + +_\* Потребно само за компајлирање_ + +Покрените следеће команде: + + meson setup build/ + ninja -C build/ + sudo ninja -C build/ install + +## Конфигурација + +Ако већ користите i3, копирајте вашу i3 конфигурацију у `~/.config/sway/config` и +радиће одмах. У супротном, копирајте пример конфигурационе датотеке у +`~/.config/sway/config`. Обично се налази у `/etc/sway/config`. +Покрените `man 5 sway` за информације о конфигурацији. + +## Покретање + +Покрените `sway` из TTY-a или из менаџера приказа. + +[i3]: https://i3wm.org/ +[Wayland]: http://wayland.freedesktop.org/ +[FAQ]: https://github.com/swaywm/sway/wiki +[IRC каналу]: https://web.libera.chat/gamja/?channels=#sway +[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 +[GitHub releases]: https://github.com/swaywm/sway/releases +[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup +[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots +[swaybg]: https://github.com/swaywm/swaybg/ +[scdoc]: https://git.sr.ht/~sircmpwn/scdoc diff --git a/README.sv.md b/README.sv.md index c50ca068c..bff9d4a85 100644 --- a/README.sv.md +++ b/README.sv.md @@ -1,7 +1,5 @@ # sway -[English][en] - [Deutsch][de] - [Dansk][dk] - [Español][es] - [Français][fr] - **[Svenska][sv]** - [Ελληνικά][gr] - [Magyar][hu] - [فارسی][ir] - [Italiano][it] - [日本語][ja] - [한국어][ko] - [Nederlands][nl] - [Polski][pl] - [Português][pt] - [Română][ro] - [Русский][ru] - [Türkçe][tr] - [Українська][uk] - [中文-简体][zh-CN] - [中文-繁體][zh-TW] - sway är en [i3]-kompatibel [Wayland] compositor. Läs våran [FAQ]-sida. Gå med i vår [IRC-kanal] \(#sway på irc.libera.chat). @@ -37,7 +35,7 @@ _\* Krav för kompilering_ Kör dessa kommandon: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.tr.md b/README.tr.md index 40de14748..3769ee755 100644 --- a/README.tr.md +++ b/README.tr.md @@ -38,7 +38,7 @@ _\*Derleme-anı bağımlılıkları_ Şu komutları çalıştırın: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.uk.md b/README.uk.md index 33359cffc..01c43afee 100644 --- a/README.uk.md +++ b/README.uk.md @@ -51,7 +51,7 @@ _\*Лише для компіляції_ Виконайте ці команди: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/README.zh-CN.md b/README.zh-CN.md index a6f4518ae..b733c71ab 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -35,7 +35,7 @@ _\*编译时依赖_ 运行如下命令: - meson build/ + meson setup build/ ninja -C build/ sudo ninja -C build/ install diff --git a/README.zh-TW.md b/README.zh-TW.md index 2de2f63fc..7cf848ea3 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -40,7 +40,7 @@ _\*編譯時相依_ 執行這些指令: - meson build + meson setup build ninja -C build sudo ninja -C build install diff --git a/common/pango.c b/common/pango.c index 288569b30..781d7b312 100644 --- a/common/pango.c +++ b/common/pango.c @@ -53,6 +53,8 @@ size_t escape_markup_text(const char *src, char *dest) { PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc, const char *text, double scale, bool markup) { PangoLayout *layout = pango_cairo_create_layout(cairo); + pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false); + PangoAttrList *attrs; if (markup) { char *buf; @@ -82,28 +84,48 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc, void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height, int *baseline, double scale, bool markup, const char *fmt, ...) { + if (width) { + *width = 0; + } + if (height) { + *height = 0; + } + if (baseline) { + *baseline = 0; + } + va_list args; va_start(args, fmt); char *buf = vformat_str(fmt, args); va_end(args); if (buf == NULL) { + sway_log(SWAY_ERROR, "Failed to format string"); return; } PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup); pango_cairo_update_layout(cairo, layout); + cairo_status_t status = cairo_status(cairo); + if (status != CAIRO_STATUS_SUCCESS) { + sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s", + cairo_status_to_string(status)); + goto out; + } + pango_layout_get_pixel_size(layout, width, height); if (baseline) { *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; } - g_object_unref(layout); +out: + g_object_unref(layout); free(buf); } void get_text_metrics(const PangoFontDescription *description, int *height, int *baseline) { - cairo_t *cairo = cairo_create(NULL); - PangoContext *pango = pango_cairo_create_context(cairo); + PangoFontMap *fontmap = pango_cairo_font_map_get_default(); + PangoContext *pango = pango_font_map_create_context(fontmap); + pango_context_set_round_glyph_positions(pango, false); // When passing NULL as a language, pango uses the current locale. PangoFontMetrics *metrics = pango_context_get_metrics(pango, description, NULL); @@ -112,7 +134,6 @@ void get_text_metrics(const PangoFontDescription *description, int *height, int pango_font_metrics_unref(metrics); g_object_unref(pango); - cairo_destroy(cairo); } void render_text(cairo_t *cairo, const PangoFontDescription *desc, @@ -122,6 +143,7 @@ void render_text(cairo_t *cairo, const PangoFontDescription *desc, char *buf = vformat_str(fmt, args); va_end(args); if (buf == NULL) { + sway_log(SWAY_ERROR, "Failed to format string"); return; } @@ -130,9 +152,18 @@ void render_text(cairo_t *cairo, const PangoFontDescription *desc, cairo_get_font_options(cairo, fo); pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); cairo_font_options_destroy(fo); - pango_cairo_update_layout(cairo, layout); - pango_cairo_show_layout(cairo, layout); - g_object_unref(layout); + pango_cairo_update_layout(cairo, layout); + cairo_status_t status = cairo_status(cairo); + if (status != CAIRO_STATUS_SUCCESS) { + sway_log(SWAY_ERROR, "pango_cairo_update_layout() failed: %s", + cairo_status_to_string(status)); + goto out; + } + + pango_cairo_show_layout(cairo, layout); + +out: + g_object_unref(layout); free(buf); } diff --git a/common/stringop.c b/common/stringop.c index 16d04917e..dcbd16564 100644 --- a/common/stringop.c +++ b/common/stringop.c @@ -360,3 +360,7 @@ char *format_str(const char *fmt, ...) { va_end(args); return str; } + +bool has_prefix(const char *str, const char *prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} diff --git a/common/util.c b/common/util.c index 7c492bcbf..ae1fe4431 100644 --- a/common/util.c +++ b/common/util.c @@ -141,3 +141,9 @@ bool sway_set_cloexec(int fd, bool cloexec) { } return true; } + +uint32_t get_current_time_in_msec(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * 1000 + now.tv_nsec / 1000000; +} diff --git a/config.in b/config.in index a5173165b..d71bc628b 100644 --- a/config.in +++ b/config.in @@ -16,9 +16,7 @@ set $right l # Your preferred terminal emulator set $term foot # Your preferred application launcher -# Note: pass the final command to swaymsg so that the resulting window can be opened -# on the original workspace that the command was run on. -set $menu dmenu_path | wmenu | xargs swaymsg exec -- +set $menu wmenu-run ### Output configuration # @@ -48,14 +46,18 @@ output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill # # Example configuration: # -# input "2:14:SynPS/2_Synaptics_TouchPad" { +# input type:touchpad { # dwt enabled # tap enabled # natural_scroll enabled # middle_emulation enabled # } # -# You can get the names of your inputs by running: swaymsg -t get_inputs +# input type:keyboard { +# xkb_layout "eu" +# } +# +# You can also configure each device individually. # Read `man 5 sway-input` for more information about this section. ### Key bindings @@ -195,6 +197,28 @@ mode "resize" { bindsym Escape mode "default" } bindsym $mod+r mode "resize" +# +# Utilities: +# + # Special keys to adjust volume via PulseAudio + bindsym --locked XF86AudioMute exec pactl set-sink-mute \@DEFAULT_SINK@ toggle + bindsym --locked XF86AudioLowerVolume exec pactl set-sink-volume \@DEFAULT_SINK@ -5% + bindsym --locked XF86AudioRaiseVolume exec pactl set-sink-volume \@DEFAULT_SINK@ +5% + bindsym --locked XF86AudioMicMute exec pactl set-source-mute \@DEFAULT_SOURCE@ toggle + + # Special keys to control media via playerctl + bindsym --locked XF86AudioPlay exec playerctl play-pause + bindsym --locked XF86AudioPause exec playerctl play-pause + bindsym --locked XF86AudioPrev exec playerctl previous + bindsym --locked XF86AudioNext exec playerctl next + bindsym --locked XF86AudioStop exec playerctl stop + + # Special keys to adjust brightness via brightnessctl + bindsym --locked XF86MonBrightnessDown exec brightnessctl set 5%- + bindsym --locked XF86MonBrightnessUp exec brightnessctl set 5%+ + + # Special key to take a screenshot with grim + bindsym Print exec grim # # Status Bar: diff --git a/include/stringop.h b/include/stringop.h index 19a50f237..ffc355cf0 100644 --- a/include/stringop.h +++ b/include/stringop.h @@ -40,4 +40,6 @@ bool expand_path(char **path); char *vformat_str(const char *fmt, va_list args) _SWAY_ATTRIB_PRINTF(1, 0); char *format_str(const char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2); +bool has_prefix(const char *str, const char *prefix); + #endif diff --git a/include/sway/commands.h b/include/sway/commands.h index d758d9642..8ccab5ef8 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -104,6 +104,7 @@ struct sway_container *container_find_resize_parent(struct sway_container *con, sway_cmd cmd_exec_validate; sway_cmd cmd_exec_process; +sway_cmd cmd_allow_tearing; sway_cmd cmd_assign; sway_cmd cmd_bar; sway_cmd cmd_bindcode; @@ -250,6 +251,7 @@ sway_cmd input_cmd_seat; sway_cmd input_cmd_accel_profile; sway_cmd input_cmd_calibration_matrix; sway_cmd input_cmd_click_method; +sway_cmd input_cmd_clickfinger_button_map; sway_cmd input_cmd_drag; sway_cmd input_cmd_drag_lock; sway_cmd input_cmd_dwt; @@ -283,10 +285,13 @@ sway_cmd input_cmd_xkb_switch_layout; sway_cmd input_cmd_xkb_variant; sway_cmd output_cmd_adaptive_sync; +sway_cmd output_cmd_allow_tearing; sway_cmd output_cmd_background; +sway_cmd output_cmd_color_profile; sway_cmd output_cmd_disable; sway_cmd output_cmd_dpms; sway_cmd output_cmd_enable; +sway_cmd output_cmd_hdr; sway_cmd output_cmd_max_render_time; sway_cmd output_cmd_mode; sway_cmd output_cmd_modeline; diff --git a/include/sway/config.h b/include/sway/config.h index 453c258eb..5a8608eb2 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include "../include/config.h" @@ -148,6 +149,7 @@ struct input_config { int accel_profile; struct calibration_matrix calibration_matrix; int click_method; + int clickfinger_button_map; int drag; int drag_lock; int dwt; @@ -259,11 +261,18 @@ enum scale_filter_mode { }; enum render_bit_depth { - RENDER_BIT_DEPTH_DEFAULT, // the default is currently 8 + RENDER_BIT_DEPTH_DEFAULT, // the default is currently 8 for SDR, 10 for HDR + RENDER_BIT_DEPTH_6, RENDER_BIT_DEPTH_8, RENDER_BIT_DEPTH_10, }; +enum color_profile { + COLOR_PROFILE_DEFAULT, // default is Transform with NULL color_transform + COLOR_PROFILE_TRANSFORM, // use color_transform from output_config + COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES, // create transform from wlr_output +}; + /** * Size and position configuration for a particular output. * @@ -285,6 +294,10 @@ struct output_config { int max_render_time; // In milliseconds int adaptive_sync; enum render_bit_depth render_bit_depth; + enum color_profile color_profile; + struct wlr_color_transform *color_transform; + int allow_tearing; + int hdr; char *background; char *background_option; @@ -689,22 +702,28 @@ const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filt struct output_config *new_output_config(const char *name); -void merge_output_config(struct output_config *dst, struct output_config *src); +bool apply_output_configs(struct output_config **ocs, size_t ocs_len, + bool test_only, bool degrade_to_off); -bool apply_output_config(struct output_config *oc, struct sway_output *output); +void apply_stored_output_configs(void); -bool test_output_config(struct output_config *oc, struct sway_output *output); - -struct output_config *store_output_config(struct output_config *oc); +/** + * store_output_config stores a new output config. An output may be matched by + * three different config types, in order of precedence: Identifier, name and + * wildcard. When storing a config type of lower precedence, assume that the + * user wants the config to take immediate effect by superseding (clearing) the + * same values from higher presedence configuration. + */ +void store_output_config(struct output_config *oc); struct output_config *find_output_config(struct sway_output *output); -void apply_output_config_to_outputs(struct output_config *oc); - -void reset_outputs(void); - void free_output_config(struct output_config *oc); +void request_modeset(void); +void force_modeset(void); +bool modeset_is_pending(void); + bool spawn_swaybg(void); int workspace_output_cmp_workspace(const void *a, const void *b); diff --git a/include/sway/criteria.h b/include/sway/criteria.h index 8da345ea6..fad278e02 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -7,6 +7,10 @@ #include "list.h" #include "tree/view.h" +#if WLR_HAS_XWAYLAND +#include "sway/xwayland.h" +#endif + enum criteria_type { CT_COMMAND = 1 << 0, CT_ASSIGN_OUTPUT = 1 << 1, @@ -36,7 +40,7 @@ struct criteria { struct pattern *app_id; struct pattern *con_mark; uint32_t con_id; // internal ID -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct pattern *class; uint32_t id; // X11 window ID struct pattern *instance; @@ -49,6 +53,10 @@ struct criteria { char urgent; // 'l' for latest or 'o' for oldest struct pattern *workspace; pid_t pid; + struct pattern *sandbox_engine; + struct pattern *sandbox_app_id; + struct pattern *sandbox_instance_id; + struct pattern *tag; }; bool criteria_is_empty(struct criteria *criteria); diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h index 84cc666d0..447ac8708 100644 --- a/include/sway/desktop/idle_inhibit_v1.h +++ b/include/sway/desktop/idle_inhibit_v1.h @@ -13,6 +13,7 @@ enum sway_idle_inhibit_mode { struct sway_idle_inhibit_manager_v1 { struct wlr_idle_inhibit_manager_v1 *wlr_manager; struct wl_listener new_idle_inhibitor_v1; + struct wl_listener manager_destroy; struct wl_list inhibitors; }; diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h index 45c751994..5113844d8 100644 --- a/include/sway/input/input-manager.h +++ b/include/sway/input/input-manager.h @@ -5,10 +5,11 @@ #include #include #include -#include "sway/server.h" #include "sway/config.h" #include "list.h" +struct sway_server; + struct sway_input_device { char *identifier; struct wlr_input_device *wlr_device; @@ -38,6 +39,8 @@ struct sway_input_manager { struct sway_input_manager *input_manager_create(struct sway_server *server); +void input_manager_finish(struct sway_input_manager *input); + bool input_manager_has_focus(struct sway_node *node); void input_manager_set_focus(struct sway_node *node); diff --git a/include/sway/input/text_input.h b/include/sway/input/text_input.h index 1993f928f..1818749ad 100644 --- a/include/sway/input/text_input.h +++ b/include/sway/input/text_input.h @@ -25,8 +25,10 @@ struct sway_input_method_relay { struct wlr_input_method_v2 *input_method; // doesn't have to be present struct wl_listener text_input_new; + struct wl_listener text_input_manager_destroy; struct wl_listener input_method_new; + struct wl_listener input_method_manager_destroy; struct wl_listener input_method_commit; struct wl_listener input_method_new_popup_surface; struct wl_listener input_method_grab_keyboard; diff --git a/include/sway/input/text_input_popup.h b/include/sway/input/text_input_popup.h index e5f6ab8b5..7e838ed2a 100644 --- a/include/sway/input/text_input_popup.h +++ b/include/sway/input/text_input_popup.h @@ -9,11 +9,14 @@ struct sway_input_popup { struct wlr_scene_tree *scene_tree; struct sway_popup_desc desc; struct wlr_input_popup_surface_v2 *popup_surface; + struct wlr_output *fixed_output; struct wl_list link; struct wl_listener popup_destroy; struct wl_listener popup_surface_commit; + struct wl_listener popup_surface_map; + struct wl_listener popup_surface_unmap; struct wl_listener focused_surface_unmap; }; diff --git a/include/sway/layers.h b/include/sway/layers.h index fd6384e0e..e257da0bd 100644 --- a/include/sway/layers.h +++ b/include/sway/layers.h @@ -9,7 +9,6 @@ struct sway_layer_surface { struct wl_listener map; struct wl_listener unmap; struct wl_listener surface_commit; - struct wl_listener output_destroy; struct wl_listener node_destroy; struct wl_listener new_popup; @@ -19,6 +18,8 @@ struct sway_layer_surface { struct sway_popup_desc desc; struct sway_output *output; + struct wl_list link; // sway_output.layer_surfaces + struct wlr_scene_layer_surface_v1 *scene; struct wlr_scene_tree *tree; struct wlr_layer_surface_v1 *layer_surface; @@ -32,6 +33,7 @@ struct sway_layer_popup { struct wl_listener destroy; struct wl_listener new_popup; struct wl_listener commit; + struct wl_listener reposition; }; struct sway_output; @@ -41,4 +43,6 @@ struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface( void arrange_layers(struct sway_output *output); +void destroy_layers(struct sway_output *output); + #endif diff --git a/include/sway/lock.h b/include/sway/lock.h new file mode 100644 index 000000000..5be0f9695 --- /dev/null +++ b/include/sway/lock.h @@ -0,0 +1,6 @@ +#ifndef _SWAY_LOCK_H +#define _SWAY_LOCK_H + +void arrange_locks(void); + +#endif diff --git a/include/sway/output.h b/include/sway/output.h index d546d4884..ae2e50d36 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -52,25 +52,26 @@ struct sway_output { bool enabled; list_t *workspaces; + struct wl_list layer_surfaces; // sway_layer_surface.link struct sway_output_state current; struct wl_listener layout_destroy; struct wl_listener destroy; - struct wl_listener commit; struct wl_listener present; struct wl_listener frame; struct wl_listener request_state; - struct { - struct wl_signal disable; - } events; + struct wlr_color_transform *color_transform; + struct wlr_ext_workspace_group_handle_v1 *ext_workspace_group; struct timespec last_presentation; uint32_t refresh_nsec; int max_render_time; // In milliseconds struct wl_event_source *repaint_timer; - bool gamma_lut_changed; + + bool allow_tearing; + bool hdr; }; struct sway_output_non_desktop { @@ -90,6 +91,9 @@ struct sway_output *output_from_wlr_output(struct wlr_output *output); struct sway_output *output_get_in_direction(struct sway_output *reference, enum wlr_direction direction); +void output_configure_scene(struct sway_output *output, + struct wlr_scene_node *node, float opacity); + void output_add_workspace(struct sway_output *output, struct sway_workspace *workspace); @@ -128,15 +132,13 @@ struct sway_container *output_find_container(struct sway_output *output, void output_get_box(struct sway_output *output, struct wlr_box *box); +bool output_supports_hdr(struct wlr_output *output, const char **unsupported_reason_ptr); + enum sway_container_layout output_get_default_layout( struct sway_output *output); enum wlr_direction opposite_direction(enum wlr_direction d); -void handle_output_layout_change(struct wl_listener *listener, void *data); - -void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data); - void handle_output_manager_apply(struct wl_listener *listener, void *data); void handle_output_manager_test(struct wl_listener *listener, void *data); @@ -146,4 +148,6 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output); +void update_output_manager_config(struct sway_server *server); + #endif diff --git a/include/sway/server.h b/include/sway/server.h index c71851f68..8c8114882 100644 --- a/include/sway/server.h +++ b/include/sway/server.h @@ -5,7 +5,7 @@ #include "config.h" #include "list.h" #include "sway/desktop/idle_inhibit_v1.h" -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND #include "sway/xwayland.h" #endif @@ -27,7 +27,7 @@ struct sway_session_lock { struct sway_server { struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; - const char *socket; + char *socket; struct wlr_backend *backend; struct wlr_session *session; @@ -45,8 +45,8 @@ struct sway_server { struct sway_input_manager *input; struct wl_listener new_output; - struct wl_listener output_layout_change; struct wl_listener renderer_lost; + struct wl_event_source *recreating_renderer; struct wlr_idle_notifier_v1 *idle_notifier_v1; struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1; @@ -59,7 +59,7 @@ struct sway_server { struct wlr_tablet_manager_v2 *tablet_v2; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct sway_xwayland xwayland; struct wl_listener xwayland_surface; struct wl_listener xwayland_ready; @@ -81,6 +81,8 @@ struct sway_server { struct wlr_pointer_constraints_v1 *pointer_constraints; struct wl_listener pointer_constraint; + struct wlr_xdg_output_manager_v1 *xdg_output_manager_v1; + struct wlr_output_manager_v1 *output_manager_v1; struct wl_listener output_manager_apply; struct wl_listener output_manager_test; @@ -103,17 +105,31 @@ struct sway_server { struct wlr_ext_foreign_toplevel_list_v1 *foreign_toplevel_list; struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; struct wlr_content_type_manager_v1 *content_type_manager_v1; - struct wlr_data_control_manager_v1 *data_control_manager_v1; + struct wlr_data_control_manager_v1 *wlr_data_control_manager_v1; + struct wlr_ext_data_control_manager_v1 *ext_data_control_manager_v1; struct wlr_screencopy_manager_v1 *screencopy_manager_v1; + struct wlr_ext_image_copy_capture_manager_v1 *ext_image_copy_capture_manager_v1; struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1; struct wlr_security_context_manager_v1 *security_context_manager_v1; + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *ext_foreign_toplevel_image_capture_source_manager_v1; + struct wl_listener new_foreign_toplevel_capture_request; + struct wlr_xdg_activation_v1 *xdg_activation_v1; struct wl_listener xdg_activation_v1_request_activate; struct wl_listener xdg_activation_v1_new_token; + struct wl_listener xdg_toplevel_tag_manager_v1_set_tag; + struct wl_listener request_set_cursor_shape; + struct wlr_tearing_control_manager_v1 *tearing_control_v1; + struct wl_listener tearing_control_new_object; + struct wl_list tearing_controllers; // sway_tearing_controller::link + + struct wlr_ext_workspace_manager_v1 *workspace_manager_v1; + struct wl_listener workspace_manager_v1_commit; + struct wl_list pending_launcher_ctxs; // launcher_ctx::link // The timeout for transactions, after which a transaction is applied @@ -133,6 +149,8 @@ struct sway_server { // Stores the nodes that have been marked as "dirty" and will be put into // the pending transaction. list_t *dirty_nodes; + + struct wl_event_source *delayed_modeset; }; extern struct sway_server server; @@ -141,31 +159,30 @@ struct sway_debug { bool noatomic; // Ignore atomic layout updates bool txn_timings; // Log verbose messages about transactions bool txn_wait; // Always wait for the timeout before applying - bool legacy_wl_drm; // Enable the legacy wl_drm interface }; extern struct sway_debug debug; -extern bool allow_unsupported_gpu; +extern bool unsupported_gpu_detected; + +void sway_terminate(int exit_code); bool server_init(struct sway_server *server); void server_fini(struct sway_server *server); bool server_start(struct sway_server *server); void server_run(struct sway_server *server); -void restore_nofile_limit(void); - void handle_new_output(struct wl_listener *listener, void *data); void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); void handle_layer_shell_surface(struct wl_listener *listener, void *data); -void sway_session_lock_init(void); +bool sway_session_lock_init(void); void sway_session_lock_add_output(struct sway_session_lock *lock, struct sway_output *output); bool sway_session_lock_has_surface(struct sway_session_lock *lock, struct wlr_surface *surface); void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND void handle_xwayland_surface(struct wl_listener *listener, void *data); #endif void handle_server_decoration(struct wl_listener *listener, void *data); @@ -178,4 +195,6 @@ void xdg_activation_v1_handle_new_token(struct wl_listener *listener, void set_rr_scheduling(void); +void handle_new_tearing_hint(struct wl_listener *listener, void *data); + #endif diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index 93f6bfbb1..d8780544d 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -91,10 +91,6 @@ struct sway_container { } border; struct wlr_scene_tree *content_tree; - struct wlr_scene_buffer *output_handler; - - struct wl_listener output_enter; - struct wl_listener output_leave; struct sway_container_state current; struct sway_container_state pending; @@ -103,6 +99,8 @@ struct sway_container { char *formatted_title; // The title displayed in the title bar int title_width; + char *title_format; + enum sway_container_layout prev_split_layout; // Whether stickiness has been enabled on this container. Use @@ -175,8 +173,6 @@ struct sway_container *container_obstructing_fullscreen_container(struct sway_co bool container_has_ancestor(struct sway_container *container, struct sway_container *ancestor); -void container_update_textures_recursive(struct sway_container *con); - void container_reap_empty(struct sway_container *con); struct sway_container *container_flatten(struct sway_container *container); @@ -185,6 +181,8 @@ void container_update_title_bar(struct sway_container *container); void container_update_marks(struct sway_container *container); +size_t parse_title_format(struct sway_container *container, char *buffer); + size_t container_build_representation(enum sway_container_layout layout, list_t *children, char *buffer); diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h index 15df0f551..10d9ea981 100644 --- a/include/sway/tree/root.h +++ b/include/sway/tree/root.h @@ -2,12 +2,12 @@ #define _SWAY_ROOT_H #include #include +#include #include #include #include #include "sway/tree/container.h" #include "sway/tree/node.h" -#include "config.h" #include "list.h" extern struct sway_root *root; @@ -16,8 +16,6 @@ struct sway_root { struct sway_node node; struct wlr_output_layout *output_layout; - struct wl_listener output_layout_change; - // scene node layout: // - root // - staging @@ -47,7 +45,7 @@ struct sway_root { struct wlr_scene_tree *shell_top; struct wlr_scene_tree *fullscreen; struct wlr_scene_tree *fullscreen_global; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_scene_tree *unmanaged; #endif struct wlr_scene_tree *shell_overlay; diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 7faacdcc2..26148a92a 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -1,10 +1,12 @@ #ifndef _SWAY_VIEW_H #define _SWAY_VIEW_H #include +#include #include #include +#include #include "sway/config.h" -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND #include #endif #include "sway/input/input-manager.h" @@ -15,7 +17,7 @@ struct sway_xdg_decoration; enum sway_view_type { SWAY_VIEW_XDG_SHELL, -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND SWAY_VIEW_XWAYLAND, #endif }; @@ -23,16 +25,23 @@ enum sway_view_type { enum sway_view_prop { VIEW_PROP_TITLE, VIEW_PROP_APP_ID, + VIEW_PROP_TAG, VIEW_PROP_CLASS, VIEW_PROP_INSTANCE, VIEW_PROP_WINDOW_TYPE, VIEW_PROP_WINDOW_ROLE, -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND VIEW_PROP_X11_WINDOW_ID, VIEW_PROP_X11_PARENT_ID, #endif }; +enum sway_view_tearing_mode { + TEARING_OVERRIDE_FALSE, + TEARING_OVERRIDE_TRUE, + TEARING_WINDOW_HINT, +}; + struct sway_view_impl { void (*get_constraints)(struct sway_view *view, double *min_width, double *max_width, double *min_height, double *max_height); @@ -60,6 +69,12 @@ struct sway_view { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; struct wlr_scene_tree *saved_surface_tree; + struct wlr_scene_buffer *output_handler; + + struct wl_listener outputs_update; + + struct wlr_scene *image_capture_scene; + struct wlr_ext_image_capture_source_v1 *image_capture_source; struct sway_container *container; // NULL if unmapped and transactions finished struct wlr_surface *surface; // NULL for unmapped views @@ -72,8 +87,6 @@ struct sway_view { // Used when changing a view from tiled to floating. int natural_width, natural_height; - char *title_format; - bool using_csd; struct timespec urgent; @@ -98,7 +111,7 @@ struct sway_view { union { struct wlr_xdg_toplevel *wlr_xdg_toplevel; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *wlr_xwayland_surface; #endif }; @@ -110,11 +123,17 @@ struct sway_view { int max_render_time; // In milliseconds enum seat_config_shortcuts_inhibit shortcuts_inhibit; + + enum sway_view_tearing_mode tearing_mode; + enum wp_tearing_control_v1_presentation_hint tearing_hint; }; struct sway_xdg_shell_view { struct sway_view view; + struct wlr_scene_tree *image_capture_tree; + char *tag; + struct wl_listener commit; struct wl_listener request_move; struct wl_listener request_resize; @@ -127,12 +146,14 @@ struct sway_xdg_shell_view { struct wl_listener unmap; struct wl_listener destroy; }; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct sway_xwayland_view { struct sway_view view; struct wlr_scene_tree *surface_tree; + struct wlr_scene_surface *image_capture_scene_surface; + struct wl_listener commit; struct wl_listener request_move; struct wl_listener request_resize; @@ -183,10 +204,12 @@ struct sway_popup_desc { struct sway_xdg_popup { struct sway_view *view; + struct wlr_xdg_popup *wlr_xdg_popup; struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *xdg_surface_tree; - struct wlr_xdg_popup *wlr_xdg_popup; + + struct wlr_scene_tree *image_capture_tree; struct sway_popup_desc desc; @@ -212,6 +235,14 @@ const char *view_get_window_role(struct sway_view *view); uint32_t view_get_window_type(struct sway_view *view); +const char *view_get_sandbox_engine(struct sway_view *view); + +const char *view_get_sandbox_app_id(struct sway_view *view); + +const char *view_get_sandbox_instance_id(struct sway_view *view); + +const char *view_get_tag(struct sway_view *view); + const char *view_get_shell(struct sway_view *view); void view_get_constraints(struct sway_view *view, double *min_width, @@ -293,7 +324,7 @@ void view_center_and_clip_surface(struct sway_view *view); struct sway_view *view_from_wlr_xdg_surface( struct wlr_xdg_surface *xdg_surface); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct sway_view *view_from_wlr_xwayland_surface( struct wlr_xwayland_surface *xsurface); #endif @@ -334,4 +365,8 @@ void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx); void view_send_frame_done(struct sway_view *view); +bool view_can_tear(struct sway_view *view); + +void xdg_toplevel_tag_manager_v1_handle_set_tag(struct wl_listener *listener, void *data); + #endif diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index 58bde20cb..50bf02cd1 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -3,6 +3,7 @@ #include #include +#include #include "sway/config.h" #include "sway/tree/container.h" #include "sway/tree/node.h" @@ -51,6 +52,7 @@ struct sway_workspace { bool urgent; struct sway_workspace_state current; + struct wlr_ext_workspace_handle_v1 *ext_workspace; // Always set. }; struct workspace_config *workspace_find_config(const char *ws_name); @@ -96,7 +98,7 @@ void workspace_output_add_priority(struct sway_workspace *workspace, struct sway_output *output); struct sway_output *workspace_output_get_highest_available( - struct sway_workspace *ws, struct sway_output *exclude); + struct sway_workspace *ws); void workspace_detect_urgent(struct sway_workspace *workspace); @@ -157,4 +159,11 @@ size_t workspace_num_sticky_containers(struct sway_workspace *ws); */ void workspace_squash(struct sway_workspace *workspace); +void workspace_move_to_output(struct sway_workspace *workspace, + struct sway_output *output); + +bool sway_ext_workspace_init(void); +void sway_ext_workspace_output_enable(struct sway_output *output); +void sway_ext_workspace_output_disable(struct sway_output *output); + #endif diff --git a/include/swaybar/config.h b/include/swaybar/config.h index 361acd991..ad58b3c3c 100644 --- a/include/swaybar/config.h +++ b/include/swaybar/config.h @@ -14,6 +14,11 @@ struct box_colors { uint32_t text; }; +struct box_size { + uint32_t width; + uint32_t height; +}; + struct config_output { struct wl_list link; // swaybar_config::outputs char *name; diff --git a/include/util.h b/include/util.h index f887d4895..92f5916cf 100644 --- a/include/util.h +++ b/include/util.h @@ -61,4 +61,6 @@ const char *sway_wl_output_subpixel_to_string(enum wl_output_subpixel subpixel); bool sway_set_cloexec(int fd, bool cloexec); +uint32_t get_current_time_in_msec(void); + #endif diff --git a/meson.build b/meson.build index 1043e4ba9..17d65c334 100644 --- a/meson.build +++ b/meson.build @@ -1,13 +1,14 @@ project( 'sway', 'c', - version: '1.10-dev', + version: '1.13-dev', license: 'MIT', - meson_version: '>=0.60.0', + meson_version: '>=1.3', default_options: [ 'c_std=c11', 'warning_level=2', 'werror=true', + 'wrap_mode=nodownload', ], ) @@ -38,14 +39,14 @@ if is_freebsd endif # Execute the wlroots subproject, if any -wlroots_version = ['>=0.18.0', '<0.19.0'] +wlroots_version = ['>=0.21.0', '<0.22.0'] subproject( 'wlroots', default_options: ['examples=false'], required: false, version: wlroots_version, ) -wlroots = dependency('wlroots', version: wlroots_version) +wlroots = dependency('wlroots-0.21', version: wlroots_version, fallback: 'wlroots') wlroots_features = { 'xwayland': false, 'libinput_backend': false, @@ -57,10 +58,6 @@ foreach name, _ : wlroots_features wlroots_features += { name: have } endforeach -if get_option('xwayland').enabled() and not wlroots_features['xwayland'] - error('Cannot enable Xwayland in sway: wlroots has been built without Xwayland support') -endif - null_dep = dependency('', required: false) jsonc = dependency('json-c', version: '>=0.13') @@ -68,7 +65,7 @@ pcre2 = dependency('libpcre2-8') wayland_server = dependency('wayland-server', version: '>=1.21.0') wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') -wayland_protos = dependency('wayland-protocols', version: '>=1.24') +wayland_protos = dependency('wayland-protocols', version: '>=1.41', default_options: ['tests=false']) xkbcommon = dependency('xkbcommon', version: '>=1.5.0') cairo = dependency('cairo') pango = dependency('pango') @@ -76,16 +73,14 @@ pangocairo = dependency('pangocairo') gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf')) pixman = dependency('pixman-1') libevdev = dependency('libevdev') -libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.21.0') : null_dep -xcb = dependency('xcb', required: get_option('xwayland')) +libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.26.0') : null_dep +xcb = wlroots_features['xwayland'] ? dependency('xcb') : null_dep drm = dependency('libdrm') libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep math = cc.find_library('m') rt = cc.find_library('rt') -xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland')) -threads = dependency('threads') # for pthread_setschedparam - -have_xwayland = xcb.found() and xcb_icccm.found() and wlroots_features['xwayland'] +xcb_icccm = wlroots_features['xwayland'] ? dependency('xcb-icccm') : null_dep +threads = dependency('threads') # for pthread_setschedparam and pthread_atfork if get_option('sd-bus-provider') == 'auto' if not get_option('tray').disabled() @@ -110,17 +105,14 @@ have_tray = (not get_option('tray').disabled()) and tray_deps_found conf_data = configuration_data() -conf_data.set10('HAVE_XWAYLAND', have_xwayland) conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd') conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind') conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu') conf_data.set10('HAVE_TRAY', have_tray) -conf_data.set10('HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', cc.has_header_symbol( - 'libinput.h', - 'LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', - dependencies: libinput, -)) +foreach sym : ['LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', 'LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY', 'LIBINPUT_SWITCH_KEYPAD_SLIDE'] + conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: libinput)) +endforeach scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() @@ -167,8 +159,8 @@ add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir version = '"@0@"'.format(meson.project_version()) git = find_program('git', native: true, required: false) if git.found() - git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false) - git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) + git_commit = run_command([git, '--git-dir=.git', 'rev-parse', '--short', 'HEAD'], check: false) + git_branch = run_command([git, '--git-dir=.git', 'rev-parse', '--abbrev-ref', 'HEAD'], check: false) if git_commit.returncode() == 0 and git_branch.returncode() == 0 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format( meson.project_version(), @@ -179,31 +171,10 @@ if git.found() endif add_project_arguments('-DSWAY_VERSION=@0@'.format(version), language: 'c') -# Compute the relative path used by compiler invocations. -source_root = meson.current_source_dir().split('/') -build_root = meson.global_build_root().split('/') -relative_dir_parts = [] -i = 0 -in_prefix = true -foreach p : build_root - if i >= source_root.length() or not in_prefix or p != source_root[i] - in_prefix = false - relative_dir_parts += '..' - endif - i += 1 -endforeach -i = 0 -in_prefix = true -foreach p : source_root - if i >= build_root.length() or not in_prefix or build_root[i] != p - in_prefix = false - relative_dir_parts += p - endif - i += 1 -endforeach -relative_dir = join_paths(relative_dir_parts) + '/' +fs = import('fs') # Strip relative path prefixes from the code if possible, otherwise hide them. +relative_dir = fs.relative_to(meson.current_source_dir(), meson.global_build_root()) + '/' if cc.has_argument('-fmacro-prefix-map=/prefix/to/hide=') add_project_arguments( '-fmacro-prefix-map=@0@='.format(relative_dir), @@ -271,7 +242,6 @@ endif subdir('completions') summary({ - 'xwayland': have_xwayland, 'gdk-pixbuf': gdk_pixbuf.found(), 'tray': have_tray, 'man-pages': scdoc.found(), diff --git a/meson_options.txt b/meson_options.txt index 8d0d6509c..506ecc9a6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,7 +4,6 @@ option('bash-completions', type: 'boolean', value: true, description: 'Install b option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') option('swaybar', type: 'boolean', value: true, description: 'Enable support for swaybar') option('swaynag', type: 'boolean', value: true, description: 'Enable support for swaynag') -option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') option('tray', type: 'feature', value: 'auto', description: 'Enable support for swaybar tray') option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybar tray') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') diff --git a/protocols/idle.xml b/protocols/idle.xml deleted file mode 100644 index 92d9989c7..000000000 --- a/protocols/idle.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - . - ]]> - - - This interface allows to monitor user idle time on a given seat. The interface - allows to register timers which trigger after no user activity was registered - on the seat for a given interval. It notifies when user activity resumes. - - This is useful for applications wanting to perform actions when the user is not - interacting with the system, e.g. chat applications setting the user as away, power - management features to dim screen, etc.. - - - - - - - - - - - - - - - - - - - - - - diff --git a/protocols/meson.build b/protocols/meson.build index 81edb5841..a1cb35344 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -7,15 +7,11 @@ wayland_scanner = find_program( ) protocols = [ + wl_protocol_dir / 'stable/tablet/tablet-v2.xml', wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', - wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', - wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', - wl_protocol_dir / 'unstable/tablet/tablet-unstable-v2.xml', - wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml', - wl_protocol_dir / 'staging/content-type/content-type-v1.xml', wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', + wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', - 'idle.xml', 'wlr-output-power-management-unstable-v1.xml', ] diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml index d62fd51e9..e9f27e4fd 100644 --- a/protocols/wlr-layer-shell-unstable-v1.xml +++ b/protocols/wlr-layer-shell-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and @@ -100,7 +100,7 @@ - + An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like @@ -367,6 +367,7 @@ + @@ -386,5 +387,21 @@ + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + diff --git a/protocols/wlr-output-power-management-unstable-v1.xml b/protocols/wlr-output-power-management-unstable-v1.xml index a97783991..20dbb7760 100644 --- a/protocols/wlr-output-power-management-unstable-v1.xml +++ b/protocols/wlr-output-power-management-unstable-v1.xml @@ -50,7 +50,7 @@ - Create a output power management mode control that can be used to + Create an output power management mode control that can be used to adjust the power management mode for a given output. @@ -79,7 +79,7 @@ - + diff --git a/release.sh b/release.sh index 62baf4153..6ad8f2c5e 100755 --- a/release.sh +++ b/release.sh @@ -1,7 +1,7 @@ #!/bin/sh -eu prev=$(git describe --tags --abbrev=0) -next=$(meson rewrite kwargs info project / 2>&1 >/dev/null | jq -r '.kwargs["project#/"].version') +next=$(meson rewrite kwargs info project / | jq -r '.kwargs["project#/"].version') case "$next" in *-dev) @@ -28,4 +28,5 @@ archive=$prefix.tar.gz git archive --prefix="$prefix/" -o "$archive" "$next" gpg --output "$archive".sig --detach-sig "$archive" +git push --follow-tags gh release create "sway $next" -t "$next" -n "" -d "$archive" "$archive.sig" diff --git a/sway.desktop b/sway.desktop index 420db5aa1..cc7fcee73 100644 --- a/sway.desktop +++ b/sway.desktop @@ -3,3 +3,4 @@ Name=Sway Comment=An i3-compatible Wayland compositor Exec=sway Type=Application +DesktopNames=sway;wlroots diff --git a/sway/commands.c b/sway/commands.c index 6f455d95c..49959f5d0 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -113,6 +113,7 @@ static const struct cmd_handler config_handlers[] = { /* Runtime-only commands. Keep alphabetized */ static const struct cmd_handler command_handlers[] = { + { "allow_tearing", cmd_allow_tearing }, { "border", cmd_border }, { "create_output", cmd_create_output }, { "exit", cmd_exit }, diff --git a/sway/commands/allow_tearing.c b/sway/commands/allow_tearing.c new file mode 100644 index 000000000..ee5941381 --- /dev/null +++ b/sway/commands/allow_tearing.c @@ -0,0 +1,24 @@ +#include +#include "sway/config.h" +#include "sway/tree/view.h" +#include "util.h" + +struct cmd_results *cmd_allow_tearing(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "allow_tearing", EXPECTED_AT_LEAST, 1))) { + return error; + } + + struct sway_container *container = config->handler_context.container; + if (!container || !container->view) { + return cmd_results_new(CMD_INVALID, "Tearing can only be allowed on views"); + } + + bool wants_tearing = parse_boolean(argv[0], true); + + struct sway_view *view = container->view; + view->tearing_mode = wants_tearing ? TEARING_OVERRIDE_TRUE : + TEARING_OVERRIDE_FALSE; + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/commands/assign.c b/sway/commands/assign.c index bf95cf002..5bcbb1644 100644 --- a/sway/commands/assign.c +++ b/sway/commands/assign.c @@ -23,7 +23,7 @@ struct cmd_results *cmd_assign(int argc, char **argv) { --argc; ++argv; - if (strncmp(*argv, "→", strlen("→")) == 0) { + if (has_prefix(*argv, "→")) { if (argc < 2) { free(criteria); return cmd_results_new(CMD_INVALID, "Missing workspace"); diff --git a/sway/commands/bar/colors.c b/sway/commands/bar/colors.c index 275fa3c64..da606c1b8 100644 --- a/sway/commands/bar/colors.c +++ b/sway/commands/bar/colors.c @@ -72,6 +72,10 @@ static struct cmd_results *parse_three_colors(char ***colors, } struct cmd_results *bar_cmd_colors(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "colors", EXPECTED_AT_LEAST, 1))) { + return error; + } return config_subcommand(argv, argc, bar_colors_handlers, sizeof(bar_colors_handlers)); } diff --git a/sway/commands/bar/font.c b/sway/commands/bar/font.c index 0c074679a..51ca20ed5 100644 --- a/sway/commands/bar/font.c +++ b/sway/commands/bar/font.c @@ -11,7 +11,7 @@ struct cmd_results *bar_cmd_font(int argc, char **argv) { char *font = join_args(argv, argc); free(config->current_bar->font); - if (strncmp(font, "pango:", 6) == 0) { + if (has_prefix(font, "pango:")) { if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) { config->current_bar->pango_markup = true; } diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 268f28553..627d994a0 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -367,8 +367,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, } } else if (strcmp("--exclude-titlebar", argv[0]) == 0) { exclude_titlebar = true; - } else if (strncmp("--input-device=", argv[0], - strlen("--input-device=")) == 0) { + } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); strip_quotes(binding->input); @@ -399,7 +398,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, list_t *split = split_string(argv[0], "+"); for (int i = 0; i < split->length; ++i) { // Check for group - if (strncmp(split->items[i], "Group", strlen("Group")) == 0) { + if (has_prefix(split->items[i], "Group")) { if (binding->group != XKB_LAYOUT_INVALID) { free_sway_binding(binding); list_free_items_and_destroy(split); @@ -544,6 +543,10 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv, binding->type = WLR_SWITCH_TYPE_TABLET_MODE; } else if (strcmp(split->items[0], "lid") == 0) { binding->type = WLR_SWITCH_TYPE_LID; +#if HAVE_LIBINPUT_SWITCH_KEYPAD_SLIDE + } else if (strcmp(split->items[0], "keypad_slide") == 0) { + binding->type = WLR_SWITCH_TYPE_KEYPAD_SLIDE; +#endif } else { free_switch_binding(binding); return cmd_results_new(CMD_FAILURE, diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c index 8bc1048cd..a966696c5 100644 --- a/sway/commands/exec_always.c +++ b/sway/commands/exec_always.c @@ -25,16 +25,6 @@ struct cmd_results *cmd_exec_validate(int argc, char **argv) { return error; } -static void export_xdga_token(struct launcher_ctx *ctx) { - const char *token = launcher_ctx_get_token_name(ctx); - setenv("XDG_ACTIVATION_TOKEN", token, 1); -} - -static void export_startup_id(struct launcher_ctx *ctx) { - const char *token = launcher_ctx_get_token_name(ctx); - setenv("DESKTOP_STARTUP_ID", token, 1); -} - struct cmd_results *cmd_exec_process(int argc, char **argv) { struct cmd_results *error = NULL; char *cmd = NULL; @@ -56,67 +46,37 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) { sway_log(SWAY_DEBUG, "Executing %s", cmd); - int fd[2]; - if (pipe(fd) != 0) { - sway_log(SWAY_ERROR, "Unable to create pipe for fork"); - } - - pid_t pid, child; struct launcher_ctx *ctx = launcher_ctx_create_internal(); + // Fork process - if ((pid = fork()) == 0) { - // Fork child process again - restore_nofile_limit(); + pid_t child = fork(); + if (child == 0) { setsid(); - sigset_t set; - sigemptyset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); - signal(SIGPIPE, SIG_DFL); - close(fd[0]); - if ((child = fork()) == 0) { - close(fd[1]); - if (ctx) { - export_xdga_token(ctx); + + if (ctx) { + const char *token = launcher_ctx_get_token_name(ctx); + setenv("XDG_ACTIVATION_TOKEN", token, 1); + if (!no_startup_id) { + setenv("DESKTOP_STARTUP_ID", token, 1); } - if (ctx && !no_startup_id) { - export_startup_id(ctx); - } - execlp("sh", "sh", "-c", cmd, (void *)NULL); - sway_log_errno(SWAY_ERROR, "execlp failed"); - _exit(1); } - ssize_t s = 0; - while ((size_t)s < sizeof(pid_t)) { - s += write(fd[1], ((uint8_t *)&child) + s, sizeof(pid_t) - s); - } - close(fd[1]); + + execlp("sh", "sh", "-c", cmd, (void*)NULL); + sway_log_errno(SWAY_ERROR, "execve failed"); _exit(0); // Close child process - } else if (pid < 0) { + } else if (child < 0) { + launcher_ctx_destroy(ctx); free(cmd); - close(fd[0]); - close(fd[1]); return cmd_results_new(CMD_FAILURE, "fork() failed"); } - free(cmd); - close(fd[1]); // close write - ssize_t s = 0; - while ((size_t)s < sizeof(pid_t)) { - s += read(fd[0], ((uint8_t *)&child) + s, sizeof(pid_t) - s); - } - close(fd[0]); - // cleanup child process - waitpid(pid, NULL, 0); - if (child > 0) { - sway_log(SWAY_DEBUG, "Child process created with pid %d", child); - if (ctx != NULL) { - sway_log(SWAY_DEBUG, "Recording workspace for process %d", child); - ctx->pid = child; - } - } else { - launcher_ctx_destroy(ctx); - return cmd_results_new(CMD_FAILURE, "Second fork() failed"); + + sway_log(SWAY_DEBUG, "Child process created with pid %d", child); + if (ctx != NULL) { + sway_log(SWAY_DEBUG, "Recording workspace for process %d", child); + ctx->pid = child; } + free(cmd); return cmd_results_new(CMD_SUCCESS, NULL); } diff --git a/sway/commands/exit.c b/sway/commands/exit.c index 10cde640c..0f326cea6 100644 --- a/sway/commands/exit.c +++ b/sway/commands/exit.c @@ -1,8 +1,7 @@ #include #include "sway/commands.h" #include "sway/config.h" - -void sway_terminate(int exit_code); +#include "sway/server.h" struct cmd_results *cmd_exit(int argc, char **argv) { struct cmd_results *error = NULL; diff --git a/sway/commands/font.c b/sway/commands/font.c index 9920d03eb..842e8ae61 100644 --- a/sway/commands/font.c +++ b/sway/commands/font.c @@ -13,9 +13,9 @@ struct cmd_results *cmd_font(int argc, char **argv) { char *font = join_args(argv, argc); free(config->font); - if (strncmp(font, "pango:", 6) == 0) { + if (has_prefix(font, "pango:")) { config->pango_markup = true; - config->font = strdup(font + 6); + config->font = strdup(font + strlen("pango:")); free(font); } else { config->pango_markup = false; diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c index 1deeb56e1..7ac6fcff5 100644 --- a/sway/commands/gaps.c +++ b/sway/commands/gaps.c @@ -215,15 +215,13 @@ struct cmd_results *cmd_gaps(int argc, char **argv) { return error; } - bool config_loading = !config->active || config->reloading; - if (argc == 2) { return gaps_set_defaults(argc, argv); } - if (argc == 4 && !config_loading) { + if (argc == 4 && !config->reading) { return gaps_set_runtime(argc, argv); } - if (config_loading) { + if (config->reading) { return cmd_results_new(CMD_INVALID, "Expected %s", expected_defaults); } return cmd_results_new(CMD_INVALID, "Expected %s or %s", diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c index 90a20716d..8dff29a34 100644 --- a/sway/commands/gesture.c +++ b/sway/commands/gesture.c @@ -121,8 +121,7 @@ static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, boo binding->flags |= BINDING_EXACT; } else if (strcmp("--no-warn", argv[0]) == 0) { warn = false; - } else if (strncmp("--input-device=", argv[0], - strlen("--input-device=")) == 0) { + } else if (has_prefix(argv[0], "--input-device=")) { free(binding->input); binding->input = strdup(argv[0] + strlen("--input-device=")); } else { diff --git a/sway/commands/include.c b/sway/commands/include.c index d4c14c35f..e0d0c0640 100644 --- a/sway/commands/include.c +++ b/sway/commands/include.c @@ -3,12 +3,12 @@ struct cmd_results *cmd_include(int argc, char **argv) { struct cmd_results *error = NULL; - if ((error = checkarg(argc, "include", EXPECTED_EQUAL_TO, 1))) { + if ((error = checkarg(argc, "include", EXPECTED_AT_LEAST, 1))) { return error; } - + char *files = join_args(argv, argc); // We don't care if the included config(s) fails to load. - load_include_configs(argv[0], config, &config->swaynag_config_errors); + load_include_configs(files, config, &config->swaynag_config_errors); return cmd_results_new(CMD_SUCCESS, NULL); } diff --git a/sway/commands/input.c b/sway/commands/input.c index 306c40f74..310375a94 100644 --- a/sway/commands/input.c +++ b/sway/commands/input.c @@ -11,6 +11,7 @@ static const struct cmd_handler input_handlers[] = { { "accel_profile", input_cmd_accel_profile }, { "calibration_matrix", input_cmd_calibration_matrix }, { "click_method", input_cmd_click_method }, + { "clickfinger_button_map", input_cmd_clickfinger_button_map }, { "drag", input_cmd_drag }, { "drag_lock", input_cmd_drag_lock }, { "dwt", input_cmd_dwt }, @@ -93,7 +94,7 @@ struct cmd_results *cmd_input(int argc, char **argv) { return res; } - if (!config->reloading) { + if (!config->reading) { input_manager_apply_input_config(ic); } } else { diff --git a/sway/commands/input/clickfinger_button_map.c b/sway/commands/input/clickfinger_button_map.c new file mode 100644 index 000000000..57d6e39a6 --- /dev/null +++ b/sway/commands/input/clickfinger_button_map.c @@ -0,0 +1,27 @@ +#include +#include +#include "sway/config.h" +#include "sway/commands.h" +#include "sway/input/input-manager.h" + +struct cmd_results *input_cmd_clickfinger_button_map(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "clickfinger_button_map", EXPECTED_AT_LEAST, 1))) { + return error; + } + struct input_config *ic = config->handler_context.input_config; + if (!ic) { + return cmd_results_new(CMD_FAILURE, "No input device defined."); + } + + if (strcasecmp(argv[0], "lrm") == 0) { + ic->clickfinger_button_map = LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM; + } else if (strcasecmp(argv[0], "lmr") == 0) { + ic->clickfinger_button_map = LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR; + } else { + return cmd_results_new(CMD_INVALID, + "Expected 'clickfinger_button_map '"); + } + + return cmd_results_new(CMD_SUCCESS, NULL); +} diff --git a/sway/commands/input/drag_lock.c b/sway/commands/input/drag_lock.c index 24c548e2d..915a7ada1 100644 --- a/sway/commands/input/drag_lock.c +++ b/sway/commands/input/drag_lock.c @@ -15,6 +15,11 @@ struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, "No input device defined."); } +#if HAVE_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY + if (strcmp(argv[0], "enabled_sticky") == 0) { + ic->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY; + } else +#endif if (parse_boolean(argv[0], true)) { ic->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; } else { diff --git a/sway/commands/input/events.c b/sway/commands/input/events.c index 08d99bf0b..4650108a1 100644 --- a/sway/commands/input/events.c +++ b/sway/commands/input/events.c @@ -5,6 +5,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "sway/server.h" #include "log.h" #if WLR_HAS_LIBINPUT_BACKEND @@ -85,7 +86,7 @@ static void toggle_select_send_events_for_device(struct input_config *ic, static void toggle_send_events(int argc, char **argv) { struct input_config *ic = config->handler_context.input_config; bool wildcard = strcmp(ic->identifier, "*") == 0; - const char *type = strncmp(ic->identifier, "type:", strlen("type:")) == 0 + const char *type = has_prefix(ic->identifier, "type:") ? ic->identifier + strlen("type:") : NULL; struct sway_input_device *device = NULL; wl_list_for_each(device, &server.input->devices, link) { @@ -145,8 +146,7 @@ struct cmd_results *input_cmd_events(int argc, char **argv) { toggle_send_events(argc - 1, argv + 1); - if (strcmp(ic->identifier, "*") == 0 || - strncmp(ic->identifier, "type:", strlen("type:")) == 0) { + if (strcmp(ic->identifier, "*") == 0 || has_prefix(ic->identifier, "type:")) { // Update the device input configs and then reset the type/wildcard // config send events mode so that is does not override the device // ones. The device ones will be applied when attempting to apply diff --git a/sway/commands/input/xkb_switch_layout.c b/sway/commands/input/xkb_switch_layout.c index ecac8e6c2..623dfa186 100644 --- a/sway/commands/input/xkb_switch_layout.c +++ b/sway/commands/input/xkb_switch_layout.c @@ -3,6 +3,7 @@ #include "sway/config.h" #include "sway/commands.h" #include "sway/input/input-manager.h" +#include "sway/server.h" #include "log.h" struct xkb_switch_layout_action { @@ -94,10 +95,18 @@ struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) { continue; } + struct wlr_keyboard *keyboard = + wlr_keyboard_from_input_device(dev->wlr_device); + if (keyboard->keymap == NULL && dev->is_virtual) { + // The `sway_keyboard_set_layout` function is by default skipped + // when configuring virtual keyboards. + continue; + } + struct xkb_switch_layout_action *action = &actions[actions_len++]; + action->keyboard = keyboard; - action->keyboard = wlr_keyboard_from_input_device(dev->wlr_device); if (relative) { action->layout = get_layout_relative(action->keyboard, relative); } else { diff --git a/sway/commands/layout.c b/sway/commands/layout.c index 12ce48398..22dfdf3d7 100644 --- a/sway/commands/layout.c +++ b/sway/commands/layout.c @@ -134,6 +134,19 @@ struct cmd_results *cmd_layout(int argc, char **argv) { // Operate on parent container, like i3. if (container) { container = container->pending.parent; + // If parent has only a singe child operate on its parent and + // flatten once, like i3 + if (container && container->pending.children->length == 1) { + // Also check grandparent to avoid restricting layouts + struct sway_container *parent = container->pending.parent; + if (parent && parent->pending.children->length == 1) { + struct sway_container *child = container->pending.children->items[0]; + struct sway_container *parent = container->pending.parent; + container_replace(container, child); + container_begin_destroy(container); + container = parent; + } + } } // We could be working with a container OR a workspace. These are different diff --git a/sway/commands/mark.c b/sway/commands/mark.c index 2bfc86b30..77c8d2394 100644 --- a/sway/commands/mark.c +++ b/sway/commands/mark.c @@ -23,7 +23,7 @@ struct cmd_results *cmd_mark(int argc, char **argv) { } bool add = false, toggle = false; - while (argc > 0 && strncmp(*argv, "--", 2) == 0) { + while (argc > 0 && has_prefix(*argv, "--")) { if (strcmp(*argv, "--add") == 0) { add = true; } else if (strcmp(*argv, "--replace") == 0) { diff --git a/sway/commands/move.c b/sway/commands/move.c index 8addf26ec..43fce0d6d 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -11,6 +11,7 @@ #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" +#include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/root.h" @@ -221,6 +222,7 @@ static void container_move_to_workspace(struct sway_container *container, container_detach(container); if (workspace_is_empty(workspace) && container->pending.children) { workspace_unwrap_children(workspace, container); + container_reap_empty(container); } else { container->pending.width = container->pending.height = 0; container->width_fraction = container->height_fraction = 0; @@ -239,7 +241,6 @@ static void container_move_to_workspace(struct sway_container *container, static void container_move_to_container(struct sway_container *container, struct sway_container *destination) { if (container == destination - || container_has_ancestor(container, destination) || container_has_ancestor(destination, container)) { return; } @@ -509,6 +510,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, } } ws = workspace_create(NULL, ws_name); + arrange_workspace(ws); } free(ws_name); struct sway_container *dst = seat_get_focus_inactive_tiling(seat, ws); @@ -625,40 +627,6 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth, return cmd_results_new(CMD_SUCCESS, NULL); } -static void workspace_move_to_output(struct sway_workspace *workspace, - struct sway_output *output) { - if (workspace->output == output) { - return; - } - struct sway_output *old_output = workspace->output; - workspace_detach(workspace); - struct sway_workspace *new_output_old_ws = - output_get_active_workspace(output); - if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) { - return; - } - - output_add_workspace(output, workspace); - - // If moving the last workspace from the old output, create a new workspace - // on the old output - struct sway_seat *seat = config->handler_context.seat; - if (old_output->workspaces->length == 0) { - char *ws_name = workspace_next_name(old_output->wlr_output->name); - struct sway_workspace *ws = workspace_create(old_output, ws_name); - free(ws_name); - seat_set_raw_focus(seat, &ws->node); - } - - workspace_consider_destroy(new_output_old_ws); - - output_sort_workspaces(output); - struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node); - seat_set_focus(seat, focus); - workspace_output_raise_priority(workspace, old_output, output); - ipc_event_workspace(NULL, workspace, "move"); -} - static struct cmd_results *cmd_move_workspace(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "move workspace", EXPECTED_AT_LEAST, 1))) { @@ -694,6 +662,8 @@ static struct cmd_results *cmd_move_workspace(int argc, char **argv) { arrange_output(new_output); struct sway_seat *seat = config->handler_context.seat; + struct sway_node *focus = seat_get_focus_inactive(seat, &workspace->node); + seat_set_focus(seat, focus); seat_consider_warp_to_focus(seat); return cmd_results_new(CMD_SUCCESS, NULL); diff --git a/sway/commands/opacity.c b/sway/commands/opacity.c index 610cecc6d..de058c6b8 100644 --- a/sway/commands/opacity.c +++ b/sway/commands/opacity.c @@ -2,7 +2,8 @@ #include #include #include "sway/commands.h" -#include "sway/tree/view.h" +#include "sway/tree/container.h" +#include "sway/output.h" #include "log.h" struct cmd_results *cmd_opacity(int argc, char **argv) { @@ -37,6 +38,7 @@ struct cmd_results *cmd_opacity(int argc, char **argv) { } con->alpha = val; + output_configure_scene(NULL, &con->scene_tree->node, 1); container_update(con); return cmd_results_new(CMD_SUCCESS, NULL); diff --git a/sway/commands/output.c b/sway/commands/output.c index 462dffd23..afff23f6a 100644 --- a/sway/commands/output.c +++ b/sway/commands/output.c @@ -8,11 +8,14 @@ // must be in order for the bsearch static const struct cmd_handler output_handlers[] = { { "adaptive_sync", output_cmd_adaptive_sync }, + { "allow_tearing", output_cmd_allow_tearing }, { "background", output_cmd_background }, { "bg", output_cmd_background }, + { "color_profile", output_cmd_color_profile }, { "disable", output_cmd_disable }, { "dpms", output_cmd_dpms }, { "enable", output_cmd_enable }, + { "hdr", output_cmd_hdr }, { "max_render_time", output_cmd_max_render_time }, { "mode", output_cmd_mode }, { "modeline", output_cmd_modeline }, @@ -103,19 +106,18 @@ struct cmd_results *cmd_output(int argc, char **argv) { bool background = output->background; - output = store_output_config(output); + store_output_config(output); - // If reloading, the output configs will be applied after reading the - // entire config and before the deferred commands so that an auto generated - // workspace name is not given to re-enabled outputs. - if (!config->reloading && !config->validating) { - apply_output_config_to_outputs(output); - if (background) { - if (!spawn_swaybg()) { - return cmd_results_new(CMD_FAILURE, - "Failed to apply background configuration"); - } - } + if (config->reading) { + // When reading the config file, we wait till the end to do a single + // modeset and swaybg spawn. + return cmd_results_new(CMD_SUCCESS, NULL); + } + request_modeset(); + + if (background && !spawn_swaybg()) { + return cmd_results_new(CMD_FAILURE, + "Failed to apply background configuration"); } return cmd_results_new(CMD_SUCCESS, NULL); diff --git a/sway/commands/output/adaptive_sync.c b/sway/commands/output/adaptive_sync.c index 7382e2ee3..4ce88f885 100644 --- a/sway/commands/output/adaptive_sync.c +++ b/sway/commands/output/adaptive_sync.c @@ -1,5 +1,7 @@ +#include #include "sway/commands.h" #include "sway/config.h" +#include "sway/output.h" #include "util.h" struct cmd_results *output_cmd_adaptive_sync(int argc, char **argv) { @@ -10,12 +12,26 @@ struct cmd_results *output_cmd_adaptive_sync(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "Missing adaptive_sync argument"); } - if (parse_boolean(argv[0], true)) { - config->handler_context.output_config->adaptive_sync = 1; - } else { - config->handler_context.output_config->adaptive_sync = 0; + bool current_value = true; + if (strcasecmp(argv[0], "toggle") == 0) { + const char *oc_name = config->handler_context.output_config->name; + if (strcmp(oc_name, "*") == 0) { + return cmd_results_new(CMD_INVALID, + "Cannot apply toggle to all outputs"); + } + + struct sway_output *sway_output = all_output_by_name_or_id(oc_name); + if (!sway_output || !sway_output->wlr_output) { + return cmd_results_new(CMD_FAILURE, + "Cannot apply toggle to unknown output %s", oc_name); + } + + current_value = + sway_output->wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; } + config->handler_context.output_config->adaptive_sync = parse_boolean(argv[0], current_value); + config->handler_context.leftovers.argc = argc - 1; config->handler_context.leftovers.argv = argv + 1; return NULL; diff --git a/sway/commands/output/allow_tearing.c b/sway/commands/output/allow_tearing.c new file mode 100644 index 000000000..9a183b96c --- /dev/null +++ b/sway/commands/output/allow_tearing.c @@ -0,0 +1,23 @@ +#include "sway/commands.h" +#include "sway/config.h" +#include "util.h" + +struct cmd_results *output_cmd_allow_tearing(int argc, char **argv) { + if (!config->handler_context.output_config) { + return cmd_results_new(CMD_FAILURE, "Missing output config"); + } + if (argc == 0) { + return cmd_results_new(CMD_INVALID, "Missing allow_tearing argument"); + } + + if (parse_boolean(argv[0], + (config->handler_context.output_config->allow_tearing == 1))) { + config->handler_context.output_config->allow_tearing = 1; + } else { + config->handler_context.output_config->allow_tearing = 0; + } + + config->handler_context.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + return NULL; +} diff --git a/sway/commands/output/background.c b/sway/commands/output/background.c index 55bd7671f..fe1621626 100644 --- a/sway/commands/output/background.c +++ b/sway/commands/output/background.c @@ -3,10 +3,8 @@ #include #include #include -#include #include "sway/commands.h" #include "sway/config.h" -#include "sway/swaynag.h" #include "log.h" #include "stringop.h" @@ -42,14 +40,14 @@ struct cmd_results *output_cmd_background(int argc, char **argv) { } struct output_config *output = config->handler_context.output_config; - + char *src = NULL; if (strcasecmp(argv[1], "solid_color") == 0) { if (!validate_color(argv[0])) { return cmd_results_new(CMD_INVALID, "Colors should be of the form #RRGGBB"); } - output->background = strdup(argv[0]); - output->background_option = strdup("solid_color"); + if (!(output->background = strdup(argv[0]))) goto cleanup; + if (!(output->background_option = strdup("solid_color"))) goto cleanup; output->background_fallback = NULL; argc -= 2; argv += 2; } else { @@ -77,37 +75,25 @@ struct cmd_results *output_cmd_background(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "Missing background file"); } - char *src = join_args(argv, j); + if (!(src = join_args(argv, j))) goto cleanup; if (!expand_path(&src)) { struct cmd_results *cmd_res = cmd_results_new(CMD_INVALID, "Invalid syntax (%s)", src); free(src); return cmd_res; } - if (!src) { - sway_log(SWAY_ERROR, "Failed to allocate expanded path"); - return cmd_results_new(CMD_FAILURE, "Unable to allocate resource"); - } if (config->reading && *src != '/') { // src file is inside configuration dir char *conf = strdup(config->current_config_path); - if (!conf) { - sway_log(SWAY_ERROR, "Failed to duplicate string"); - free(src); - return cmd_results_new(CMD_FAILURE, - "Unable to allocate resources"); - } + if (!conf) goto cleanup; char *conf_path = dirname(conf); char *real_src = malloc(strlen(conf_path) + strlen(src) + 2); if (!real_src) { - free(src); free(conf); - sway_log(SWAY_ERROR, "Unable to allocate memory"); - return cmd_results_new(CMD_FAILURE, - "Unable to allocate resources"); + goto cleanup; } snprintf(real_src, strlen(conf_path) + strlen(src) + 2, "%s/%s", conf_path, src); @@ -117,40 +103,48 @@ struct cmd_results *output_cmd_background(int argc, char **argv) { } bool can_access = access(src, F_OK) != -1; + argc -= j + 1; argv += j + 1; + free(output->background_option); + free(output->background_fallback); + free(output->background); + output->background = output->background_option = output->background_fallback = NULL; + char *fallback = NULL; + + if (argc && *argv[0] == '#') { + if (validate_color(argv[0])) { + if (!(fallback = strdup(argv[0]))) goto cleanup; + output->background_fallback = fallback; + } else { + sway_log(SWAY_ERROR, "fallback '%s' should be of the form #RRGGBB", argv[0]); + config_add_swaynag_warning("fallback '%s' should be of the form #RRGGBB\n", argv[0]); + } + argc--; argv++; + } + if (!can_access) { - sway_log_errno(SWAY_ERROR, "Unable to access background file '%s'", - src); - config_add_swaynag_warning("Unable to access background file '%s'", - src); - struct cmd_results *result = cmd_results_new(CMD_FAILURE, - "unable to access background file '%s'", src); - free(src); - return result; + if (!fallback) { + sway_log(SWAY_ERROR, "Unable to access background file '%s' " + "and no valid fallback provided", src); + struct cmd_results *res = cmd_results_new(CMD_FAILURE, "Unable to access " + "background file '%s' and no valid fallback provided", src); + free(src); + return res; + } + sway_log(SWAY_DEBUG, "Cannot access file '%s', using fallback '%s'", src, fallback); + output->background = fallback; + if (!(output->background_option = strdup("solid_color"))) goto cleanup; + output->background_fallback = NULL; } else { output->background = src; - output->background_option = strdup(mode); - } - argc -= j + 1; argv += j + 1; - - output->background_fallback = NULL; - if (argc && *argv[0] == '#') { - if (!validate_color(argv[0])) { - return cmd_results_new(CMD_INVALID, - "fallback color should be of the form #RRGGBB"); - } - - output->background_fallback = strdup(argv[0]); - argc--; argv++; - - if (!can_access) { - output->background = output->background_fallback; - output->background_option = strdup("solid_color"); - output->background_fallback = NULL; - } + if (!(output->background_option = strdup(mode))) goto cleanup; } } - config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; + +cleanup: + free(src); + sway_log(SWAY_ERROR, "Failed to allocate resources"); + return cmd_results_new(CMD_FAILURE, "Unable to allocate resources"); } diff --git a/sway/commands/output/color_profile.c b/sway/commands/output/color_profile.c new file mode 100644 index 000000000..0456b19b7 --- /dev/null +++ b/sway/commands/output/color_profile.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "stringop.h" + +static bool read_file_into_buf(const char *path, void **buf, size_t *size) { + /* Why not use fopen/fread directly? glibc will succesfully open directories, + * not just files, and supports seeking on them. Instead, we directly + * work with file descriptors and use the more consistent open/fstat/read. */ + int fd = open(path, O_RDONLY | O_NOCTTY | O_CLOEXEC); + if (fd == -1) { + return false; + } + char *b = NULL; + struct stat info; + if (fstat(fd, &info) == -1) { + goto fail; + } + // only regular files, to avoid issues with e.g. opening pipes + if (!S_ISREG(info.st_mode)) { + goto fail; + } + off_t s = info.st_size; + if (s <= 0) { + goto fail; + } + b = calloc(1, s); + if (!b) { + goto fail; + } + size_t nread = 0; + while (nread < (size_t)s) { + size_t to_read = (size_t)s - nread; + ssize_t r = read(fd, b + nread, to_read); + if ((r == -1 && errno != EINTR) || r == 0) { + goto fail; + } + nread += (size_t)r; + } + close(fd); + *buf = b; + *size = (size_t)s; + return true; // success +fail: + free(b); + close(fd); + return false; +} + +struct cmd_results *output_cmd_color_profile(int argc, char **argv) { + if (!config->handler_context.output_config) { + return cmd_results_new(CMD_FAILURE, "Missing output config"); + } + + enum color_profile new_mode = COLOR_PROFILE_TRANSFORM; + if (argc >= 2 && strcmp(*argv, "--device-primaries") == 0) { + new_mode = COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES; + argc--; + argv++; + } + + if (!argc) { + return cmd_results_new(CMD_INVALID, "Missing color_profile first argument."); + } + + if (strcmp(*argv, "gamma22") == 0) { + wlr_color_transform_unref(config->handler_context.output_config->color_transform); + config->handler_context.output_config->color_transform = NULL; + config->handler_context.output_config->color_profile = new_mode; + + config->handler_context.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + } else if (strcmp(*argv, "srgb") == 0) { + wlr_color_transform_unref(config->handler_context.output_config->color_transform); + config->handler_context.output_config->color_transform = + wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_SRGB); + config->handler_context.output_config->color_profile = new_mode; + + config->handler_context.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + } else if (strcmp(*argv, "icc") == 0) { + if (argc < 2) { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: icc type requires a file"); + } + if (new_mode != COLOR_PROFILE_TRANSFORM) { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: --device-primaries cannot be used with icc"); + } + + char *icc_path = strdup(argv[1]); + if (!expand_path(&icc_path)) { + struct cmd_results *cmd_res = cmd_results_new(CMD_INVALID, + "Invalid color profile specification: invalid file path"); + free(icc_path); + return cmd_res; + } + + void *data = NULL; + size_t size = 0; + if (!read_file_into_buf(icc_path, &data, &size)) { + free(icc_path); + return cmd_results_new(CMD_FAILURE, + "Failed to load color profile: could not read ICC file"); + } + free(icc_path); + + struct wlr_color_transform *tmp = + wlr_color_transform_init_linear_to_icc(data, size); + if (!tmp) { + free(data); + return cmd_results_new(CMD_FAILURE, + "Failed to load color profile: failed to initialize transform from ICC"); + } + free(data); + + wlr_color_transform_unref(config->handler_context.output_config->color_transform); + config->handler_context.output_config->color_transform = tmp; + config->handler_context.output_config->color_profile = COLOR_PROFILE_TRANSFORM; + + config->handler_context.leftovers.argc = argc - 2; + config->handler_context.leftovers.argv = argv + 2; + } else { + return cmd_results_new(CMD_INVALID, + "Invalid color profile specification: " + "first argument should be gamma22|icc|srgb"); + } + + return NULL; +} diff --git a/sway/commands/output/hdr.c b/sway/commands/output/hdr.c new file mode 100644 index 000000000..26d202216 --- /dev/null +++ b/sway/commands/output/hdr.c @@ -0,0 +1,37 @@ +#include +#include "sway/commands.h" +#include "sway/config.h" +#include "sway/output.h" +#include "util.h" + +struct cmd_results *output_cmd_hdr(int argc, char **argv) { + if (!config->handler_context.output_config) { + return cmd_results_new(CMD_FAILURE, "Missing output config"); + } + if (argc == 0) { + return cmd_results_new(CMD_INVALID, "Missing hdr argument"); + } + + bool current = false; + if (strcasecmp(argv[0], "toggle") == 0) { + const char *oc_name = config->handler_context.output_config->name; + if (strcmp(oc_name, "*") == 0) { + return cmd_results_new(CMD_INVALID, + "Cannot apply toggle to all outputs"); + } + + struct sway_output *output = all_output_by_name_or_id(oc_name); + if (!output) { + return cmd_results_new(CMD_FAILURE, + "Cannot apply toggle to unknown output %s", oc_name); + } + + current = output->hdr; + } + + config->handler_context.output_config->hdr = parse_boolean(argv[0], current); + + config->handler_context.leftovers.argc = argc - 1; + config->handler_context.leftovers.argv = argv + 1; + return NULL; +} diff --git a/sway/commands/output/render_bit_depth.c b/sway/commands/output/render_bit_depth.c index c419321ea..3fa30e045 100644 --- a/sway/commands/output/render_bit_depth.c +++ b/sway/commands/output/render_bit_depth.c @@ -11,7 +11,10 @@ struct cmd_results *output_cmd_render_bit_depth(int argc, char **argv) { return cmd_results_new(CMD_INVALID, "Missing bit depth argument."); } - if (strcmp(*argv, "8") == 0) { + if (strcmp(*argv, "6") == 0) { + config->handler_context.output_config->render_bit_depth = + RENDER_BIT_DEPTH_6; + } else if (strcmp(*argv, "8") == 0) { config->handler_context.output_config->render_bit_depth = RENDER_BIT_DEPTH_8; } else if (strcmp(*argv, "10") == 0) { @@ -19,7 +22,7 @@ struct cmd_results *output_cmd_render_bit_depth(int argc, char **argv) { RENDER_BIT_DEPTH_10; } else { return cmd_results_new(CMD_INVALID, - "Invalid bit depth. Must be a value in (8|10)."); + "Invalid bit depth. Must be a value in (6|8|10)."); } config->handler_context.leftovers.argc = argc - 1; diff --git a/sway/commands/output/toggle.c b/sway/commands/output/toggle.c index 6342d526b..c6b72845b 100644 --- a/sway/commands/output/toggle.c +++ b/sway/commands/output/toggle.c @@ -29,7 +29,7 @@ struct cmd_results *output_cmd_toggle(int argc, char **argv) { config->handler_context.output_config->enabled = 1; } - free(oc); + free_output_config(oc); config->handler_context.leftovers.argc = argc; config->handler_context.leftovers.argv = argv; return NULL; diff --git a/sway/commands/rename.c b/sway/commands/rename.c index 0d36cc21e..63fac05cc 100644 --- a/sway/commands/rename.c +++ b/sway/commands/rename.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "log.h" #include "stringop.h" #include "sway/commands.h" @@ -95,6 +96,8 @@ struct cmd_results *cmd_rename(int argc, char **argv) { free(workspace->name); workspace->name = new_name; + wlr_ext_workspace_handle_v1_set_name(workspace->ext_workspace, workspace->name); + output_sort_workspaces(workspace->output); ipc_event_workspace(NULL, workspace, "rename"); diff --git a/sway/commands/resize.c b/sway/commands/resize.c index 32b746eaf..3530ee5a8 100644 --- a/sway/commands/resize.c +++ b/sway/commands/resize.c @@ -80,61 +80,46 @@ void container_resize_tiled(struct sway_container *con, } // For HORIZONTAL or VERTICAL, we are growing in two directions so select - // both adjacent siblings. For RIGHT or DOWN, just select the next sibling. - // For LEFT or UP, convert it to a RIGHT or DOWN resize and reassign con to - // the previous sibling. - struct sway_container *prev = NULL; - struct sway_container *next = NULL; + // all adjacent siblings. For RIGHT or DOWN or LEFT or UP select just the + // previous or next sibling. + list_t *resize = create_list(); list_t *siblings = container_get_siblings(con); int index = container_sibling_index(con); if (axis == AXIS_HORIZONTAL || axis == AXIS_VERTICAL) { - if (index == 0) { - next = siblings->items[1]; - } else if (index == siblings->length - 1) { - // Convert edge to top/left - next = con; - con = siblings->items[index - 1]; - amount = -amount; - } else { - prev = siblings->items[index - 1]; - next = siblings->items[index + 1]; - } + list_cat(resize, siblings); } else if (axis == WLR_EDGE_TOP || axis == WLR_EDGE_LEFT) { if (!sway_assert(index > 0, "Didn't expect first child")) { - return; + goto cleanup; } - next = con; - con = siblings->items[index - 1]; - amount = -amount; + list_add(resize, siblings->items[index - 1]); + list_add(resize, con); } else { if (!sway_assert(index < siblings->length - 1, "Didn't expect last child")) { - return; + goto cleanup; } - next = siblings->items[index + 1]; + list_add(resize, con); + list_add(resize, siblings->items[index + 1]); } // Apply new dimensions - int sibling_amount = prev ? ceil((double)amount / 2.0) : amount; + int sibling_amount = ceil((double)amount / (double)(resize->length - 1)); if (is_horizontal(axis)) { - if (con->pending.width + amount < MIN_SANE_W) { - return; - } - if (next->pending.width - sibling_amount < MIN_SANE_W) { - return; - } - if (prev && prev->pending.width - sibling_amount < MIN_SANE_W) { - return; + for (int i = 0; i < resize->length; i++) { + struct sway_container *sibling = resize->items[i]; + double change = sibling == con ? amount : -sibling_amount; + if (sibling->pending.width + change < MIN_SANE_W) { + goto cleanup; + } } if (con->child_total_width <= 0) { - return; + goto cleanup; } // We're going to resize so snap all the width fractions to full pixels // to avoid rounding issues - list_t *siblings = container_get_siblings(con); for (int i = 0; i < siblings->length; ++i) { struct sway_container *con = siblings->items[i]; con->width_fraction = con->pending.width / con->child_total_width; @@ -142,30 +127,27 @@ void container_resize_tiled(struct sway_container *con, double amount_fraction = (double)amount / con->child_total_width; double sibling_amount_fraction = - prev ? amount_fraction / 2.0 : amount_fraction; + amount_fraction / (double)(resize->length - 1); - con->width_fraction += amount_fraction; - next->width_fraction -= sibling_amount_fraction; - if (prev) { - prev->width_fraction -= sibling_amount_fraction; + for (int i = 0; i < resize->length; i++) { + struct sway_container *sibling = resize->items[i]; + sibling->width_fraction += + sibling == con ? amount_fraction : -sibling_amount_fraction; } } else { - if (con->pending.height + amount < MIN_SANE_H) { - return; - } - if (next->pending.height - sibling_amount < MIN_SANE_H) { - return; - } - if (prev && prev->pending.height - sibling_amount < MIN_SANE_H) { - return; + for (int i = 0; i < resize->length; i++) { + struct sway_container *sibling = resize->items[i]; + double change = sibling == con ? amount : -sibling_amount; + if (sibling->pending.height + change < MIN_SANE_H) { + goto cleanup; + } } if (con->child_total_height <= 0) { - return; + goto cleanup; } // We're going to resize so snap all the height fractions to full pixels // to avoid rounding issues - list_t *siblings = container_get_siblings(con); for (int i = 0; i < siblings->length; ++i) { struct sway_container *con = siblings->items[i]; con->height_fraction = con->pending.height / con->child_total_height; @@ -173,12 +155,12 @@ void container_resize_tiled(struct sway_container *con, double amount_fraction = (double)amount / con->child_total_height; double sibling_amount_fraction = - prev ? amount_fraction / 2.0 : amount_fraction; + amount_fraction / (double)(resize->length - 1); - con->height_fraction += amount_fraction; - next->height_fraction -= sibling_amount_fraction; - if (prev) { - prev->height_fraction -= sibling_amount_fraction; + for (int i = 0; i < resize->length; i++) { + struct sway_container *sibling = resize->items[i]; + sibling->height_fraction += + sibling == con ? amount_fraction : -sibling_amount_fraction; } } @@ -187,6 +169,9 @@ void container_resize_tiled(struct sway_container *con, } else { arrange_workspace(con->pending.workspace); } + +cleanup: + list_free(resize); } /** @@ -457,7 +442,7 @@ static struct cmd_results *cmd_resize_set(int argc, char **argv) { if (argc > num_consumed_args) { return cmd_results_new(CMD_INVALID, "%s", usage); } - if (width.unit == MOVEMENT_UNIT_INVALID) { + if (height.unit == MOVEMENT_UNIT_INVALID) { return cmd_results_new(CMD_INVALID, "%s", usage); } } diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c index c995f2f08..8a63740c3 100644 --- a/sway/commands/scratchpad.c +++ b/sway/commands/scratchpad.c @@ -118,10 +118,10 @@ struct cmd_results *cmd_scratchpad(int argc, char **argv) { // If using criteria, this command is executed for every container which // matches the criteria. If this container isn't in the scratchpad, - // we'll just silently return a success. The same is true if the + // we'll return an error. The same is true if the // overridden node is not a container. if (!con || !con->scratchpad) { - return cmd_results_new(CMD_SUCCESS, NULL); + return cmd_results_new(CMD_INVALID, "Container is not in scratchpad."); } scratchpad_toggle_container(con); } else { diff --git a/sway/commands/seat/cursor.c b/sway/commands/seat/cursor.c index df7c379d1..434e6bbb9 100644 --- a/sway/commands/seat/cursor.c +++ b/sway/commands/seat/cursor.c @@ -5,6 +5,7 @@ #include #include "sway/commands.h" #include "sway/input/cursor.h" +#include "sway/server.h" static struct cmd_results *press_or_release(struct sway_cursor *cursor, char *action, char *button_str); diff --git a/sway/commands/seat/pointer_constraint.c b/sway/commands/seat/pointer_constraint.c index 3890ebde0..38f85bcd6 100644 --- a/sway/commands/seat/pointer_constraint.c +++ b/sway/commands/seat/pointer_constraint.c @@ -4,6 +4,7 @@ #include "sway/config.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" +#include "sway/server.h" enum operation { OP_ENABLE, diff --git a/sway/commands/seat/shortcuts_inhibitor.c b/sway/commands/seat/shortcuts_inhibitor.c index 7c7f99cf0..df68618dc 100644 --- a/sway/commands/seat/shortcuts_inhibitor.c +++ b/sway/commands/seat/shortcuts_inhibitor.c @@ -2,6 +2,7 @@ #include "sway/commands.h" #include "sway/input/seat.h" #include "sway/input/input-manager.h" +#include "sway/server.h" #include "util.h" static struct cmd_results *handle_action(struct seat_config *sc, diff --git a/sway/commands/shortcuts_inhibitor.c b/sway/commands/shortcuts_inhibitor.c index ffa1a5c99..2dfd1b9f9 100644 --- a/sway/commands/shortcuts_inhibitor.c +++ b/sway/commands/shortcuts_inhibitor.c @@ -3,6 +3,7 @@ #include "sway/commands.h" #include "sway/config.h" #include "sway/input/seat.h" +#include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/view.h" diff --git a/sway/commands/swap.c b/sway/commands/swap.c index e142eede8..c0b0d0b9c 100644 --- a/sway/commands/swap.c +++ b/sway/commands/swap.c @@ -18,7 +18,7 @@ static bool test_con_id(struct sway_container *container, void *data) { return container->node.id == *con_id; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND static bool test_id(struct sway_container *container, void *data) { xcb_window_t *wid = data; return (container->view && container->view->type == SWAY_VIEW_XWAYLAND @@ -53,7 +53,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) { char *value = join_args(argv + 3, argc - 3); if (strcasecmp(argv[2], "id") == 0) { -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND xcb_window_t id = strtol(value, NULL, 0); other = root_find_container(test_id, &id); #endif diff --git a/sway/commands/title_format.c b/sway/commands/title_format.c index 0b2ea2659..fbade4731 100644 --- a/sway/commands/title_format.c +++ b/sway/commands/title_format.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 200809L #include #include "sway/commands.h" #include "sway/config.h" @@ -11,16 +12,19 @@ struct cmd_results *cmd_title_format(int argc, char **argv) { return error; } struct sway_container *container = config->handler_context.container; - if (!container || !container->view) { + if (!container) { return cmd_results_new(CMD_INVALID, - "Only views can have a title_format"); + "Only valid containers can have a title_format"); } - struct sway_view *view = container->view; char *format = join_args(argv, argc); - if (view->title_format) { - free(view->title_format); + if (container->title_format) { + free(container->title_format); + } + container->title_format = format; + if (container->view) { + view_update_title(container->view, true); + } else { + container_update_representation(container); } - view->title_format = format; - view_update_title(view, true); return cmd_results_new(CMD_SUCCESS, NULL); } diff --git a/sway/commands/ws_auto_back_and_forth.c b/sway/commands/ws_auto_back_and_forth.c index e4411c8ed..5827c389d 100644 --- a/sway/commands/ws_auto_back_and_forth.c +++ b/sway/commands/ws_auto_back_and_forth.c @@ -8,7 +8,7 @@ struct cmd_results *cmd_ws_auto_back_and_forth(int argc, char **argv) { if ((error = checkarg(argc, "workspace_auto_back_and_forth", EXPECTED_EQUAL_TO, 1))) { return error; } - config->auto_back_and_forth = + config->auto_back_and_forth = parse_boolean(argv[0], config->auto_back_and_forth); return cmd_results_new(CMD_SUCCESS, NULL); } diff --git a/sway/commands/xwayland.c b/sway/commands/xwayland.c index 584a8e3ae..c0b175fcf 100644 --- a/sway/commands/xwayland.c +++ b/sway/commands/xwayland.c @@ -10,7 +10,7 @@ struct cmd_results *cmd_xwayland(int argc, char **argv) { return error; } -#ifdef HAVE_XWAYLAND +#ifdef WLR_HAS_XWAYLAND enum xwayland_mode xwayland; if (strcmp(argv[0], "force") == 0) { xwayland = XWAYLAND_MODE_IMMEDIATE; diff --git a/sway/config.c b/sway/config.c index 3dc1b8a4d..59c58f832 100644 --- a/sway/config.c +++ b/sway/config.c @@ -23,6 +23,7 @@ #include "sway/config.h" #include "sway/criteria.h" #include "sway/desktop/transaction.h" +#include "sway/server.h" #include "sway/swaynag.h" #include "sway/tree/arrange.h" #include "sway/tree/root.h" @@ -516,7 +517,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { // Only really necessary if not explicitly `font` is set in the config. config_update_font_height(); - if (is_active && !validating) { + if (!validating) { input_manager_verify_fallback_seat(); for (int i = 0; i < config->input_configs->length; i++) { @@ -533,12 +534,14 @@ bool load_main_config(const char *file, bool is_active, bool validating) { } sway_switch_retrigger_bindings_for_all(); - reset_outputs(); spawn_swaybg(); config->reloading = false; - if (config->swaynag_config_errors.client != NULL) { - swaynag_show(&config->swaynag_config_errors); + if (is_active) { + request_modeset(); + if (config->swaynag_config_errors.client != NULL) { + swaynag_show(&config->swaynag_config_errors); + } } } @@ -550,28 +553,12 @@ bool load_main_config(const char *file, bool is_active, bool validating) { return success; } -static bool load_include_config(const char *path, const char *parent_dir, - struct sway_config *config, struct swaynag_instance *swaynag) { +static bool load_include_config(const char *path, struct sway_config *config, + struct swaynag_instance *swaynag) { // save parent config const char *parent_config = config->current_config_path; - char *full_path; - int len = strlen(path); - if (len >= 1 && path[0] != '/') { - len = len + strlen(parent_dir) + 2; - full_path = malloc(len * sizeof(char)); - if (!full_path) { - sway_log(SWAY_ERROR, - "Unable to allocate full path to included config"); - return false; - } - snprintf(full_path, len, "%s/%s", parent_dir, path); - } else { - full_path = strdup(path); - } - - char *real_path = realpath(full_path, NULL); - free(full_path); + char *real_path = realpath(path, NULL); if (real_path == NULL) { sway_log(SWAY_DEBUG, "%s not found.", path); @@ -623,7 +610,7 @@ void load_include_configs(const char *path, struct sway_config *config, char **w = p.we_wordv; size_t i; for (i = 0; i < p.we_wordc; ++i) { - load_include_config(w[i], parent_dir, config, swaynag); + load_include_config(w[i], config, swaynag); } wordfree(&p); } @@ -923,8 +910,8 @@ char *do_var_replacement(char *str) { // Find matching variable for (i = 0; i < config->symbols->length; ++i) { struct sway_variable *var = config->symbols->items[i]; - int vnlen = strlen(var->name); - if (strncmp(find, var->name, vnlen) == 0) { + if (has_prefix(find, var->name)) { + int vnlen = strlen(var->name); int vvlen = strlen(var->value); char *newstr = malloc(strlen(str) - vnlen + vvlen + 1); if (!newstr) { diff --git a/sway/config/bar.c b/sway/config/bar.c index 908b28650..f4efb276c 100644 --- a/sway/config/bar.c +++ b/sway/config/bar.c @@ -12,6 +12,7 @@ #include "sway/config.h" #include "sway/input/keyboard.h" #include "sway/output.h" +#include "sway/server.h" #include "config.h" #include "list.h" #include "log.h" @@ -212,36 +213,21 @@ static void invoke_swaybar(struct bar_config *bar) { sway_log(SWAY_ERROR, "Failed to create fork for swaybar"); return; } else if (pid == 0) { - // Remove the SIGUSR1 handler that wlroots adds for xwayland - sigset_t set; - sigemptyset(&set); - sigprocmask(SIG_SETMASK, &set, NULL); - signal(SIGPIPE, SIG_DFL); - - restore_nofile_limit(); - - pid = fork(); - if (pid < 0) { - sway_log_errno(SWAY_ERROR, "fork failed"); - _exit(EXIT_FAILURE); - } else if (pid == 0) { - if (!sway_set_cloexec(sockets[1], false)) { - _exit(EXIT_FAILURE); - } - - char wayland_socket_str[16]; - snprintf(wayland_socket_str, sizeof(wayland_socket_str), - "%d", sockets[1]); - setenv("WAYLAND_SOCKET", wayland_socket_str, true); - - // run custom swaybar - char *const cmd[] = { - bar->swaybar_command ? bar->swaybar_command : "swaybar", - "-b", bar->id, NULL}; - execvp(cmd[0], cmd); + if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } - _exit(EXIT_SUCCESS); + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), + "%d", sockets[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + // run custom swaybar + char *const cmd[] = { + bar->swaybar_command ? bar->swaybar_command : "swaybar", + "-b", bar->id, NULL}; + execvp(cmd[0], cmd); + _exit(EXIT_FAILURE); } if (close(sockets[1]) != 0) { @@ -249,11 +235,6 @@ static void invoke_swaybar(struct bar_config *bar) { return; } - if (waitpid(pid, NULL, 0) < 0) { - sway_log_errno(SWAY_ERROR, "waitpid failed"); - return; - } - sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id); } diff --git a/sway/config/input.c b/sway/config/input.c index de3b21ed4..1fb737b65 100644 --- a/sway/config/input.c +++ b/sway/config/input.c @@ -3,6 +3,7 @@ #include #include "sway/config.h" #include "sway/input/keyboard.h" +#include "sway/server.h" #include "log.h" struct input_config *new_input_config(const char* identifier) { @@ -27,6 +28,7 @@ struct input_config *new_input_config(const char* identifier) { input->dwtp = INT_MIN; input->send_events = INT_MIN; input->click_method = INT_MIN; + input->clickfinger_button_map = INT_MIN; input->middle_emulation = INT_MIN; input->natural_scroll = INT_MIN; input->accel_profile = INT_MIN; @@ -54,6 +56,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) { if (src->click_method != INT_MIN) { dst->click_method = src->click_method; } + if (src->clickfinger_button_map != INT_MIN) { + dst->clickfinger_button_map = src->clickfinger_button_map; + } if (src->drag != INT_MIN) { dst->drag = src->drag; } @@ -295,7 +300,7 @@ struct input_config *store_input_config(struct input_config *ic, return NULL; } - bool type = strncmp(ic->identifier, "type:", strlen("type:")) == 0; + bool type = has_prefix(ic->identifier, "type:"); if (type && error && !validate_type_on_existing(ic, error)) { return NULL; } diff --git a/sway/config/output.c b/sway/config/output.c index 1b2332e95..6d6afdc25 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -6,12 +6,20 @@ #include #include #include +#include #include #include #include +#include +#include #include "sway/config.h" +#include "sway/desktop/transaction.h" #include "sway/input/cursor.h" +#include "sway/layers.h" +#include "sway/lock.h" #include "sway/output.h" +#include "sway/server.h" +#include "sway/tree/arrange.h" #include "sway/tree/root.h" #include "log.h" #include "util.h" @@ -20,13 +28,6 @@ #include #endif -int output_name_cmp(const void *item, const void *data) { - const struct output_config *output = item; - const char *name = data; - - return strcmp(output->name, name); -} - void output_get_identifier(char *identifier, size_t len, struct sway_output *output) { struct wlr_output *wlr_output = output->wlr_output; @@ -66,7 +67,7 @@ struct output_config *new_output_config(const char *name) { oc->refresh_rate = -1; oc->custom_mode = -1; oc->drm_mode.type = -1; - oc->x = oc->y = -1; + oc->x = oc->y = INT_MAX; oc->scale = -1; oc->scale_filter = SCALE_FILTER_DEFAULT; oc->transform = -1; @@ -74,11 +75,93 @@ struct output_config *new_output_config(const char *name) { oc->max_render_time = -1; oc->adaptive_sync = -1; oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; + oc->color_profile = COLOR_PROFILE_DEFAULT; + oc->color_transform = NULL; oc->power = -1; + oc->allow_tearing = -1; + oc->hdr = -1; return oc; } -void merge_output_config(struct output_config *dst, struct output_config *src) { +// supersede_output_config clears all fields in dst that were set in src +static void supersede_output_config(struct output_config *dst, struct output_config *src) { + if (src->enabled != -1) { + dst->enabled = -1; + } + if (src->width != -1) { + dst->width = -1; + } + if (src->height != -1) { + dst->height = -1; + } + if (src->x != INT_MAX) { + dst->x = INT_MAX; + } + if (src->y != INT_MAX) { + dst->y = INT_MAX; + } + if (src->scale != -1) { + dst->scale = -1; + } + if (src->scale_filter != SCALE_FILTER_DEFAULT) { + dst->scale_filter = SCALE_FILTER_DEFAULT; + } + if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { + dst->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + } + if (src->refresh_rate != -1) { + dst->refresh_rate = -1; + } + if (src->custom_mode != -1) { + dst->custom_mode = -1; + } + if (src->drm_mode.type != (uint32_t) -1) { + dst->drm_mode.type = -1; + } + if (src->transform != -1) { + dst->transform = -1; + } + if (src->max_render_time != -1) { + dst->max_render_time = -1; + } + if (src->adaptive_sync != -1) { + dst->adaptive_sync = -1; + } + if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { + dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT; + } + if (src->color_profile != COLOR_PROFILE_DEFAULT) { + if (dst->color_transform) { + wlr_color_transform_unref(dst->color_transform); + dst->color_transform = NULL; + } + dst->color_profile = COLOR_PROFILE_DEFAULT; + } + if (src->background) { + free(dst->background); + dst->background = NULL; + } + if (src->background_option) { + free(dst->background_option); + dst->background_option = NULL; + } + if (src->background_fallback) { + free(dst->background_fallback); + dst->background_fallback = NULL; + } + if (src->power != -1) { + dst->power = -1; + } + if (src->allow_tearing != -1) { + dst->allow_tearing = -1; + } + if (src->hdr != -1) { + dst->hdr = -1; + } +} + +// merge_output_config sets all fields in dst that were set in src +static void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->enabled != -1) { dst->enabled = src->enabled; } @@ -88,10 +171,10 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->height != -1) { dst->height = src->height; } - if (src->x != -1) { + if (src->x != INT_MAX) { dst->x = src->x; } - if (src->y != -1) { + if (src->y != INT_MAX) { dst->y = src->y; } if (src->scale != -1) { @@ -124,6 +207,14 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { dst->render_bit_depth = src->render_bit_depth; } + if (src->color_profile != COLOR_PROFILE_DEFAULT) { + if (src->color_transform) { + wlr_color_transform_ref(src->color_transform); + } + wlr_color_transform_unref(dst->color_transform); + dst->color_profile = src->color_profile; + dst->color_transform = src->color_transform; + } if (src->background) { free(dst->background); dst->background = strdup(src->background); @@ -139,107 +230,67 @@ void merge_output_config(struct output_config *dst, struct output_config *src) { if (src->power != -1) { dst->power = src->power; } -} - -static void merge_wildcard_on_all(struct output_config *wildcard) { - for (int i = 0; i < config->output_configs->length; i++) { - struct output_config *oc = config->output_configs->items[i]; - if (strcmp(wildcard->name, oc->name) != 0) { - sway_log(SWAY_DEBUG, "Merging output * config on %s", oc->name); - merge_output_config(oc, wildcard); - } + if (src->allow_tearing != -1) { + dst->allow_tearing = src->allow_tearing; + } + if (src->hdr != -1) { + dst->hdr = src->hdr; } } -static void merge_id_on_name(struct output_config *oc) { - struct sway_output *output = all_output_by_name_or_id(oc->name); - if (output == NULL) { - return; - } - - const char *name = output->wlr_output->name; - char id[128]; - output_get_identifier(id, sizeof(id), output); - - char *id_on_name = format_str("%s on %s", id, name); - if (!id_on_name) { - return; - } - - int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); - if (i >= 0) { - sway_log(SWAY_DEBUG, "Merging on top of existing id on name config"); - merge_output_config(config->output_configs->items[i], oc); - } else { - // If both a name and identifier config, exist generate an id on name - int ni = list_seq_find(config->output_configs, output_name_cmp, name); - int ii = list_seq_find(config->output_configs, output_name_cmp, id); - if ((ni >= 0 && ii >= 0) || (ni >= 0 && strcmp(oc->name, id) == 0) - || (ii >= 0 && strcmp(oc->name, name) == 0)) { - struct output_config *ion_oc = new_output_config(id_on_name); - if (ni >= 0) { - merge_output_config(ion_oc, config->output_configs->items[ni]); - } - if (ii >= 0) { - merge_output_config(ion_oc, config->output_configs->items[ii]); - } - merge_output_config(ion_oc, oc); - list_add(config->output_configs, ion_oc); - sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" - " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " - "transform %d) (bg %s %s) (power %d) (max render time: %d)", - ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height, - ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, - ion_oc->transform, ion_oc->background, - ion_oc->background_option, ion_oc->power, - ion_oc->max_render_time); - } - } - free(id_on_name); -} - -struct output_config *store_output_config(struct output_config *oc) { +void store_output_config(struct output_config *oc) { + bool merged = false; bool wildcard = strcmp(oc->name, "*") == 0; - if (wildcard) { - merge_wildcard_on_all(oc); - } else { - merge_id_on_name(oc); + struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name); + + char id[128]; + if (output) { + output_get_identifier(id, sizeof(id), output); } - int i = list_seq_find(config->output_configs, output_name_cmp, oc->name); - if (i >= 0) { - sway_log(SWAY_DEBUG, "Merging on top of existing output config"); - struct output_config *current = config->output_configs->items[i]; - merge_output_config(current, oc); - free_output_config(oc); - oc = current; - } else if (!wildcard) { - sway_log(SWAY_DEBUG, "Adding non-wildcard output config"); - i = list_seq_find(config->output_configs, output_name_cmp, "*"); - if (i >= 0) { - sway_log(SWAY_DEBUG, "Merging on top of output * config"); - struct output_config *current = new_output_config(oc->name); - merge_output_config(current, config->output_configs->items[i]); - merge_output_config(current, oc); - free_output_config(oc); - oc = current; + for (int i = 0; i < config->output_configs->length; i++) { + struct output_config *old = config->output_configs->items[i]; + + // If the old config matches the new config's name, regardless of + // whether it was name or identifier, merge on top of the existing + // config. If the new config is a wildcard, this also merges on top of + // old wildcard configs. + if (strcmp(old->name, oc->name) == 0) { + merge_output_config(old, oc); + merged = true; + continue; + } + + // If the new config is a wildcard config we supersede all non-wildcard + // configs. Old wildcard configs have already been handled above. + if (wildcard) { + supersede_output_config(old, oc); + continue; + } + + // If the new config matches an output's name, and the old config + // matches on that output's identifier, supersede it. + if (output && strcmp(old->name, id) == 0 && + strcmp(oc->name, output->wlr_output->name) == 0) { + supersede_output_config(old, oc); } - list_add(config->output_configs, oc); - } else { - // New wildcard config. Just add it - sway_log(SWAY_DEBUG, "Adding output * config"); - list_add(config->output_configs, oc); } sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) " - "(max render time: %d)", + "(max render time: %d) (allow tearing: %d) (hdr: %d)", oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), oc->transform, oc->background, oc->background_option, oc->power, - oc->max_render_time); + oc->max_render_time, oc->allow_tearing, oc->hdr); - return oc; + // If the configuration was not merged into an existing configuration, add + // it to the list. Otherwise we're done with it and can free it. + if (!merged) { + list_add(config->output_configs, oc); + } else { + free_output_config(oc); + } } static void set_mode(struct wlr_output *output, struct wlr_output_state *pending, @@ -252,7 +303,6 @@ static void set_mode(struct wlr_output *output, struct wlr_output_state *pending mhz = mhz <= 0 ? INT_MAX : mhz; if (wl_list_empty(&output->modes) || custom) { - sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name); wlr_output_state_set_custom_mode(pending, width, height, refresh_rate > 0 ? mhz : 0); return; @@ -272,10 +322,7 @@ static void set_mode(struct wlr_output *output, struct wlr_output_state *pending } } } - if (best) { - sway_log(SWAY_INFO, "Assigning configured mode (%dx%d@%.3fHz) to %s", - best->width, best->height, best->refresh / 1000.f, output->name); - } else { + if (!best) { best = wlr_output_preferred_mode(output); sway_log(SWAY_INFO, "Configured mode (%dx%d@%.3fHz) not available, " "applying preferred mode (%dx%d@%.3fHz)", @@ -292,7 +339,6 @@ static void set_modeline(struct wlr_output *output, sway_log(SWAY_ERROR, "Modeline can only be set to DRM output"); return; } - sway_log(SWAY_DEBUG, "Assigning custom modeline to %s", output->name); struct wlr_output_mode *mode = wlr_drm_connector_add_mode(output, drm_mode); if (mode) { wlr_output_state_set_mode(pending, mode); @@ -302,6 +348,45 @@ static void set_modeline(struct wlr_output *output, #endif } +bool output_supports_hdr(struct wlr_output *output, const char **unsupported_reason_ptr) { + const char *unsupported_reason = NULL; + if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) { + unsupported_reason = "BT2020 primaries not supported by output"; + } else if (!(output->supported_transfer_functions & WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) { + unsupported_reason = "PQ transfer function not supported by output"; + } else if (!server.renderer->features.output_color_transform) { + unsupported_reason = "renderer doesn't support output color transforms"; + } + if (unsupported_reason_ptr != NULL) { + *unsupported_reason_ptr = unsupported_reason; + } + return unsupported_reason == NULL; +} + +static void set_hdr(struct wlr_output *output, struct wlr_output_state *pending, bool enabled) { + const char *unsupported_reason = NULL; + if (enabled && !output_supports_hdr(output, &unsupported_reason)) { + sway_log(SWAY_ERROR, "Cannot enable HDR on output %s: %s", + output->name, unsupported_reason); + enabled = false; + } + + if (!enabled) { + if (output->supported_primaries != 0 || output->supported_transfer_functions != 0) { + sway_log(SWAY_DEBUG, "Disabling HDR on output %s", output->name); + wlr_output_state_set_image_description(pending, NULL); + } + return; + } + + sway_log(SWAY_DEBUG, "Enabling HDR on output %s", output->name); + const struct wlr_output_image_description image_desc = { + .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, + }; + wlr_output_state_set_image_description(pending, &image_desc); +} + /* Some manufacturers hardcode the aspect-ratio of the output in the physical * size field. */ static bool phys_size_is_aspect_ratio(struct wlr_output *output) { @@ -358,7 +443,6 @@ static int compute_default_scale(struct wlr_output *output, double dpi_x = (double) width / (output->phys_width / MM_PER_INCH); double dpi_y = (double) height / (output->phys_height / MM_PER_INCH); - sway_log(SWAY_DEBUG, "Output DPI: %fx%f", dpi_x, dpi_y); if (dpi_x <= HIDPI_DPI_LIMIT || dpi_y <= HIDPI_DPI_LIMIT) { return 1; } @@ -366,22 +450,34 @@ static int compute_default_scale(struct wlr_output *output, return 2; } -/* Lists of formats to try, in order, when a specific render bit depth has - * been asked for. The second to last format in each list should always - * be XRGB8888, as a reliable backup in case the others are not available; - * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */ -static const uint32_t *bit_depth_preferences[] = { - [RENDER_BIT_DEPTH_8] = (const uint32_t []){ - DRM_FORMAT_XRGB8888, - DRM_FORMAT_INVALID, - }, - [RENDER_BIT_DEPTH_10] = (const uint32_t []){ - DRM_FORMAT_XRGB2101010, - DRM_FORMAT_XBGR2101010, - DRM_FORMAT_XRGB8888, - DRM_FORMAT_INVALID, - }, -}; +static enum render_bit_depth bit_depth_from_format(uint32_t render_format) { + if (render_format == DRM_FORMAT_XRGB2101010 || render_format == DRM_FORMAT_XBGR2101010) { + return RENDER_BIT_DEPTH_10; + } else if (render_format == DRM_FORMAT_XRGB8888 || render_format == DRM_FORMAT_ARGB8888) { + return RENDER_BIT_DEPTH_8; + } else if (render_format == DRM_FORMAT_RGB565) { + return RENDER_BIT_DEPTH_6; + } + return RENDER_BIT_DEPTH_DEFAULT; +} + +static enum render_bit_depth get_config_render_bit_depth(const struct output_config *oc) { + if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { + return oc->render_bit_depth; + } + if (oc && oc->hdr == 1) { + return RENDER_BIT_DEPTH_10; + } + return RENDER_BIT_DEPTH_8; +} + +static bool render_format_is_bgr(uint32_t fmt) { + return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888; +} + +static bool output_config_is_disabling(struct output_config *oc) { + return oc && (!oc->enabled || oc->power == 0); +} static void queue_output_config(struct output_config *oc, struct sway_output *output, struct wlr_output_state *pending) { @@ -391,137 +487,157 @@ static void queue_output_config(struct output_config *oc, struct wlr_output *wlr_output = output->wlr_output; - if (oc && (!oc->enabled || oc->power == 0)) { - sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); + if (output_config_is_disabling(oc)) { wlr_output_state_set_enabled(pending, false); return; } - - sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name); wlr_output_state_set_enabled(pending, true); if (oc && oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t) -1) { - sway_log(SWAY_DEBUG, "Set %s modeline", - wlr_output->name); set_modeline(wlr_output, pending, &oc->drm_mode); } else if (oc && oc->width > 0 && oc->height > 0) { - sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)", - wlr_output->name, oc->width, oc->height, oc->refresh_rate); set_mode(wlr_output, pending, oc->width, oc->height, oc->refresh_rate, oc->custom_mode == 1); } else if (!wl_list_empty(&wlr_output->modes)) { - sway_log(SWAY_DEBUG, "Set preferred mode"); struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); wlr_output_state_set_mode(pending, preferred_mode); - - if (!wlr_output_test_state(wlr_output, pending)) { - sway_log(SWAY_DEBUG, "Preferred mode rejected, " - "falling back to another mode"); - struct wlr_output_mode *mode; - wl_list_for_each(mode, &wlr_output->modes, link) { - if (mode == preferred_mode) { - continue; - } - - wlr_output_state_set_mode(pending, mode); - if (wlr_output_test_state(wlr_output, pending)) { - break; - } - } - } } - if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { - sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name, - sway_wl_output_subpixel_to_string(oc->subpixel)); + if (oc && oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) { wlr_output_state_set_subpixel(pending, oc->subpixel); + } else { + wlr_output_state_set_subpixel(pending, output->detected_subpixel); } - enum wl_output_transform tr = WL_OUTPUT_TRANSFORM_NORMAL; if (oc && oc->transform >= 0) { - tr = oc->transform; + wlr_output_state_set_transform(pending, oc->transform); #if WLR_HAS_DRM_BACKEND } else if (wlr_output_is_drm(wlr_output)) { - tr = wlr_drm_connector_get_panel_orientation(wlr_output); - sway_log(SWAY_DEBUG, "Auto-detected output transform: %d", tr); + wlr_output_state_set_transform(pending, + wlr_drm_connector_get_panel_orientation(wlr_output)); #endif - } - if (wlr_output->transform != tr) { - sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, tr); - wlr_output_state_set_transform(pending, tr); + } else { + wlr_output_state_set_transform(pending, WL_OUTPUT_TRANSFORM_NORMAL); } - // Apply the scale last before the commit, because the scale auto-detection - // reads the pending output size - float scale; + // Apply the scale after sorting out the mode, because the scale + // auto-detection reads the pending output size if (oc && oc->scale > 0) { - scale = oc->scale; - // The factional-scale-v1 protocol uses increments of 120ths to send // the scale factor to the client. Adjust the scale so that we use the // same value as the clients'. - float adjusted_scale = round(scale * 120) / 120; - if (scale != adjusted_scale) { - sway_log(SWAY_INFO, "Adjusting output scale from %f to %f", - scale, adjusted_scale); - scale = adjusted_scale; - } + wlr_output_state_set_scale(pending, round(oc->scale * 120) / 120); } else { - scale = compute_default_scale(wlr_output, pending); - sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale); - } - if (scale != wlr_output->scale) { - sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale); - wlr_output_state_set_scale(pending, scale); + wlr_output_state_set_scale(pending, + compute_default_scale(wlr_output, pending)); } - if (oc && oc->adaptive_sync != -1) { - sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, - oc->adaptive_sync); - wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); - if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) { - sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring"); + if (wlr_output->adaptive_sync_supported) { + if (oc && oc->adaptive_sync != -1) { + wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); + } else { wlr_output_state_set_adaptive_sync_enabled(pending, false); } } - if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { - const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth]; - assert(fmts); + enum render_bit_depth render_bit_depth = get_config_render_bit_depth(oc); + if (render_bit_depth == RENDER_BIT_DEPTH_10 && + bit_depth_from_format(output->wlr_output->render_format) == render_bit_depth) { + // 10-bit was set successfully before, try to save some tests by reusing the format + wlr_output_state_set_render_format(pending, output->wlr_output->render_format); + } else if (render_bit_depth == RENDER_BIT_DEPTH_10) { + wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010); + } else if (render_bit_depth == RENDER_BIT_DEPTH_6) { + wlr_output_state_set_render_format(pending, DRM_FORMAT_RGB565); + } else { + wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888); + } - for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) { - wlr_output_state_set_render_format(pending, fmts[i]); - if (wlr_output_test_state(wlr_output, pending)) { - break; - } + bool hdr = oc && oc->hdr == 1; + bool color_profile = oc && (oc->color_transform != NULL + || oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES); + if (hdr && color_profile) { + sway_log(SWAY_ERROR, "Cannot use HDR on output %s: output has a color profile set", wlr_output->name); + hdr = false; + } + set_hdr(wlr_output, pending, hdr); +} - sway_log(SWAY_DEBUG, "Preferred output format 0x%08x " - "failed to work, falling back to next in " - "list, 0x%08x", fmts[i], fmts[i + 1]); +struct config_output_state { + struct wlr_color_transform *color_transform; +}; + +static void config_output_state_finish(struct config_output_state *state) { + wlr_color_transform_unref(state->color_transform); +} + +static struct wlr_color_transform *color_profile_from_device(struct wlr_output *wlr_output, + struct wlr_color_transform *transfer_function) { + struct wlr_color_primaries srgb_primaries; + wlr_color_primaries_from_named(&srgb_primaries, WLR_COLOR_NAMED_PRIMARIES_SRGB); + + const struct wlr_color_primaries *primaries = wlr_output->default_primaries; + if (primaries == NULL) { + sway_log(SWAY_INFO, "output has no reported color information"); + if (transfer_function) { + wlr_color_transform_ref(transfer_function); } + return transfer_function; + } else if (memcmp(primaries, &srgb_primaries, sizeof(*primaries)) == 0) { + sway_log(SWAY_INFO, "output reports sRGB colors, no correction needed"); + if (transfer_function) { + wlr_color_transform_ref(transfer_function); + } + return transfer_function; + } else { + sway_log(SWAY_INFO, "Creating color profile from reported color primaries: " + "R(%f, %f) G(%f, %f) B(%f, %f) W(%f, %f)", + primaries->red.x, primaries->red.y, primaries->green.x, primaries->green.y, + primaries->blue.x, primaries->blue.y, primaries->white.x, primaries->white.y); + float matrix[9]; + wlr_color_primaries_transform_absolute_colorimetric(&srgb_primaries, primaries, matrix); + struct wlr_color_transform *matrix_transform = wlr_color_transform_init_matrix(matrix); + if (matrix_transform == NULL) { + return NULL; + } + struct wlr_color_transform *resolved_tf = transfer_function ? + wlr_color_transform_ref(transfer_function) : + wlr_color_transform_init_linear_to_inverse_eotf(WLR_COLOR_TRANSFER_FUNCTION_GAMMA22); + if (resolved_tf == NULL) { + wlr_color_transform_unref(matrix_transform); + return NULL; + } + struct wlr_color_transform *transforms[] = { matrix_transform, resolved_tf }; + size_t transforms_len = sizeof(transforms) / sizeof(transforms[0]); + struct wlr_color_transform *result = wlr_color_transform_init_pipeline(transforms, transforms_len); + wlr_color_transform_unref(matrix_transform); + wlr_color_transform_unref(resolved_tf); + return result; } } -bool apply_output_config(struct output_config *oc, struct sway_output *output) { +static struct wlr_color_transform *get_color_profile(struct wlr_output *output, + struct output_config *oc) { + if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM) { + if (oc->color_transform) { + wlr_color_transform_ref(oc->color_transform); + } + return oc->color_transform; + } else if (oc && oc->color_profile == COLOR_PROFILE_TRANSFORM_WITH_DEVICE_PRIMARIES) { + return color_profile_from_device(output, oc->color_transform); + } else { + return NULL; + } +} + +static bool finalize_output_config(struct output_config *oc, struct sway_output *output, + const struct wlr_output_state *applied, const struct config_output_state *config_applied) { if (output == root->fallback_output) { return false; } struct wlr_output *wlr_output = output->wlr_output; - - struct wlr_output_state pending = {0}; - queue_output_config(oc, output, &pending); - - sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name); - if (!wlr_output_commit_state(wlr_output, &pending)) { - // Failed to commit output changes, maybe the output is missing a CRTC. - // Leave the output disabled for now and try again when the output gets - // the mode we asked for. - sway_log(SWAY_ERROR, "Failed to commit output %s", wlr_output->name); - return false; - } - if (oc && !oc->enabled) { sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); if (output->enabled) { @@ -531,51 +647,463 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { return true; } - if (oc) { - enum scale_filter_mode scale_filter_old = output->scale_filter; - switch (oc->scale_filter) { - case SCALE_FILTER_DEFAULT: - case SCALE_FILTER_SMART: - output->scale_filter = ceilf(wlr_output->scale) == wlr_output->scale ? - SCALE_FILTER_NEAREST : SCALE_FILTER_LINEAR; - break; - case SCALE_FILTER_LINEAR: - case SCALE_FILTER_NEAREST: - output->scale_filter = oc->scale_filter; - break; - } - if (scale_filter_old != output->scale_filter) { - sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, - sway_output_scale_filter_to_string(output->scale_filter)); - wlr_damage_ring_add_whole(&output->scene_output->damage_ring); - } + enum scale_filter_mode scale_filter_old = output->scale_filter; + enum scale_filter_mode scale_filter_new = oc ? oc->scale_filter : SCALE_FILTER_DEFAULT; + switch (scale_filter_new) { + case SCALE_FILTER_DEFAULT: + case SCALE_FILTER_SMART: + output->scale_filter = ceilf(wlr_output->scale) == wlr_output->scale ? + SCALE_FILTER_NEAREST : SCALE_FILTER_LINEAR; + break; + case SCALE_FILTER_LINEAR: + case SCALE_FILTER_NEAREST: + output->scale_filter = scale_filter_new; + break; + } + if (scale_filter_old != output->scale_filter) { + sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, + sway_output_scale_filter_to_string(output->scale_filter)); + wlr_damage_ring_add_whole(&output->scene_output->damage_ring); } // Find position for it - if (oc && (oc->x != -1 || oc->y != -1)) { + if (oc && oc->x != INT_MAX && oc->y != INT_MAX) { sway_log(SWAY_DEBUG, "Set %s position to %d, %d", oc->name, oc->x, oc->y); wlr_output_layout_add(root->output_layout, wlr_output, oc->x, oc->y); } else { wlr_output_layout_add_auto(root->output_layout, wlr_output); } - // Update output->{lx, ly, width, height} - struct wlr_box output_box; - wlr_output_layout_get_box(root->output_layout, wlr_output, &output_box); - output->lx = output_box.x; - output->ly = output_box.y; - output->width = output_box.width; - output->height = output_box.height; - if (!output->enabled) { output_enable(output); } - if (oc && oc->max_render_time >= 0) { - sway_log(SWAY_DEBUG, "Set %s max render time to %d", - oc->name, oc->max_render_time); - output->max_render_time = oc->max_render_time; + wlr_color_transform_unref(output->color_transform); + if (config_applied->color_transform != NULL) { + wlr_color_transform_ref(config_applied->color_transform); } + output->color_transform = config_applied->color_transform; + + output->max_render_time = oc && oc->max_render_time > 0 ? oc->max_render_time : 0; + output->allow_tearing = oc && oc->allow_tearing > 0; + output->hdr = applied->image_description != NULL; + + return true; +} + +static void output_update_position(struct sway_output *output) { + struct wlr_box output_box; + wlr_output_layout_get_box(root->output_layout, output->wlr_output, &output_box); + output->lx = output_box.x; + output->ly = output_box.y; + output->width = output_box.width; + output->height = output_box.height; +} + +// find_output_config_from_list returns a merged output_config containing all +// stored configuration that applies to the specified output. +static struct output_config *find_output_config_from_list( + struct output_config **configs, size_t configs_len, + struct sway_output *sway_output) { + const char *name = sway_output->wlr_output->name; + struct output_config *result = new_output_config(name); + if (result == NULL) { + return NULL; + } + + char id[128]; + output_get_identifier(id, sizeof(id), sway_output); + + // We take a new config and merge on top, in order, the wildcard config, + // output config by name, and output config by identifier to form the final + // config. If there are multiple matches, they are merged in order. + struct output_config *oc = NULL; + const char *names[] = {"*", name, id, NULL}; + for (const char **name = &names[0]; *name; name++) { + for (size_t idx = 0; idx < configs_len; idx++) { + oc = configs[idx]; + if (strcmp(oc->name, *name) == 0) { + merge_output_config(result, oc); + } + } + } + + return result; +} + +struct output_config *find_output_config(struct sway_output *sway_output) { + return find_output_config_from_list( + (struct output_config **)config->output_configs->items, + config->output_configs->length, sway_output); +} + +static bool config_has_manual_mode(struct output_config *oc) { + if (!oc) { + return false; + } + if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) { + return true; + } else if (oc->width > 0 && oc->height > 0) { + return true; + } + return false; +} + +/** + * An output config pre-matched to an output + */ +struct matched_output_config { + struct sway_output *output; + struct output_config *config; +}; + +struct search_context { + struct wlr_output_swapchain_manager *swapchain_mgr; + struct wlr_backend_output_state *states; + struct matched_output_config *configs; + size_t configs_len; + bool degrade_to_off; +}; + +static void dump_output_state(struct wlr_output *wlr_output, struct wlr_output_state *state) { + sway_log(SWAY_DEBUG, "Output state for %s", wlr_output->name); + if (state->committed & WLR_OUTPUT_STATE_ENABLED) { + sway_log(SWAY_DEBUG, " enabled: %s", state->enabled ? "yes" : "no"); + } + if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { + char *format_name = drmGetFormatName(state->render_format); + sway_log(SWAY_DEBUG, " render_format: %s", format_name); + free(format_name); + } + if (state->committed & WLR_OUTPUT_STATE_MODE) { + if (state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { + sway_log(SWAY_DEBUG, " custom mode: %dx%d@%dmHz", + state->custom_mode.width, state->custom_mode.height, state->custom_mode.refresh); + } else { + sway_log(SWAY_DEBUG, " mode: %dx%d@%dmHz%s", + state->mode->width, state->mode->height, state->mode->refresh, + state->mode->preferred ? " (preferred)" : ""); + } + } + if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { + sway_log(SWAY_DEBUG, " adaptive_sync: %s", + state->adaptive_sync_enabled ? "enabled": "disabled"); + } + if (state->committed & WLR_OUTPUT_STATE_SCALE) { + sway_log(SWAY_DEBUG, " scale: %f", state->scale); + } + if (state->committed & WLR_OUTPUT_STATE_SUBPIXEL) { + sway_log(SWAY_DEBUG, " subpixel: %s", + sway_wl_output_subpixel_to_string(state->subpixel)); + } +} + +static bool search_valid_config(struct search_context *ctx, size_t output_idx); + +static void reset_output_state(struct wlr_output_state *state) { + wlr_output_state_finish(state); + wlr_output_state_init(state); + state->committed = 0; +} + +static void clear_later_output_states(struct wlr_backend_output_state *states, + size_t configs_len, size_t output_idx) { + + // Clear and disable all output states after this one to avoid conflict + // with previous tests. + for (size_t idx = output_idx+1; idx < configs_len; idx++) { + struct wlr_backend_output_state *backend_state = &states[idx]; + struct wlr_output_state *state = &backend_state->base; + + reset_output_state(state); + wlr_output_state_set_enabled(state, false); + } +} + +static bool search_finish(struct search_context *ctx, size_t output_idx) { + struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; + struct wlr_output_state *state = &backend_state->base; + struct wlr_output *wlr_output = backend_state->output; + + clear_later_output_states(ctx->states, ctx->configs_len, output_idx); + dump_output_state(wlr_output, state); + return wlr_output_swapchain_manager_prepare(ctx->swapchain_mgr, ctx->states, ctx->configs_len) && + search_valid_config(ctx, output_idx+1); +} + +static bool search_adaptive_sync(struct search_context *ctx, size_t output_idx) { + struct matched_output_config *cfg = &ctx->configs[output_idx]; + struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; + struct wlr_output_state *state = &backend_state->base; + + if (!backend_state->output->adaptive_sync_supported) { + return search_finish(ctx, output_idx); + } + + if (cfg->config && cfg->config->adaptive_sync == 1) { + wlr_output_state_set_adaptive_sync_enabled(state, true); + if (search_finish(ctx, output_idx)) { + return true; + } + } + + wlr_output_state_set_adaptive_sync_enabled(state, false); + return search_finish(ctx, output_idx); +} + +static bool search_mode(struct search_context *ctx, size_t output_idx) { + struct matched_output_config *cfg = &ctx->configs[output_idx]; + struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; + struct wlr_output_state *state = &backend_state->base; + struct wlr_output *wlr_output = backend_state->output; + + // We only search for mode if one is not explicitly specified in the config + if (config_has_manual_mode(cfg->config)) { + return search_adaptive_sync(ctx, output_idx); + } + + struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); + if (preferred_mode) { + wlr_output_state_set_mode(state, preferred_mode); + if (search_adaptive_sync(ctx, output_idx)) { + return true; + } + } + + if (wl_list_empty(&wlr_output->modes)) { + state->committed &= ~WLR_OUTPUT_STATE_MODE; + return search_adaptive_sync(ctx, output_idx); + } + + struct wlr_output_mode *mode; + wl_list_for_each(mode, &backend_state->output->modes, link) { + if (mode == preferred_mode) { + continue; + } + wlr_output_state_set_mode(state, mode); + if (search_adaptive_sync(ctx, output_idx)) { + return true; + } + } + + return false; +} + +static bool search_render_format(struct search_context *ctx, size_t output_idx) { + struct matched_output_config *cfg = &ctx->configs[output_idx]; + struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; + struct wlr_output_state *state = &backend_state->base; + struct wlr_output *wlr_output = backend_state->output; + + uint32_t fmts[] = { + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_RGB565, + DRM_FORMAT_INVALID, + }; + if (render_format_is_bgr(wlr_output->render_format)) { + // Start with BGR in the unlikely event that we previously required it. + fmts[0] = DRM_FORMAT_XBGR2101010; + fmts[1] = DRM_FORMAT_XRGB2101010; + } + + const struct wlr_drm_format_set *primary_formats = + wlr_output_get_primary_formats(wlr_output, server.allocator->buffer_caps); + enum render_bit_depth needed_bits = get_config_render_bit_depth(cfg->config); + for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) { + enum render_bit_depth format_bits = bit_depth_from_format(fmts[idx]); + if (needed_bits < format_bits) { + continue; + } + // If primary_formats is NULL, all formats are supported + if (primary_formats && !wlr_drm_format_set_get(primary_formats, fmts[idx])) { + // This is not a supported format for this output + continue; + } + wlr_output_state_set_render_format(state, fmts[idx]); + if (search_mode(ctx, output_idx)) { + return true; + } + } + return false; +} + +static bool search_valid_config(struct search_context *ctx, size_t output_idx) { + if (output_idx >= ctx->configs_len) { + // We reached the end of the search, all good! + return true; + } + + struct matched_output_config *cfg = &ctx->configs[output_idx]; + struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; + struct wlr_output_state *state = &backend_state->base; + struct wlr_output *wlr_output = backend_state->output; + + if (!output_config_is_disabling(cfg->config)) { + // Search through our possible configurations, doing a depth-first + // through render_format, modes, adaptive_sync and the next output's + // config. + queue_output_config(cfg->config, cfg->output, &backend_state->base); + if (search_render_format(ctx, output_idx)) { + return true; + } else if (!ctx->degrade_to_off) { + return false; + } + // We could not get anything to work, try to disable this output to see + // if we can at least make the outputs before us work. + sway_log(SWAY_DEBUG, "Unable to find valid config with output %s, disabling", + wlr_output->name); + reset_output_state(state); + } + + wlr_output_state_set_enabled(state, false); + return search_finish(ctx, output_idx); +} + +static int compare_matched_output_config_priority(const void *a, const void *b) { + + const struct matched_output_config *amc = a; + const struct matched_output_config *bmc = b; + bool a_disabling = output_config_is_disabling(amc->config); + bool b_disabling = output_config_is_disabling(bmc->config); + bool a_enabled = amc->output->enabled; + bool b_enabled = bmc->output->enabled; + + // We want to give priority to existing enabled outputs. To do so, we want + // the configuration order to be: + // 1. Existing, enabled outputs + // 2. Outputs that need to be enabled + // 3. Disabled or disabling outputs + if (a_enabled && !a_disabling) { + return -1; + } else if (b_enabled && !b_disabling) { + return 1; + } else if (b_disabling && !a_disabling) { + return -1; + } else if (a_disabling && !b_disabling) { + return 1; + } + return 0; +} + +static void sort_output_configs_by_priority( + struct matched_output_config *configs, size_t configs_len) { + qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority); +} + +static bool apply_resolved_output_configs(struct matched_output_config *configs, + size_t configs_len, bool test_only, bool degrade_to_off) { + struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); + if (!states) { + return false; + } + struct config_output_state *config_states = calloc(configs_len, sizeof(*config_states)); + if (!config_states) { + free(states); + return false; + } + + sway_log(SWAY_DEBUG, "Committing %zd outputs", configs_len); + for (size_t idx = 0; idx < configs_len; idx++) { + struct matched_output_config *cfg = &configs[idx]; + struct wlr_backend_output_state *backend_state = &states[idx]; + struct config_output_state *config_state = &config_states[idx]; + + backend_state->output = cfg->output->wlr_output; + wlr_output_state_init(&backend_state->base); + + queue_output_config(cfg->config, cfg->output, &backend_state->base); + dump_output_state(cfg->output->wlr_output, &backend_state->base); + + config_state->color_transform = get_color_profile(cfg->output->wlr_output, cfg->config); + } + + struct wlr_output_swapchain_manager swapchain_mgr; + wlr_output_swapchain_manager_init(&swapchain_mgr, server.backend); + + bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); + if (!ok) { + sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks"); + struct search_context ctx = { + .swapchain_mgr = &swapchain_mgr, + .states = states, + .configs = configs, + .configs_len = configs_len, + .degrade_to_off = degrade_to_off, + }; + if (!search_valid_config(&ctx, 0)) { + sway_log(SWAY_ERROR, "Search for valid config failed"); + goto out; + } + } + + if (test_only) { + // The swapchain manager already did a test for us + goto out; + } + + for (size_t idx = 0; idx < configs_len; idx++) { + struct matched_output_config *cfg = &configs[idx]; + struct wlr_backend_output_state *backend_state = &states[idx]; + struct config_output_state *config_state = &config_states[idx]; + + struct wlr_scene_output_state_options opts = { + .swapchain = wlr_output_swapchain_manager_get_swapchain( + &swapchain_mgr, backend_state->output), + .color_transform = config_state->color_transform, + }; + struct wlr_scene_output *scene_output = cfg->output->scene_output; + struct wlr_output_state *state = &backend_state->base; + if (!wlr_scene_output_build_state(scene_output, state, &opts)) { + sway_log(SWAY_ERROR, "Building output state for '%s' failed", + backend_state->output->name); + goto out; + } + } + + ok = wlr_backend_commit(server.backend, states, configs_len); + if (!ok) { + sway_log(SWAY_ERROR, "Backend commit failed"); + goto out; + } + + sway_log(SWAY_DEBUG, "Commit of %zd outputs succeeded", configs_len); + + wlr_output_swapchain_manager_apply(&swapchain_mgr); + + for (size_t idx = 0; idx < configs_len; idx++) { + struct matched_output_config *cfg = &configs[idx]; + struct wlr_backend_output_state *backend_state = &states[idx]; + struct config_output_state *config_state = &config_states[idx]; + sway_log(SWAY_DEBUG, "Finalizing config for %s", + cfg->output->wlr_output->name); + finalize_output_config(cfg->config, cfg->output, &backend_state->base, config_state); + } + + // Output layout being applied in finalize_output_config can shift outputs + // around, so we do a second pass to update positions and arrange. + for (size_t idx = 0; idx < configs_len; idx++) { + struct matched_output_config *cfg = &configs[idx]; + output_update_position(cfg->output); + arrange_layers(cfg->output); + } + + arrange_root(); + arrange_locks(); + update_output_manager_config(&server); + transaction_commit_dirty(); + +out: + wlr_output_swapchain_manager_finish(&swapchain_mgr); + for (size_t idx = 0; idx < configs_len; idx++) { + struct wlr_backend_output_state *backend_state = &states[idx]; + wlr_output_state_finish(&backend_state->base); + config_output_state_finish(&config_states[idx]); + } + free(states); + free(config_states); // Reconfigure all devices, since input config may have been applied before // this output came online, and some config items (like map_to_output) are @@ -583,172 +1111,50 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) { input_manager_configure_all_input_mappings(); // Reconfigure the cursor images, since the scale may have changed. input_manager_configure_xcursor(); - return true; -} - -bool test_output_config(struct output_config *oc, struct sway_output *output) { - if (output == root->fallback_output) { - return false; - } - - struct wlr_output_state pending = {0}; - queue_output_config(oc, output, &pending); - return wlr_output_test_state(output->wlr_output, &pending); -} - -static void default_output_config(struct output_config *oc, - struct wlr_output *wlr_output) { - oc->enabled = 1; - oc->power = 1; - struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); - if (mode != NULL) { - oc->width = mode->width; - oc->height = mode->height; - oc->refresh_rate = mode->refresh / 1000.f; - } - oc->x = oc->y = -1; - oc->scale = 0; // auto - oc->scale_filter = SCALE_FILTER_DEFAULT; - struct sway_output *output = wlr_output->data; - oc->subpixel = output->detected_subpixel; - oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; - oc->max_render_time = 0; -} - -static struct output_config *get_output_config(char *identifier, - struct sway_output *sway_output) { - const char *name = sway_output->wlr_output->name; - - struct output_config *oc_id_on_name = NULL; - struct output_config *oc_name = NULL; - struct output_config *oc_id = NULL; - - char *id_on_name = format_str("%s on %s", identifier, name); - int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); - if (i >= 0) { - oc_id_on_name = config->output_configs->items[i]; - } else { - i = list_seq_find(config->output_configs, output_name_cmp, name); - if (i >= 0) { - oc_name = config->output_configs->items[i]; - } - - i = list_seq_find(config->output_configs, output_name_cmp, identifier); - if (i >= 0) { - oc_id = config->output_configs->items[i]; - } - } - - struct output_config *result = new_output_config("temp"); - if (config->reloading) { - default_output_config(result, sway_output->wlr_output); - } - if (oc_id_on_name) { - // Already have an identifier on name config, use that - free(result->name); - result->name = strdup(id_on_name); - merge_output_config(result, oc_id_on_name); - } else if (oc_name && oc_id) { - // Generate a config named ` on ` which contains a - // merged copy of the identifier on name. This will make sure that both - // identifier and name configs are respected, with identifier getting - // priority - struct output_config *temp = new_output_config(id_on_name); - merge_output_config(temp, oc_name); - merge_output_config(temp, oc_id); - list_add(config->output_configs, temp); - - free(result->name); - result->name = strdup(id_on_name); - merge_output_config(result, temp); - - sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" - " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" - " (power %d) (max render time: %d)", result->name, result->enabled, - result->width, result->height, result->refresh_rate, - result->x, result->y, result->scale, result->transform, - result->background, result->background_option, result->power, - result->max_render_time); - } else if (oc_name) { - // No identifier config, just return a copy of the name config - free(result->name); - result->name = strdup(name); - merge_output_config(result, oc_name); - } else if (oc_id) { - // No name config, just return a copy of the identifier config - free(result->name); - result->name = strdup(identifier); - merge_output_config(result, oc_id); - } else { - i = list_seq_find(config->output_configs, output_name_cmp, "*"); - if (i >= 0) { - // No name or identifier config, but there is a wildcard config - free(result->name); - result->name = strdup("*"); - merge_output_config(result, config->output_configs->items[i]); - } else if (!config->reloading) { - // No name, identifier, or wildcard config. Since we are not - // reloading with defaults, the output config will be empty, so - // just return NULL - free_output_config(result); - result = NULL; - } - } - - free(id_on_name); - return result; -} - -struct output_config *find_output_config(struct sway_output *output) { - char id[128]; - output_get_identifier(id, sizeof(id), output); - return get_output_config(id, output); -} - -void apply_output_config_to_outputs(struct output_config *oc) { - // Try to find the output container and apply configuration now. If - // this is during startup then there will be no container and config - // will be applied during normal "new output" event from wlroots. - bool wildcard = strcmp(oc->name, "*") == 0; - struct sway_output *sway_output, *tmp; - wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) { - if (output_match_name_or_id(sway_output, oc->name)) { - char id[128]; - output_get_identifier(id, sizeof(id), sway_output); - struct output_config *current = get_output_config(id, sway_output); - if (!current) { - // No stored output config matched, apply oc directly - sway_log(SWAY_DEBUG, "Applying oc directly"); - current = new_output_config(oc->name); - merge_output_config(current, oc); - } - apply_output_config(current, sway_output); - free_output_config(current); - - if (!wildcard) { - // Stop looking if the output config isn't applicable to all - // outputs - break; - } - } - } struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); cursor_rebase(seat->cursor); } + + return ok; } -void reset_outputs(void) { - struct output_config *oc = NULL; - int i = list_seq_find(config->output_configs, output_name_cmp, "*"); - if (i >= 0) { - oc = config->output_configs->items[i]; - } else { - oc = store_output_config(new_output_config("*")); +bool apply_output_configs(struct output_config **ocs, size_t ocs_len, + bool test_only, bool degrade_to_off) { + size_t configs_len = wl_list_length(&root->all_outputs); + struct matched_output_config *configs = calloc(configs_len, sizeof(*configs)); + if (!configs) { + return false; } - apply_output_config_to_outputs(oc); + + int config_idx = 0; + struct sway_output *sway_output; + wl_list_for_each(sway_output, &root->all_outputs, link) { + if (sway_output == root->fallback_output) { + configs_len--; + continue; + } + + struct matched_output_config *config = &configs[config_idx++]; + config->output = sway_output; + config->config = find_output_config_from_list(ocs, ocs_len, sway_output); + } + + sort_output_configs_by_priority(configs, configs_len); + bool ok = apply_resolved_output_configs(configs, configs_len, test_only, degrade_to_off); + for (size_t idx = 0; idx < configs_len; idx++) { + struct matched_output_config *cfg = &configs[idx]; + free_output_config(cfg->config); + } + free(configs); + return ok; +} + +void apply_stored_output_configs(void) { + apply_output_configs((struct output_config **)config->output_configs->items, + config->output_configs->length, false, true); } void free_output_config(struct output_config *oc) { @@ -758,6 +1164,8 @@ void free_output_config(struct output_config *oc) { free(oc->name); free(oc->background); free(oc->background_option); + free(oc->background_fallback); + wlr_color_transform_unref(oc->color_transform); free(oc); } @@ -798,42 +1206,27 @@ static bool _spawn_swaybg(char **command) { sway_log_errno(SWAY_ERROR, "fork failed"); return false; } else if (pid == 0) { - restore_nofile_limit(); - - pid = fork(); - if (pid < 0) { - sway_log_errno(SWAY_ERROR, "fork failed"); - _exit(EXIT_FAILURE); - } else if (pid == 0) { - if (!sway_set_cloexec(sockets[1], false)) { - _exit(EXIT_FAILURE); - } - - char wayland_socket_str[16]; - snprintf(wayland_socket_str, sizeof(wayland_socket_str), - "%d", sockets[1]); - setenv("WAYLAND_SOCKET", wayland_socket_str, true); - - execvp(command[0], command); - sway_log_errno(SWAY_ERROR, "failed to execute '%s' " - "(background configuration probably not applied)", - command[0]); + if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } - _exit(EXIT_SUCCESS); + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), + "%d", sockets[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + execvp(command[0], command); + sway_log_errno(SWAY_ERROR, "failed to execute '%s' " + "(background configuration probably not applied)", + command[0]); + _exit(EXIT_FAILURE); } if (close(sockets[1]) != 0) { sway_log_errno(SWAY_ERROR, "close failed"); return false; } - int fork_status = 0; - if (waitpid(pid, &fork_status, 0) < 0) { - sway_log_errno(SWAY_ERROR, "waitpid failed"); - return false; - } - - return WIFEXITED(fork_status) && WEXITSTATUS(fork_status) == EXIT_SUCCESS; + return true; } bool spawn_swaybg(void) { diff --git a/sway/criteria.c b/sway/criteria.c index e16b4fa82..6be6e7042 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -7,6 +7,7 @@ #include "sway/criteria.h" #include "sway/tree/container.h" #include "sway/config.h" +#include "sway/server.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" @@ -22,7 +23,7 @@ bool criteria_is_empty(struct criteria *criteria) { && !criteria->app_id && !criteria->con_mark && !criteria->con_id -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND && !criteria->class && !criteria->id && !criteria->instance @@ -33,7 +34,11 @@ bool criteria_is_empty(struct criteria *criteria) { && !criteria->tiling && !criteria->urgent && !criteria->workspace - && !criteria->pid; + && !criteria->pid + && !criteria->sandbox_engine + && !criteria->sandbox_app_id + && !criteria->sandbox_instance_id + && !criteria->tag; } // The error pointer is used for parsing functions, and saves having to pass it @@ -90,13 +95,17 @@ void criteria_destroy(struct criteria *criteria) { pattern_destroy(criteria->title); pattern_destroy(criteria->shell); pattern_destroy(criteria->app_id); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND pattern_destroy(criteria->class); pattern_destroy(criteria->instance); pattern_destroy(criteria->window_role); #endif pattern_destroy(criteria->con_mark); pattern_destroy(criteria->workspace); + pattern_destroy(criteria->sandbox_engine); + pattern_destroy(criteria->sandbox_app_id); + pattern_destroy(criteria->sandbox_instance_id); + pattern_destroy(criteria->tag); free(criteria->target); free(criteria->cmdlist); free(criteria->raw); @@ -110,7 +119,7 @@ static int regex_cmp(const char *item, const pcre2_code *regex) { return result; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND static bool view_has_window_type(struct sway_view *view, enum atom_name name) { if (view->type != SWAY_VIEW_XWAYLAND) { return false; @@ -187,6 +196,10 @@ static bool criteria_matches_view(struct criteria *criteria, struct sway_container *focus = seat_get_focused_container(seat); struct sway_view *focused = focus ? focus->view : NULL; + if (!view->container) { + return false; + } + if (criteria->title) { const char *title = view_get_title(view); if (!title) { @@ -195,7 +208,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->title->match_type) { case PATTERN_FOCUSED: - if (focused && lenient_strcmp(title, view_get_title(focused))) { + if (!focused || lenient_strcmp(title, view_get_title(focused))) { return false; } break; @@ -215,7 +228,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->shell->match_type) { case PATTERN_FOCUSED: - if (focused && strcmp(shell, view_get_shell(focused))) { + if (!focused || strcmp(shell, view_get_shell(focused))) { return false; } break; @@ -235,7 +248,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->app_id->match_type) { case PATTERN_FOCUSED: - if (focused && lenient_strcmp(app_id, view_get_app_id(focused))) { + if (!focused || lenient_strcmp(app_id, view_get_app_id(focused))) { return false; } break; @@ -247,11 +260,91 @@ static bool criteria_matches_view(struct criteria *criteria, } } + if (criteria->sandbox_engine) { + const char *sandbox_engine = view_get_sandbox_engine(view); + if (!sandbox_engine) { + return false; + } + + switch (criteria->sandbox_engine->match_type) { + case PATTERN_FOCUSED: + if (!focused || lenient_strcmp(sandbox_engine, view_get_sandbox_engine(focused))) { + return false; + } + break; + case PATTERN_PCRE2: + if (regex_cmp(sandbox_engine, criteria->sandbox_engine->regex) < 0) { + return false; + } + break; + } + } + + if (criteria->sandbox_app_id) { + const char *sandbox_app_id = view_get_sandbox_app_id(view); + if (!sandbox_app_id) { + return false; + } + + switch (criteria->sandbox_app_id->match_type) { + case PATTERN_FOCUSED: + if (!focused || lenient_strcmp(sandbox_app_id, view_get_sandbox_app_id(focused))) { + return false; + } + break; + case PATTERN_PCRE2: + if (regex_cmp(sandbox_app_id, criteria->sandbox_app_id->regex) < 0) { + return false; + } + break; + } + } + + if (criteria->sandbox_instance_id) { + const char *sandbox_instance_id = view_get_sandbox_instance_id(view); + if (!sandbox_instance_id) { + return false; + } + + switch (criteria->sandbox_instance_id->match_type) { + case PATTERN_FOCUSED: + if (!focused || lenient_strcmp(sandbox_instance_id, view_get_sandbox_instance_id(focused))) { + return false; + } + break; + case PATTERN_PCRE2: + if (regex_cmp(sandbox_instance_id, criteria->sandbox_instance_id->regex) < 0) { + return false; + } + break; + } + } + + if (criteria->tag) { + const char *tag = view_get_tag(view); + if (!tag) { + return false; + } + + switch (criteria->tag->match_type) { + case PATTERN_FOCUSED: + if (!focused || lenient_strcmp(tag, view_get_tag(focused))) { + return false; + } + break; + case PATTERN_PCRE2: + if (regex_cmp(tag, criteria->tag->regex) < 0) { + return false; + } + break; + } + } + if (!criteria_matches_container(criteria, view->container)) { return false; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (criteria->id) { // X11 window ID uint32_t x11_window_id = view_get_x11_window_id(view); if (!x11_window_id || x11_window_id != criteria->id) { @@ -267,7 +360,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->class->match_type) { case PATTERN_FOCUSED: - if (focused && lenient_strcmp(class, view_get_class(focused))) { + if (!focused || lenient_strcmp(class, view_get_class(focused))) { return false; } break; @@ -287,7 +380,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->instance->match_type) { case PATTERN_FOCUSED: - if (focused && lenient_strcmp(instance, view_get_instance(focused))) { + if (!focused || lenient_strcmp(instance, view_get_instance(focused))) { return false; } break; @@ -307,7 +400,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->window_role->match_type) { case PATTERN_FOCUSED: - if (focused && lenient_strcmp(window_role, view_get_window_role(focused))) { + if (!focused || lenient_strcmp(window_role, view_get_window_role(focused))) { return false; } break; @@ -365,7 +458,7 @@ static bool criteria_matches_view(struct criteria *criteria, switch (criteria->workspace->match_type) { case PATTERN_FOCUSED: - if (focused && + if (!focused || strcmp(ws->name, focused->container->pending.workspace->name)) { return false; } @@ -428,7 +521,7 @@ list_t *criteria_get_containers(struct criteria *criteria) { return matches; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND static enum atom_name parse_window_type(const char *type) { if (strcasecmp(type, "normal") == 0) { return NET_WM_WINDOW_TYPE_NORMAL; @@ -461,7 +554,7 @@ enum criteria_token { T_CON_ID, T_CON_MARK, T_FLOATING, -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND T_CLASS, T_ID, T_INSTANCE, @@ -474,6 +567,10 @@ enum criteria_token { T_URGENT, T_WORKSPACE, T_PID, + T_SANDBOX_ENGINE, + T_SANDBOX_APP_ID, + T_SANDBOX_INSTANCE_ID, + T_TAG, T_INVALID, }; @@ -487,7 +584,7 @@ static enum criteria_token token_from_name(char *name) { return T_CON_ID; } else if (strcmp(name, "con_mark") == 0) { return T_CON_MARK; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND } else if (strcmp(name, "class") == 0) { return T_CLASS; } else if (strcmp(name, "id") == 0) { @@ -513,6 +610,14 @@ static enum criteria_token token_from_name(char *name) { return T_FLOATING; } else if (strcmp(name, "pid") == 0) { return T_PID; + } else if (strcmp(name, "sandbox_engine") == 0) { + return T_SANDBOX_ENGINE; + } else if (strcmp(name, "sandbox_app_id") == 0) { + return T_SANDBOX_APP_ID; + } else if (strcmp(name, "sandbox_instance_id") == 0) { + return T_SANDBOX_INSTANCE_ID; + } else if (strcmp(name, "tag") == 0) { + return T_TAG; } return T_INVALID; } @@ -554,8 +659,7 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { if (strcmp(value, "__focused__") == 0) { struct sway_seat *seat = input_manager_current_seat(); struct sway_container *focus = seat_get_focused_container(seat); - struct sway_view *view = focus ? focus->view : NULL; - criteria->con_id = view ? view->container->node.id : 0; + criteria->con_id = focus ? focus->node.id : 0; } else { criteria->con_id = strtoul(value, &endptr, 10); if (*endptr != 0) { @@ -566,7 +670,7 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { case T_CON_MARK: pattern_create(&criteria->con_mark, value); break; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND case T_CLASS: pattern_create(&criteria->class, value); break; @@ -616,6 +720,18 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { error = strdup("The value for 'pid' should be numeric"); } break; + case T_SANDBOX_ENGINE: + pattern_create(&criteria->sandbox_engine, value); + break; + case T_SANDBOX_APP_ID: + pattern_create(&criteria->sandbox_app_id, value); + break; + case T_SANDBOX_INSTANCE_ID: + pattern_create(&criteria->sandbox_instance_id, value); + break; + case T_TAG: + pattern_create(&criteria->tag, value); + break; case T_INVALID: break; } @@ -674,7 +790,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) { ++head; struct criteria *criteria = calloc(1, sizeof(struct criteria)); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND criteria->window_type = ATOM_LAST; // default value #endif char *name = NULL, *value = NULL; diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c index f3af7aa1b..6b2761fcd 100644 --- a/sway/desktop/idle_inhibit_v1.c +++ b/sway/desktop/idle_inhibit_v1.c @@ -1,5 +1,6 @@ #include #include +#include #include "log.h" #include "sway/desktop/idle_inhibit_v1.h" #include "sway/input/seat.h" @@ -44,6 +45,14 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) { sway_idle_inhibit_v1_check_active(); } +void handle_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_idle_inhibit_manager_v1 *manager = + wl_container_of(listener, manager, manager_destroy); + + wl_list_remove(&manager->manager_destroy.link); + wl_list_remove(&manager->new_idle_inhibitor_v1.link); +} + void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, enum sway_idle_inhibit_mode mode) { struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1; @@ -103,11 +112,34 @@ void sway_idle_inhibit_v1_user_inhibitor_destroy( } bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { + if (server.session_lock.lock) { + // A session lock is active. In this case, only application inhibitors + // on the session lock surface can have any effect. + if (inhibitor->mode != INHIBIT_IDLE_APPLICATION) { + return false; + } + struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; + if (!wlr_session_lock_surface_v1_try_from_wlr_surface(wlr_surface)) { + return false; + } + return wlr_surface->mapped; + } + switch (inhibitor->mode) { case INHIBIT_IDLE_APPLICATION:; - // If there is no view associated with the inhibitor, assume visible - struct sway_view *view = view_from_wlr_surface(inhibitor->wlr_inhibitor->surface); - return !view || !view->container || view_is_visible(view); + struct wlr_surface *wlr_surface = inhibitor->wlr_inhibitor->surface; + struct wlr_layer_surface_v1 *layer_surface = + wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface); + if (layer_surface) { + // Layer surfaces can be occluded but are always on screen after + // they have been mapped. + return layer_surface->output && layer_surface->output->enabled && + wlr_surface->mapped; + } + + // If there is no view associated with the inhibitor, assume invisible + struct sway_view *view = view_from_wlr_surface(wlr_surface); + return view && view->container && view_is_visible(view); case INHIBIT_IDLE_FOCUS:; struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { @@ -153,6 +185,9 @@ bool sway_idle_inhibit_manager_v1_init(void) { wl_signal_add(&manager->wlr_manager->events.new_inhibitor, &manager->new_idle_inhibitor_v1); manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1; + wl_signal_add(&manager->wlr_manager->events.destroy, + &manager->manager_destroy); + manager->manager_destroy.notify = handle_manager_destroy; wl_list_init(&manager->inhibitors); return true; diff --git a/sway/desktop/launcher.c b/sway/desktop/launcher.c index 28043d192..2362e1ba0 100644 --- a/sway/desktop/launcher.c +++ b/sway/desktop/launcher.c @@ -4,6 +4,7 @@ #include "sway/input/seat.h" #include "sway/output.h" #include "sway/desktop/launcher.h" +#include "sway/server.h" #include "sway/tree/node.h" #include "sway/tree/container.h" #include "sway/tree/workspace.h" diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c index 4b2584b6b..c8f485971 100644 --- a/sway/desktop/layer_shell.c +++ b/sway/desktop/layer_shell.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -53,7 +54,7 @@ struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface( } static void arrange_surface(struct sway_output *output, const struct wlr_box *full_area, - struct wlr_box *usable_area, struct wlr_scene_tree *tree) { + struct wlr_box *usable_area, struct wlr_scene_tree *tree, bool exclusive) { struct wlr_scene_node *node; wl_list_for_each(node, &tree->children, link) { struct sway_layer_surface *surface = scene_descriptor_try_get(node, @@ -67,6 +68,10 @@ static void arrange_surface(struct sway_output *output, const struct wlr_box *fu continue; } + if ((surface->scene->layer_surface->current.exclusive_zone > 0) != exclusive) { + continue; + } + wlr_scene_layer_surface_v1_configure(surface->scene, full_area, usable_area); } } @@ -77,10 +82,15 @@ void arrange_layers(struct sway_output *output) { &usable_area.width, &usable_area.height); const struct wlr_box full_area = usable_area; - arrange_surface(output, &full_area, &usable_area, output->layers.shell_background); - arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom); - arrange_surface(output, &full_area, &usable_area, output->layers.shell_top); - arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay, true); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_top, true); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom, true); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_background, true); + + arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay, false); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_top, false); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom, false); + arrange_surface(output, &full_area, &usable_area, output->layers.shell_background, false); if (!wlr_box_equal(&usable_area, &output->usable_area)) { sway_log(SWAY_DEBUG, "Usable area changed, rearranging output"); @@ -89,6 +99,43 @@ void arrange_layers(struct sway_output *output) { } else { arrange_popups(root->layers.popup); } + + // Find topmost keyboard interactive layer, if such a layer exists + struct wlr_scene_tree *layers_above_shell[] = { + output->layers.shell_overlay, + output->layers.shell_top, + }; + size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]); + struct wlr_scene_node *node; + struct sway_layer_surface *topmost = NULL; + for (size_t i = 0; i < nlayers; ++i) { + wl_list_for_each_reverse(node, + &layers_above_shell[i]->children, link) { + struct sway_layer_surface *surface = scene_descriptor_try_get(node, + SWAY_SCENE_DESC_LAYER_SHELL); + if (surface && surface->layer_surface->current.keyboard_interactive + == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE && + surface->layer_surface->surface->mapped) { + topmost = surface; + break; + } + } + if (topmost != NULL) { + break; + } + } + + struct sway_seat *seat; + wl_list_for_each(seat, &server.input->seats, link) { + seat->has_exclusive_layer = false; + if (topmost != NULL) { + seat_set_focus_layer(seat, topmost->layer_surface); + } else if (seat->focused_layer && + seat->focused_layer->current.keyboard_interactive + != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) { + seat_set_focus_layer(seat, NULL); + } + } } static struct wlr_scene_tree *sway_layer_get_scene(struct sway_output *output, @@ -169,14 +216,6 @@ static struct sway_layer_surface *find_mapped_layer_by_client( return NULL; } -static void handle_output_destroy(struct wl_listener *listener, void *data) { - struct sway_layer_surface *layer = - wl_container_of(listener, layer, output_destroy); - - layer->output = NULL; - wlr_scene_node_destroy(&layer->scene->tree->node); -} - static void handle_node_destroy(struct wl_listener *listener, void *data) { struct sway_layer_surface *layer = wl_container_of(listener, layer, node_destroy); @@ -209,10 +248,11 @@ static void handle_node_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&layer->unmap.link); wl_list_remove(&layer->surface_commit.link); wl_list_remove(&layer->node_destroy.link); - wl_list_remove(&layer->output_destroy.link); + wl_list_remove(&layer->new_popup.link); layer->layer_surface->data = NULL; + wl_list_remove(&layer->link); free(layer); } @@ -221,12 +261,8 @@ static void handle_surface_commit(struct wl_listener *listener, void *data) { wl_container_of(listener, surface, surface_commit); struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface; - if (!layer_surface->initialized) { - return; - } - uint32_t committed = layer_surface->current.committed; - if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { + if (layer_surface->initialized && committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) { enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer; struct wlr_scene_tree *output_layer = sway_layer_get_scene( surface->output, layer_type); @@ -285,6 +321,7 @@ static void popup_handle_destroy(struct wl_listener *listener, void *data) { wl_list_remove(&popup->destroy.link); wl_list_remove(&popup->new_popup.link); wl_list_remove(&popup->commit.link); + wl_list_remove(&popup->reposition.link); free(popup); } @@ -320,6 +357,11 @@ static void popup_handle_commit(struct wl_listener *listener, void *data) { } } +static void popup_handle_reposition(struct wl_listener *listener, void *data) { + struct sway_layer_popup *popup = wl_container_of(listener, popup, reposition); + popup_unconstrain(popup); +} + static void popup_handle_new_popup(struct wl_listener *listener, void *data); static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup, @@ -340,11 +382,13 @@ static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup, } popup->destroy.notify = popup_handle_destroy; - wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); + wl_signal_add(&wlr_popup->events.destroy, &popup->destroy); popup->new_popup.notify = popup_handle_new_popup; wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); popup->commit.notify = popup_handle_commit; wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit); + popup->reposition.notify = popup_handle_reposition; + wl_signal_add(&wlr_popup->events.reposition, &popup->reposition); return popup; } @@ -431,6 +475,13 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) { } surface->output = output; + wl_list_insert(&output->layer_surfaces, &surface->link); + + // now that the surface's output is known, we can advertise its scale + wlr_fractional_scale_v1_notify_scale(surface->layer_surface->surface, + layer_surface->output->scale); + wlr_surface_set_preferred_buffer_scale(surface->layer_surface->surface, + ceil(layer_surface->output->scale)); surface->surface_commit.notify = handle_surface_commit; wl_signal_add(&layer_surface->surface->events.commit, @@ -442,9 +493,14 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) { surface->new_popup.notify = handle_new_popup; wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup); - surface->output_destroy.notify = handle_output_destroy; - wl_signal_add(&output->events.disable, &surface->output_destroy); - surface->node_destroy.notify = handle_node_destroy; wl_signal_add(&scene_surface->tree->node.events.destroy, &surface->node_destroy); } + +void destroy_layers(struct sway_output *output) { + struct sway_layer_surface *layer, *layer_tmp; + wl_list_for_each_safe(layer, layer_tmp, &output->layer_surfaces, link) { + layer->output = NULL; + wlr_layer_surface_v1_destroy(layer->layer_surface); + } +} diff --git a/sway/desktop/output.c b/sway/desktop/output.c index b8f2d32d2..12dc9cc7a 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include #include #include @@ -97,11 +97,11 @@ struct buffer_timer { }; static int handle_buffer_timer(void *data) { - struct wlr_scene_buffer *buffer = data; + struct wlr_scene_surface *scene_surface = data; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - wlr_scene_buffer_send_frame_done(buffer, &now); + wlr_scene_surface_send_frame_done(scene_surface, &now); return 0; } @@ -114,7 +114,9 @@ static void handle_buffer_timer_destroy(struct wl_listener *listener, free(timer); } -static struct buffer_timer *buffer_timer_get_or_create(struct wlr_scene_buffer *buffer) { +static struct buffer_timer *buffer_timer_get_or_create(struct wlr_scene_surface *scene_surface) { + struct wlr_scene_buffer *buffer = scene_surface->buffer; + struct buffer_timer *timer = scene_descriptor_try_get(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER); if (timer) { @@ -127,7 +129,7 @@ static struct buffer_timer *buffer_timer_get_or_create(struct wlr_scene_buffer * } timer->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop, - handle_buffer_timer, buffer); + handle_buffer_timer, scene_surface); if (!timer->frame_done_timer) { free(timer); return NULL; @@ -151,6 +153,11 @@ static void send_frame_done_iterator(struct wlr_scene_buffer *buffer, return; } + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); + if (scene_surface == NULL) { + return; + } + struct wlr_scene_node *current = &buffer->node; while (true) { struct sway_view *view = scene_descriptor_try_get(current, @@ -173,13 +180,13 @@ static void send_frame_done_iterator(struct wlr_scene_buffer *buffer, struct buffer_timer *timer = NULL; if (output->max_render_time != 0 && view_max_render_time != 0 && delay > 0) { - timer = buffer_timer_get_or_create(buffer); + timer = buffer_timer_get_or_create(scene_surface); } if (timer) { wl_event_source_timer_update(timer->frame_done_timer, delay); } else { - wlr_scene_buffer_send_frame_done(buffer, &data->when); + wlr_scene_surface_send_frame_done(scene_surface, &data->when); } } @@ -187,8 +194,8 @@ static enum wlr_scale_filter_mode get_scale_filter(struct sway_output *output, struct wlr_scene_buffer *buffer) { // if we are scaling down, we should always choose linear if (buffer->dst_width > 0 && buffer->dst_height > 0 && ( - buffer->dst_width < buffer->buffer_width || - buffer->dst_height < buffer->buffer_height)) { + buffer->dst_width < buffer->WLR_PRIVATE.buffer_width || + buffer->dst_height < buffer->WLR_PRIVATE.buffer_height)) { return WLR_SCALE_FILTER_BILINEAR; } @@ -202,7 +209,7 @@ static enum wlr_scale_filter_mode get_scale_filter(struct sway_output *output, } } -static void output_configure_scene(struct sway_output *output, +void output_configure_scene(struct sway_output *output, struct wlr_scene_node *node, float opacity) { if (!node->enabled) { return; @@ -216,11 +223,22 @@ static void output_configure_scene(struct sway_output *output, if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(buffer); + + if (surface) { + const struct wlr_alpha_modifier_surface_v1_state *alpha_modifier_state = + wlr_alpha_modifier_v1_get_surface_state(surface->surface); + if (alpha_modifier_state != NULL) { + opacity *= (float)alpha_modifier_state->multiplier; + } + } // hack: don't call the scene setter because that will damage all outputs // We don't want to damage outputs that aren't our current output that // we're configuring - buffer->filter_mode = get_scale_filter(output, buffer); + if (output) { + buffer->filter_mode = get_scale_filter(output, buffer); + } wlr_scene_buffer_set_opacity(buffer, opacity); } else if (node->type == WLR_SCENE_NODE_TREE) { @@ -232,44 +250,63 @@ static void output_configure_scene(struct sway_output *output, } } +static bool output_can_tear(struct sway_output *output) { + struct sway_workspace *workspace = output->current.active_workspace; + if (!workspace) { + return false; + } + + struct sway_container *fullscreen_con = root->fullscreen_global; + if (!fullscreen_con) { + fullscreen_con = workspace->current.fullscreen; + } + if (fullscreen_con && fullscreen_con->view) { + return (output->allow_tearing && view_can_tear(fullscreen_con->view)); + } + + return false; +} + static int output_repaint_timer_handler(void *data) { struct sway_output *output = data; - if (!output->enabled) { + output->wlr_output->frame_pending = false; + if (!output->wlr_output->enabled) { return 0; } - output->wlr_output->frame_pending = false; - output_configure_scene(output, &root->root_scene->tree.node, 1.0f); - if (output->gamma_lut_changed) { - struct wlr_output_state pending; - wlr_output_state_init(&pending); - if (!wlr_scene_output_build_state(output->scene_output, &pending, NULL)) { - return 0; - } + struct wlr_scene_output_state_options opts = { + .color_transform = output->color_transform, + }; - output->gamma_lut_changed = false; - struct wlr_gamma_control_v1 *gamma_control = - wlr_gamma_control_manager_v1_get_control( - server.gamma_control_manager_v1, output->wlr_output); - if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) { - wlr_output_state_finish(&pending); - return 0; - } - - if (!wlr_output_commit_state(output->wlr_output, &pending)) { - wlr_gamma_control_v1_send_failed_and_destroy(gamma_control); - wlr_output_state_finish(&pending); - return 0; - } + struct wlr_scene_output *scene_output = output->scene_output; + if (!wlr_scene_output_needs_frame(scene_output)) { + return 0; + } + struct wlr_output_state pending; + wlr_output_state_init(&pending); + if (!wlr_scene_output_build_state(output->scene_output, &pending, &opts)) { wlr_output_state_finish(&pending); return 0; } - wlr_scene_output_commit(output->scene_output, NULL); + if (output_can_tear(output)) { + pending.tearing_page_flip = true; + + if (!wlr_output_test_state(output->wlr_output, &pending)) { + sway_log(SWAY_DEBUG, "Output test failed on '%s', retrying without tearing page-flip", + output->wlr_output->name); + pending.tearing_page_flip = false; + } + } + + if (!wlr_output_commit_state(output->wlr_output, &pending)) { + sway_log(SWAY_ERROR, "Page-flip failed on output %s", output->wlr_output->name); + } + wlr_output_state_finish(&pending); return 0; } @@ -337,7 +374,7 @@ static void handle_frame(struct wl_listener *listener, void *user_data) { wlr_scene_output_for_each_buffer(output->scene_output, send_frame_done_iterator, &data); } -static void update_output_manager_config(struct sway_server *server) { +void update_output_manager_config(struct sway_server *server) { struct wlr_output_configuration_v1 *config = wlr_output_configuration_v1_create(); @@ -362,32 +399,61 @@ static void update_output_manager_config(struct sway_server *server) { ipc_event_output(); } -static void begin_destroy(struct sway_output *output) { - struct sway_server *server = output->server; +static int timer_modeset_handle(void *data) { + struct sway_server *server = data; + wl_event_source_remove(server->delayed_modeset); + server->delayed_modeset = NULL; - if (output->enabled) { - output_disable(output); + apply_stored_output_configs(); + return 0; +} + +void request_modeset(void) { + if (server.delayed_modeset == NULL) { + server.delayed_modeset = wl_event_loop_add_timer(server.wl_event_loop, + timer_modeset_handle, &server); + wl_event_source_timer_update(server.delayed_modeset, 10); } +} - output_begin_destroy(output); +bool modeset_is_pending(void) { + return server.delayed_modeset != NULL; +} - wl_list_remove(&output->link); +void force_modeset(void) { + if (server.delayed_modeset != NULL) { + wl_event_source_remove(server.delayed_modeset); + server.delayed_modeset = NULL; + } + apply_stored_output_configs(); +} + +static void begin_destroy(struct sway_output *output) { wl_list_remove(&output->layout_destroy.link); wl_list_remove(&output->destroy.link); - wl_list_remove(&output->commit.link); wl_list_remove(&output->present.link); wl_list_remove(&output->frame.link); wl_list_remove(&output->request_state.link); + // Remove the scene_output first to ensure that the scene does not emit + // events for this output. wlr_scene_output_destroy(output->scene_output); output->scene_output = NULL; + + if (output->enabled) { + output_disable(output); + } + output_begin_destroy(output); + wl_list_remove(&output->link); + output->wlr_output->data = NULL; output->wlr_output = NULL; - transaction_commit_dirty(); + wl_event_source_remove(output->repaint_timer); + output->repaint_timer = NULL; - update_output_manager_config(server); + request_modeset(); } static void handle_destroy(struct wl_listener *listener, void *data) { @@ -400,31 +466,6 @@ static void handle_layout_destroy(struct wl_listener *listener, void *data) { begin_destroy(output); } -static void handle_commit(struct wl_listener *listener, void *data) { - struct sway_output *output = wl_container_of(listener, output, commit); - struct wlr_output_event_commit *event = data; - - if (!output->enabled) { - return; - } - - if (event->state->committed & ( - WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_TRANSFORM | - WLR_OUTPUT_STATE_SCALE)) { - arrange_layers(output); - arrange_output(output); - transaction_commit_dirty(); - - update_output_manager_config(output->server); - } - - // Next time the output is enabled, try to re-apply the gamma LUT - if ((event->state->committed & WLR_OUTPUT_STATE_ENABLED) && !output->wlr_output->enabled) { - output->gamma_lut_changed = true; - } -} - static void handle_present(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, present); struct wlr_output_event_present *output_event = data; @@ -433,7 +474,7 @@ static void handle_present(struct wl_listener *listener, void *data) { return; } - output->last_presentation = *output_event->when; + output->last_presentation = output_event->when; output->refresh_nsec = output_event->refresh; } @@ -441,7 +482,44 @@ static void handle_request_state(struct wl_listener *listener, void *data) { struct sway_output *output = wl_container_of(listener, output, request_state); const struct wlr_output_event_request_state *event = data; - wlr_output_commit_state(output->wlr_output, event->state); + const struct wlr_output_state *state = event->state; + + // Store the requested changes so that the active configuration is + // consistent with the current state, and to avoid duplicate logic to apply + // the changes. + struct output_config *oc = new_output_config(output->wlr_output->name); + if (!oc) { + sway_log(SWAY_ERROR, "Allocation failed"); + return; + } + + int committed = state->committed; + if (committed & WLR_OUTPUT_STATE_MODE) { + if (state->mode != NULL) { + oc->width = state->mode->width; + oc->height = state->mode->height; + oc->refresh_rate = state->mode->refresh / 1000.f; + } else { + oc->width = state->custom_mode.width; + oc->height = state->custom_mode.height; + oc->refresh_rate = state->custom_mode.refresh / 1000.f; + } + committed &= ~WLR_OUTPUT_STATE_MODE; + } + if (committed & WLR_OUTPUT_STATE_SCALE) { + oc->scale = state->scale; + committed &= ~WLR_OUTPUT_STATE_SCALE; + } + if (committed & WLR_OUTPUT_STATE_TRANSFORM) { + oc->transform = state->transform; + committed &= ~WLR_OUTPUT_STATE_TRANSFORM; + } + + // We do not expect or support any other changes here + assert(committed == 0); + store_output_config(oc); + + force_modeset(); } static unsigned int last_headless_num = 0; @@ -505,8 +583,6 @@ void handle_new_output(struct wl_listener *listener, void *data) { output->layout_destroy.notify = handle_layout_destroy; wl_signal_add(&wlr_output->events.destroy, &output->destroy); output->destroy.notify = handle_destroy; - wl_signal_add(&wlr_output->events.commit, &output->commit); - output->commit.notify = handle_commit; wl_signal_add(&wlr_output->events.present, &output->present); output->present.notify = handle_present; wl_signal_add(&wlr_output->events.frame, &output->frame); @@ -521,105 +597,94 @@ void handle_new_output(struct wl_listener *listener, void *data) { sway_session_lock_add_output(server->session_lock.lock, output); } - struct output_config *oc = find_output_config(output); - apply_output_config(oc, output); - free_output_config(oc); - - transaction_commit_dirty(); - - update_output_manager_config(server); + request_modeset(); } -void handle_output_layout_change(struct wl_listener *listener, - void *data) { - struct sway_server *server = - wl_container_of(listener, server, output_layout_change); - update_output_manager_config(server); -} - -void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) { - struct sway_server *server = - wl_container_of(listener, server, gamma_control_set_gamma); - const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data; - - struct sway_output *output = event->output->data; - - if(!output) { - return; +static struct output_config *output_config_for_config_head( + struct wlr_output_configuration_head_v1 *config_head) { + struct output_config *oc = new_output_config(config_head->state.output->name); + if (!oc) { + return NULL; } - output->gamma_lut_changed = true; - wlr_output_schedule_frame(output->wlr_output); + oc->enabled = config_head->state.enabled; + if (!oc->enabled) { + return oc; + } + + if (config_head->state.mode != NULL) { + struct wlr_output_mode *mode = config_head->state.mode; + oc->width = mode->width; + oc->height = mode->height; + oc->refresh_rate = mode->refresh / 1000.f; + } else { + oc->width = config_head->state.custom_mode.width; + oc->height = config_head->state.custom_mode.height; + oc->refresh_rate = + config_head->state.custom_mode.refresh / 1000.f; + } + oc->x = config_head->state.x; + oc->y = config_head->state.y; + oc->transform = config_head->state.transform; + oc->scale = config_head->state.scale; + oc->adaptive_sync = config_head->state.adaptive_sync_enabled; + return oc; } static void output_manager_apply(struct sway_server *server, - struct wlr_output_configuration_v1 *config, bool test_only) { - // TODO: perform atomic tests on the whole backend atomically + struct wlr_output_configuration_v1 *cfg, bool test_only) { + bool ok = false; + size_t configs_len = config->output_configs->length + wl_list_length(&cfg->heads); + struct output_config **configs = calloc(configs_len, sizeof(*configs)); + if (!configs) { + sway_log(SWAY_ERROR, "Allocation failed"); + goto error; + } + size_t start_new_configs = config->output_configs->length; + for (size_t idx = 0; idx < start_new_configs; idx++) { + configs[idx] = config->output_configs->items[idx]; + } + size_t config_idx = start_new_configs; struct wlr_output_configuration_head_v1 *config_head; - // First disable outputs we need to disable - bool ok = true; - wl_list_for_each(config_head, &config->heads, link) { - struct wlr_output *wlr_output = config_head->state.output; - struct sway_output *output = wlr_output->data; - if (!output->enabled || config_head->state.enabled) { - continue; - } - struct output_config *oc = new_output_config(output->wlr_output->name); - oc->enabled = false; - - if (test_only) { - ok &= test_output_config(oc, output); - } else { - oc = store_output_config(oc); - ok &= apply_output_config(oc, output); + wl_list_for_each(config_head, &cfg->heads, link) { + // Generate the configuration and store it as a temporary + // config. We keep a record of it so we can remove it later. + struct output_config *oc = output_config_for_config_head(config_head); + if (!oc) { + sway_log(SWAY_ERROR, "Allocation failed"); + goto error_config; } + configs[config_idx++] = oc; } - // Then enable outputs that need to - wl_list_for_each(config_head, &config->heads, link) { - struct wlr_output *wlr_output = config_head->state.output; - struct sway_output *output = wlr_output->data; - if (!config_head->state.enabled) { - continue; - } - struct output_config *oc = new_output_config(output->wlr_output->name); - oc->enabled = true; - if (config_head->state.mode != NULL) { - struct wlr_output_mode *mode = config_head->state.mode; - oc->width = mode->width; - oc->height = mode->height; - oc->refresh_rate = mode->refresh / 1000.f; - } else { - oc->width = config_head->state.custom_mode.width; - oc->height = config_head->state.custom_mode.height; - oc->refresh_rate = - config_head->state.custom_mode.refresh / 1000.f; - } - oc->x = config_head->state.x; - oc->y = config_head->state.y; - oc->transform = config_head->state.transform; - oc->scale = config_head->state.scale; - oc->adaptive_sync = config_head->state.adaptive_sync_enabled; + // Try to commit without degrade to off enabled. Note that this will fail + // if any output configured for enablement fails to be enabled, even if it + // was not part of the config heads we were asked to configure. + ok = apply_output_configs(configs, configs_len, test_only, false); - if (test_only) { - ok &= test_output_config(oc, output); +error_config: + for (size_t idx = start_new_configs; idx < configs_len; idx++) { + struct output_config *cfg = configs[idx]; + if (!test_only && ok) { + store_output_config(cfg); } else { - oc = store_output_config(oc); - ok &= apply_output_config(oc, output); + free_output_config(cfg); } } + free(configs); +error: if (ok) { - wlr_output_configuration_v1_send_succeeded(config); + wlr_output_configuration_v1_send_succeeded(cfg); + if (server->delayed_modeset != NULL) { + wl_event_source_remove(server->delayed_modeset); + server->delayed_modeset = NULL; + } } else { - wlr_output_configuration_v1_send_failed(config); - } - wlr_output_configuration_v1_destroy(config); - - if (!test_only) { - update_output_manager_config(server); + wlr_output_configuration_v1_send_failed(cfg); } + wlr_output_configuration_v1_destroy(cfg); } void handle_output_manager_apply(struct wl_listener *listener, void *data) { @@ -644,6 +709,11 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, struct sway_output *output = event->output->data; struct output_config *oc = new_output_config(output->wlr_output->name); + if (!oc) { + sway_log(SWAY_ERROR, "Allocation failed"); + return; + } + switch (event->mode) { case ZWLR_OUTPUT_POWER_V1_MODE_OFF: oc->power = 0; @@ -652,6 +722,6 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener, oc->power = 1; break; } - oc = store_output_config(oc); - apply_output_config(oc, output); + store_output_config(oc); + request_modeset(); } diff --git a/sway/desktop/tearing.c b/sway/desktop/tearing.c new file mode 100644 index 000000000..d8d276451 --- /dev/null +++ b/sway/desktop/tearing.c @@ -0,0 +1,64 @@ +#include +#include +#include "sway/server.h" +#include "sway/tree/view.h" +#include "sway/output.h" +#include "log.h" + +struct sway_tearing_controller { + struct wlr_tearing_control_v1 *tearing_control; + struct wl_listener set_hint; + struct wl_listener destroy; + + struct wl_list link; // sway_server::tearing_controllers +}; + +static void handle_tearing_controller_set_hint(struct wl_listener *listener, + void *data) { + struct sway_tearing_controller *controller = + wl_container_of(listener, controller, set_hint); + + struct sway_view *view = view_from_wlr_surface( + controller->tearing_control->surface); + if (view) { + view->tearing_hint = controller->tearing_control->current; + } +} + +static void handle_tearing_controller_destroy(struct wl_listener *listener, + void *data) { + struct sway_tearing_controller *controller = + wl_container_of(listener, controller, destroy); + wl_list_remove(&controller->set_hint.link); + wl_list_remove(&controller->destroy.link); + wl_list_remove(&controller->link); + free(controller); +} + +void handle_new_tearing_hint(struct wl_listener *listener, + void *data) { + struct sway_server *server = + wl_container_of(listener, server, tearing_control_new_object); + struct wlr_tearing_control_v1 *tearing_control = data; + + enum wp_tearing_control_v1_presentation_hint hint = + wlr_tearing_control_manager_v1_surface_hint_from_surface( + server->tearing_control_v1, tearing_control->surface); + sway_log(SWAY_DEBUG, "New presentation hint %d received for surface %p", + hint, tearing_control->surface); + + struct sway_tearing_controller *controller = + calloc(1, sizeof(struct sway_tearing_controller)); + if (!controller) { + return; + } + + controller->tearing_control = tearing_control; + controller->set_hint.notify = handle_tearing_controller_set_hint; + wl_signal_add(&tearing_control->events.set_hint, &controller->set_hint); + controller->destroy.notify = handle_tearing_controller_destroy; + wl_signal_add(&tearing_control->events.destroy, &controller->destroy); + wl_list_init(&controller->link); + + wl_list_insert(&server->tearing_controllers, &controller->link); +} diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c index 042141ab2..082fe20ce 100644 --- a/sway/desktop/transaction.c +++ b/sway/desktop/transaction.c @@ -10,6 +10,7 @@ #include "sway/input/cursor.h" #include "sway/input/input-manager.h" #include "sway/output.h" +#include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/node.h" #include "sway/tree/view.h" @@ -58,7 +59,7 @@ static void transaction_destroy(struct sway_transaction *transaction) { if (node->instruction == instruction) { node->instruction = NULL; } - if (node->destroying && node->ntxnrefs == 0) { + if (node->destroying && node->ntxnrefs == 0 && !node->dirty) { switch (node->type) { case N_ROOT: sway_assert(false, "Never reached"); @@ -308,12 +309,13 @@ static void arrange_children(enum sway_container_layout layout, list_t *children arrange_title_bar(child, title_offset, -title_bar_height, next_title_offset - title_offset, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); + wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - if (activated) { - arrange_container(child, width, height - title_bar_height, - false, 0); + int net_height = height - title_bar_height; + if (activated && width > 0 && net_height > 0) { + arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } @@ -337,12 +339,13 @@ static void arrange_children(enum sway_container_layout layout, list_t *children arrange_title_bar(child, 0, y - title_height, width, title_bar_height); wlr_scene_node_set_enabled(&child->border.tree->node, activated); + wlr_scene_node_set_enabled(&child->scene_tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height); wlr_scene_node_reparent(&child->scene_tree->node, content); - if (activated) { - arrange_container(child, width, height - title_height, - false, 0); + int net_height = height - title_height; + if (activated && width > 0 && net_height > 0) { + arrange_container(child, width, net_height, title_bar_height == 0, 0); } else { disable_container(child); } @@ -358,8 +361,12 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, 0, off); wlr_scene_node_reparent(&child->scene_tree->node, content); - arrange_container(child, width, cheight, true, gaps); - off += cheight + gaps; + if (width > 0 && cheight > 0) { + arrange_container(child, width, cheight, true, gaps); + off += cheight + gaps; + } else { + disable_container(child); + } } } else if (layout == L_HORIZ) { int off = 0; @@ -370,8 +377,12 @@ static void arrange_children(enum sway_container_layout layout, list_t *children wlr_scene_node_set_enabled(&child->border.tree->node, true); wlr_scene_node_set_position(&child->scene_tree->node, off, 0); wlr_scene_node_reparent(&child->scene_tree->node, content); - arrange_container(child, cwidth, height, true, gaps); - off += cwidth + gaps; + if (cwidth > 0 && height > 0) { + arrange_container(child, cwidth, height, true, gaps); + off += cwidth + gaps; + } else { + disable_container(child); + } } } else { sway_assert(false, "unreachable"); @@ -384,10 +395,6 @@ static void arrange_container(struct sway_container *con, // make sure it's enabled for viewing wlr_scene_node_set_enabled(&con->scene_tree->node, true); - if (con->output_handler) { - wlr_scene_buffer_set_dest_size(con->output_handler, width, height); - } - if (con->view) { int border_top = container_titlebar_height(); int border_width = con->current.border_thickness; @@ -423,13 +430,14 @@ static void arrange_container(struct sway_container *con, int border_bottom = con->current.border_bottom ? border_width : 0; int border_left = con->current.border_left ? border_width : 0; int border_right = con->current.border_right ? border_width : 0; + int vert_border_height = MAX(0, height - border_top - border_bottom); wlr_scene_rect_set_size(con->border.top, width, border_top); wlr_scene_rect_set_size(con->border.bottom, width, border_bottom); wlr_scene_rect_set_size(con->border.left, - border_left, height - border_top - border_bottom); + border_left, vert_border_height); wlr_scene_rect_set_size(con->border.right, - border_right, height - border_top - border_bottom); + border_right, vert_border_height); wlr_scene_node_set_position(&con->border.top->node, 0, 0); wlr_scene_node_set_position(&con->border.bottom->node, @@ -444,6 +452,13 @@ static void arrange_container(struct sway_container *con, wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree); wlr_scene_node_set_position(&con->view->scene_tree->node, border_left, border_top); + + // the output handler for the view wants to detect events for the entire + // container so give it negative coordinates to move it back over the + // decorations + wlr_scene_node_set_position(&con->view->output_handler->node, + -border_left, -border_top); + wlr_scene_buffer_set_dest_size(con->view->output_handler, width, height); } else { // make sure to disable the title bar if the parent is not managing it if (title_bar) { @@ -483,6 +498,11 @@ static void arrange_fullscreen(struct wlr_scene_tree *tree, // if we only care about the view, disable any decorations wlr_scene_node_set_enabled(&fs->scene_tree->node, false); + + // reconfigure the output handler (for foreign toplevel) to cover the + // view without container decorations + wlr_scene_node_set_position(&fs->view->output_handler->node, 0, 0); + wlr_scene_buffer_set_dest_size(fs->view->output_handler, width, height); } else { fs_node = &fs->scene_tree->node; arrange_container(fs, width, height, true, container_get_gaps(fs)); @@ -522,6 +542,7 @@ static void arrange_workspace_floating(struct sway_workspace *ws) { wlr_scene_node_set_position(&floater->scene_tree->node, floater->current.x, floater->current.y); wlr_scene_node_set_enabled(&floater->scene_tree->node, true); + wlr_scene_node_set_enabled(&floater->border.tree->node, true); arrange_container(floater, floater->current.width, floater->current.height, true, ws->gaps_inner); @@ -558,7 +579,7 @@ static void arrange_output(struct sway_output *output, int width, int height) { for (int i = 0; i < output->current.workspaces->length; i++) { struct sway_workspace *child = output->current.workspaces->items[i]; - bool activated = output->current.active_workspace == child; + bool activated = output->current.active_workspace == child && output->wlr_output->enabled; wlr_scene_node_reparent(&child->layers.tiling->node, output->layers.tiling); wlr_scene_node_reparent(&child->layers.fullscreen->node, output->layers.fullscreen); @@ -574,15 +595,16 @@ static void arrange_output(struct sway_output *output, int width, int height) { wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs); wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs); - arrange_workspace_floating(child); - wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs); wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs); wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs); if (fs) { + disable_workspace(child); + wlr_scene_rect_set_size(output->fullscreen_background, width, height); + arrange_workspace_floating(child); arrange_fullscreen(child->layers.fullscreen, fs, child, width, height); } else { @@ -595,6 +617,7 @@ static void arrange_output(struct sway_output *output, int width, int height) { arrange_workspace_tiling(child, area->width - gaps->left - gaps->right, area->height - gaps->top - gaps->bottom); + arrange_workspace_floating(child); } } else { wlr_scene_node_set_enabled(&child->layers.tiling->node, false); @@ -611,9 +634,11 @@ void arrange_popups(struct wlr_scene_tree *popups) { struct sway_popup_desc *popup = scene_descriptor_try_get(node, SWAY_SCENE_DESC_POPUP); - int lx, ly; - wlr_scene_node_coords(popup->relative, &lx, &ly); - wlr_scene_node_set_position(node, lx, ly); + if (popup) { + int lx, ly; + wlr_scene_node_coords(popup->relative, &lx, &ly); + wlr_scene_node_set_position(node, lx, ly); + } } } @@ -631,6 +656,7 @@ static void arrange_root(struct sway_root *root) { for (int i = 0; i < root->scratchpad->length; i++) { struct sway_container *con = root->scratchpad->items[i]; + disable_container(con); wlr_scene_node_set_enabled(&con->scene_tree->node, false); } @@ -639,6 +665,15 @@ static void arrange_root(struct sway_root *root) { struct sway_output *output = root->outputs->items[i]; struct sway_workspace *ws = output->current.active_workspace; + wlr_scene_output_set_position(output->scene_output, output->lx, output->ly); + + // disable all workspaces to get to a known state + for (int j = 0; j < output->current.workspaces->length; j++) { + struct sway_workspace *workspace = output->current.workspaces->items[j]; + disable_workspace(workspace); + } + + // arrange the active workspace if (ws) { arrange_workspace_floating(ws); } @@ -761,7 +796,7 @@ static bool should_configure(struct sway_node *node, } struct sway_container_state *cstate = &node->sway_container->current; struct sway_container_state *istate = &instruction->container_state; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND // Xwayland views are position-aware and need to be reconfigured // when their position changes. if (node->sway_container->view->type == SWAY_VIEW_XWAYLAND) { diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 7c4178910..7217e1369 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "log.h" #include "sway/decoration.h" @@ -20,13 +21,13 @@ static struct sway_xdg_popup *popup_create( struct wlr_xdg_popup *wlr_popup, struct sway_view *view, - struct wlr_scene_tree *parent); + struct wlr_scene_tree *parent, struct wlr_scene_tree *image_capture_parent); static void popup_handle_new_popup(struct wl_listener *listener, void *data) { struct sway_xdg_popup *popup = wl_container_of(listener, popup, new_popup); struct wlr_xdg_popup *wlr_popup = data; - popup_create(wlr_popup, popup->view, popup->xdg_surface_tree); + popup_create(wlr_popup, popup->view, popup->xdg_surface_tree, popup->image_capture_tree); } static void popup_handle_destroy(struct wl_listener *listener, void *data) { @@ -77,7 +78,8 @@ static void popup_handle_reposition(struct wl_listener *listener, void *data) { } static struct sway_xdg_popup *popup_create(struct wlr_xdg_popup *wlr_popup, - struct sway_view *view, struct wlr_scene_tree *parent) { + struct sway_view *view, struct wlr_scene_tree *parent, + struct wlr_scene_tree *image_capture_parent) { struct wlr_xdg_surface *xdg_surface = wlr_popup->base; struct sway_xdg_popup *popup = calloc(1, sizeof(struct sway_xdg_popup)); @@ -113,6 +115,11 @@ static struct sway_xdg_popup *popup_create(struct wlr_xdg_popup *wlr_popup, return NULL; } + popup->image_capture_tree = wlr_scene_xdg_surface_create(image_capture_parent, xdg_surface); + if (popup->image_capture_tree == NULL) { + return NULL; + } + popup->wlr_xdg_popup = xdg_surface->popup; struct sway_xdg_shell_view *shell_view = wl_container_of(view, shell_view, view); @@ -151,7 +158,8 @@ static void get_constraints(struct sway_view *view, double *min_width, static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { - if (xdg_shell_view_from_view(view) == NULL) { + struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + if (xdg_shell_view == NULL) { return NULL; } switch (prop) { @@ -159,6 +167,8 @@ static const char *get_string_prop(struct sway_view *view, return view->wlr_xdg_toplevel->title; case VIEW_PROP_APP_ID: return view->wlr_xdg_toplevel->app_id; + case VIEW_PROP_TAG: + return xdg_shell_view->tag; default: return NULL; } @@ -259,6 +269,7 @@ static void destroy(struct sway_view *view) { if (xdg_shell_view == NULL) { return; } + free(xdg_shell_view->tag); free(xdg_shell_view); } @@ -289,6 +300,8 @@ static void handle_commit(struct wl_listener *listener, void *data) { } // XXX: https://github.com/swaywm/sway/issues/2176 wlr_xdg_surface_schedule_configure(xdg_surface); + wlr_xdg_toplevel_set_wm_capabilities(view->wlr_xdg_toplevel, + WLR_XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); // TODO: wlr_xdg_toplevel_set_bounds() return; } @@ -297,18 +310,17 @@ static void handle_commit(struct wl_listener *listener, void *data) { return; } - struct wlr_box new_geo; - wlr_xdg_surface_get_geometry(xdg_surface, &new_geo); - bool new_size = new_geo.width != view->geometry.width || - new_geo.height != view->geometry.height || - new_geo.x != view->geometry.x || - new_geo.y != view->geometry.y; + struct wlr_box *new_geo = &xdg_surface->geometry; + bool new_size = new_geo->width != view->geometry.width || + new_geo->height != view->geometry.height || + new_geo->x != view->geometry.x || + new_geo->y != view->geometry.y; if (new_size) { // The client changed its surface size in this commit. For floating // containers, we resize the container to match. For tiling containers, // we only recenter the surface. - memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box)); + memcpy(&view->geometry, new_geo, sizeof(struct wlr_box)); if (container_is_floating(view->container)) { view_update_size(view); // Only set the toplevel size the current container actually has a size. @@ -342,6 +354,7 @@ static void handle_set_title(struct wl_listener *listener, void *data) { struct sway_view *view = &xdg_shell_view->view; view_update_title(view, false); view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_set_app_id(struct wl_listener *listener, void *data) { @@ -350,6 +363,7 @@ static void handle_set_app_id(struct wl_listener *listener, void *data) { struct sway_view *view = &xdg_shell_view->view; view_update_app_id(view); view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_new_popup(struct wl_listener *listener, void *data) { @@ -358,7 +372,7 @@ static void handle_new_popup(struct wl_listener *listener, void *data) { struct wlr_xdg_popup *wlr_popup = data; struct sway_xdg_popup *popup = popup_create(wlr_popup, - &xdg_shell_view->view, root->layers.popup); + &xdg_shell_view->view, root->layers.popup, xdg_shell_view->image_capture_tree); if (!popup) { return; } @@ -372,6 +386,9 @@ static void handle_request_maximize(struct wl_listener *listener, void *data) { struct sway_xdg_shell_view *xdg_shell_view = wl_container_of(listener, xdg_shell_view, request_maximize); struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel; + if (!toplevel->base->surface->mapped) { + return; + } wlr_xdg_surface_schedule_configure(toplevel->base); } @@ -461,12 +478,8 @@ static void handle_map(struct wl_listener *listener, void *data) { struct sway_view *view = &xdg_shell_view->view; struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel; - view->natural_width = toplevel->base->current.geometry.width; - view->natural_height = toplevel->base->current.geometry.height; - if (!view->natural_width && !view->natural_height) { - view->natural_width = toplevel->base->surface->current.width; - view->natural_height = toplevel->base->surface->current.height; - } + view->natural_width = toplevel->base->geometry.width; + view->natural_height = toplevel->base->geometry.height; bool csd = false; @@ -573,9 +586,18 @@ void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data) { wl_signal_add(&xdg_toplevel->events.destroy, &xdg_shell_view->destroy); wlr_scene_xdg_surface_create(xdg_shell_view->view.content_tree, xdg_toplevel->base); + xdg_shell_view->image_capture_tree = + wlr_scene_xdg_surface_create(&xdg_shell_view->view.image_capture_scene->tree, xdg_toplevel->base); xdg_toplevel->base->data = xdg_shell_view; - - wlr_xdg_toplevel_set_wm_capabilities(xdg_toplevel, - XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN); +} + +void xdg_toplevel_tag_manager_v1_handle_set_tag(struct wl_listener *listener, void *data) { + const struct wlr_xdg_toplevel_tag_manager_v1_set_tag_event *event = data; + struct sway_view *view = view_from_wlr_xdg_surface(event->toplevel->base); + struct sway_xdg_shell_view *xdg_shell_view = xdg_shell_view_from_view(view); + free(xdg_shell_view->tag); + xdg_shell_view->tag = strdup(event->tag); + view_execute_criteria(view); + transaction_commit_dirty(); } diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index 270cf08f8..65016b3b1 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c @@ -71,7 +71,7 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) { surface->set_geometry.notify = unmanaged_handle_set_geometry; } - if (wlr_xwayland_or_surface_wants_focus(xsurface)) { + if (wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct sway_seat *seat = input_manager_current_seat(); struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); @@ -96,7 +96,7 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { // This simply returns focus to the parent surface if there's one available. // This seems to handle JetBrains issues. if (xsurface->parent && xsurface->parent->surface - && wlr_xwayland_or_surface_wants_focus(xsurface->parent)) { + && wlr_xwayland_surface_override_redirect_wants_focus(xsurface->parent)) { seat_set_focus_surface(seat, xsurface->parent->surface, false); return; } @@ -289,7 +289,6 @@ static void set_activated(struct sway_view *view, bool activated) { } wlr_xwayland_surface_activate(surface, activated); - wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE); } static void set_tiled(struct sway_view *view, bool tiled) { @@ -297,7 +296,7 @@ static void set_tiled(struct sway_view *view, bool tiled) { return; } struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; - wlr_xwayland_surface_set_maximized(surface, tiled); + wlr_xwayland_surface_set_maximized(surface, tiled, tiled); } static void set_fullscreen(struct sway_view *view, bool fullscreen) { @@ -498,6 +497,9 @@ static void handle_unmap(struct wl_listener *listener, void *data) { wl_list_remove(&xwayland_view->commit.link); wl_list_remove(&xwayland_view->surface_tree_destroy.link); + wlr_scene_node_destroy(&xwayland_view->image_capture_scene_surface->buffer->node); + xwayland_view->image_capture_scene_surface = NULL; + if (xwayland_view->surface_tree) { wlr_scene_node_destroy(&xwayland_view->surface_tree->node); xwayland_view->surface_tree = NULL; @@ -538,6 +540,9 @@ static void handle_map(struct wl_listener *listener, void *data) { &xwayland_view->surface_tree_destroy); } + xwayland_view->image_capture_scene_surface = + wlr_scene_surface_create(&xwayland_view->view.image_capture_scene->tree, xsurface->surface); + transaction_commit_dirty(); } @@ -684,6 +689,7 @@ static void handle_set_title(struct wl_listener *listener, void *data) { } view_update_title(view, false); view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_set_class(struct wl_listener *listener, void *data) { @@ -695,6 +701,7 @@ static void handle_set_class(struct wl_listener *listener, void *data) { return; } view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_set_role(struct wl_listener *listener, void *data) { @@ -706,6 +713,7 @@ static void handle_set_role(struct wl_listener *listener, void *data) { return; } view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_set_startup_id(struct wl_listener *listener, void *data) { @@ -742,6 +750,7 @@ static void handle_set_window_type(struct wl_listener *listener, void *data) { return; } view_execute_criteria(view); + transaction_commit_dirty(); } static void handle_set_hints(struct wl_listener *listener, void *data) { diff --git a/sway/input/cursor.c b/sway/input/cursor.c index 7be83483f..336273de4 100644 --- a/sway/input/cursor.c +++ b/sway/input/cursor.c @@ -25,18 +25,13 @@ #include "sway/layers.h" #include "sway/output.h" #include "sway/scene_descriptor.h" +#include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/root.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "wlr-layer-shell-unstable-v1-protocol.h" -static uint32_t get_current_time_msec(void) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - return now.tv_sec * 1000 + now.tv_nsec / 1000000; -} - /** * Returns the node at the cursor's position. If there is a surface at that * location, it is stored in **surface (it may not be a view). @@ -107,7 +102,7 @@ struct sway_node *node_at_coords( return NULL; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (scene_descriptor_try_get(current, SWAY_SCENE_DESC_XWAYLAND_UNMANAGED)) { return NULL; } @@ -143,7 +138,7 @@ struct sway_node *node_at_coords( } void cursor_rebase(struct sway_cursor *cursor) { - uint32_t time_msec = get_current_time_msec(); + uint32_t time_msec = get_current_time_in_msec(); seatop_rebase(cursor->seat, time_msec); } @@ -358,7 +353,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor, struct wlr_input_device *device, uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state) { if (time_msec == 0) { - time_msec = get_current_time_msec(); + time_msec = get_current_time_in_msec(); } seatop_button(cursor->seat, time_msec, device, button, state); @@ -577,12 +572,13 @@ static void handle_tablet_tool_position(struct sway_cursor *cursor, // tablet events until the drag is released, even if we are now over a // non-tablet surface. if (!cursor->simulating_pointer_from_tool_tip && - ((surface && wlr_surface_accepts_tablet_v2(tablet->tablet_v2, surface)) || + ((surface && wlr_surface_accepts_tablet_v2(surface, tablet->tablet_v2)) || wlr_tablet_tool_v2_has_implicit_grab(tool->tablet_v2_tool))) { seatop_tablet_tool_motion(seat, tool, time_msec); } else { wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); pointer_motion(cursor, time_msec, input_device->wlr_device, dx, dy, dx, dy); + wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); } } @@ -648,6 +644,11 @@ static void handle_tool_tip(struct wl_listener *listener, void *data) { cursor_handle_activity_from_device(cursor, &event->tablet->base); struct sway_tablet_tool *sway_tool = event->tool->data; + if (!sway_tool) { + sway_log(SWAY_DEBUG, "tool tip before proximity"); + return; + } + struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2; struct sway_seat *seat = cursor->seat; @@ -663,7 +664,7 @@ static void handle_tool_tip(struct wl_listener *listener, void *data) { dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec, BTN_LEFT, WL_POINTER_BUTTON_STATE_RELEASED); wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); - } else if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) { + } else if (!surface || !wlr_surface_accepts_tablet_v2(surface, tablet_v2)) { // If we started holding the tool tip down on a surface that accepts // tablet v2, we should notify that surface if it gets released over a // surface that doesn't support v2. @@ -748,7 +749,7 @@ static void handle_tool_button(struct wl_listener *listener, void *data) { bool mod_pressed = modifiers & config->floating_mod; bool surface_supports_tablet_events = - surface && wlr_surface_accepts_tablet_v2(tablet_v2, surface); + surface && wlr_surface_accepts_tablet_v2(surface, tablet_v2); // Simulate pointer when: // 1. The modifier key is pressed, OR @@ -1046,6 +1047,7 @@ void sway_cursor_destroy(struct sway_cursor *cursor) { wl_list_remove(&cursor->touch_frame.link); wl_list_remove(&cursor->tool_axis.link); wl_list_remove(&cursor->tool_tip.link); + wl_list_remove(&cursor->tool_proximity.link); wl_list_remove(&cursor->tool_button.link); wl_list_remove(&cursor->request_set_cursor.link); @@ -1235,7 +1237,7 @@ uint32_t get_mouse_bindsym(const char *name, char **error) { SWAY_SCROLL_UP, SWAY_SCROLL_DOWN, SWAY_SCROLL_LEFT, SWAY_SCROLL_RIGHT, BTN_SIDE, BTN_EXTRA}; return buttons[number - 1]; - } else if (strncmp(name, "BTN_", strlen("BTN_")) == 0) { + } else if (has_prefix(name, "BTN_")) { // Get event code from name int code = libevdev_event_code_from_name(EV_KEY, name); if (code == -1) { @@ -1260,7 +1262,7 @@ uint32_t get_mouse_bindcode(const char *name, char **error) { return 0; } const char *event = libevdev_event_code_get_name(EV_KEY, code); - if (!event || strncmp(event, "BTN_", strlen("BTN_")) != 0) { + if (!event || !has_prefix(event, "BTN_")) { *error = format_str("Event code %d (%s) is not a button", code, event ? event : "(null)"); return 0; diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c index 248ca34ee..ffcf8fc52 100644 --- a/sway/input/input-manager.c +++ b/sway/input/input-manager.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -494,6 +493,14 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) { return input; } +void input_manager_finish(struct sway_input_manager *input) { + wl_list_remove(&input->new_input.link); + wl_list_remove(&input->virtual_keyboard_new.link); + wl_list_remove(&input->virtual_pointer_new.link); + wl_list_remove(&input->keyboard_shortcuts_inhibit_new_inhibitor.link); + wl_list_remove(&input->transient_seat_create.link); +} + bool input_manager_has_focus(struct sway_node *node) { struct sway_seat *seat = NULL; wl_list_for_each(seat, &server.input->seats, link) { @@ -578,7 +585,7 @@ void input_manager_configure_all_input_mappings(void) { void input_manager_apply_input_config(struct input_config *input_config) { struct sway_input_device *input_device = NULL; bool wildcard = strcmp(input_config->identifier, "*") == 0; - bool type_wildcard = strncmp(input_config->identifier, "type:", 5) == 0; + bool type_wildcard = has_prefix(input_config->identifier, "type:"); wl_list_for_each(input_device, &server.input->devices, link) { bool type_matches = type_wildcard && strcmp(input_device_get_type(input_device), input_config->identifier + 5) == 0; diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c index b97f0152c..d18237375 100644 --- a/sway/input/keyboard.c +++ b/sway/input/keyboard.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "sway/input/seat.h" #include "sway/input/cursor.h" #include "sway/ipc-server.h" +#include "sway/server.h" #include "log.h" #if WLR_HAS_SESSION @@ -32,6 +34,7 @@ static struct modifier_key { { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, { "Mod3", WLR_MODIFIER_MOD3 }, { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, + { "Super", WLR_MODIFIER_LOGO }, { "Mod5", WLR_MODIFIER_MOD5 }, }; @@ -265,6 +268,7 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard, const xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) { for (size_t i = 0; i < keysyms_len; ++i) { xkb_keysym_t keysym = pressed_keysyms[i]; + if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { #if WLR_HAS_SESSION @@ -280,6 +284,36 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard, return false; } +static bool keyboard_execute_pointer_keysyms(struct sway_keyboard *keyboard, + uint32_t time, const xkb_keysym_t *pressed_keysyms, size_t keysyms_len, + enum wl_keyboard_key_state state) { + struct sway_cursor *cursor = keyboard->seat_device->sway_seat->cursor; + + for (size_t i = 0; i < keysyms_len; ++i) { + xkb_keysym_t keysym = pressed_keysyms[i]; + + uint32_t button = wlr_keyboard_keysym_to_pointer_button(keysym); + if (button != 0) { + dispatch_cursor_button(cursor, &keyboard->wlr->base, time, button, + (enum wl_pointer_button_state)state); + wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); + return true; + } + + int dx, dy; + wlr_keyboard_keysym_to_pointer_motion(keysym, &dx, &dy); + if (state == WL_KEYBOARD_KEY_STATE_PRESSED && (dx != 0 || dy != 0)) { + dx *= 10; + dy *= 10; + pointer_motion(cursor, time, &keyboard->wlr->base, dx, dy, dx, dy); + wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); + return true; + } + } + + return false; +} + /** * Get keysyms and modifiers from the keyboard as xkb sees them. * @@ -505,14 +539,20 @@ static void handle_key_event(struct sway_keyboard *keyboard, keyboard, keyinfo.raw_keysyms, keyinfo.raw_modifiers, keyinfo.raw_keysyms_len); } + if (!handled) { + handled = keyboard_execute_pointer_keysyms(keyboard, event->time_msec, + keyinfo.translated_keysyms, keyinfo.translated_keysyms_len, + event->state); + } if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { - // If the pressed event was sent to a client, also send the released + // If the pressed event was sent to a client and we have a focused + // surface immediately before this event, also send the released // event. In particular, don't send the released event to the IM grab. bool pressed_sent = update_shortcut_state( &keyboard->state_pressed_sent, event->keycode, event->state, keyinfo.keycode, 0); - if (pressed_sent) { + if (pressed_sent && seat->wlr_seat->keyboard_state.focused_surface) { wlr_seat_set_keyboard(wlr_seat, keyboard->wlr); wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, event->keycode, event->state); @@ -940,10 +980,10 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) { wl_signal_add(&sway_group->wlr_group->keyboard.events.modifiers, &sway_group->keyboard_modifiers); sway_group->keyboard_modifiers.notify = handle_keyboard_group_modifiers; - + wl_signal_add(&sway_group->wlr_group->events.enter, &sway_group->enter); sway_group->enter.notify = handle_keyboard_group_enter; - + wl_signal_add(&sway_group->wlr_group->events.leave, &sway_group->leave); sway_group->leave.notify = handle_keyboard_group_leave; return; @@ -958,16 +998,8 @@ cleanup: free(sway_group); } -void sway_keyboard_configure(struct sway_keyboard *keyboard) { - struct input_config *input_config = - input_device_get_config(keyboard->seat_device->input_device); - - if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr), - "sway_keyboard_configure should not be called with a " - "keyboard group's keyboard")) { - return; - } - +static void sway_keyboard_set_layout(struct sway_keyboard *keyboard, + struct input_config *input_config) { struct xkb_keymap *keymap = sway_keyboard_compile_keymap(input_config, NULL); if (!keymap) { sway_log(SWAY_ERROR, "Failed to compile keymap. Attempting defaults"); @@ -983,31 +1015,13 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { !wlr_keyboard_keymaps_match(keyboard->keymap, keymap) : true; bool effective_layout_changed = keyboard->effective_layout != 0; - int repeat_rate = 25; - if (input_config && input_config->repeat_rate != INT_MIN) { - repeat_rate = input_config->repeat_rate; - } - int repeat_delay = 600; - if (input_config && input_config->repeat_delay != INT_MIN) { - repeat_delay = input_config->repeat_delay; - } - - bool repeat_info_changed = keyboard->repeat_rate != repeat_rate || - keyboard->repeat_delay != repeat_delay; - - if (keymap_changed || repeat_info_changed || config->reloading) { + if (keymap_changed || config->reloading) { xkb_keymap_unref(keyboard->keymap); keyboard->keymap = keymap; keyboard->effective_layout = 0; - keyboard->repeat_rate = repeat_rate; - keyboard->repeat_delay = repeat_delay; sway_keyboard_group_remove_invalid(keyboard); - wlr_keyboard_set_keymap(keyboard->wlr, keyboard->keymap); - wlr_keyboard_set_repeat_info(keyboard->wlr, - keyboard->repeat_rate, keyboard->repeat_delay); - if (!keyboard->wlr->group) { sway_keyboard_group_add(keyboard); } @@ -1051,6 +1065,49 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { } } + if (keymap_changed) { + ipc_event_input("xkb_keymap", + keyboard->seat_device->input_device); + } else if (effective_layout_changed) { + ipc_event_input("xkb_layout", + keyboard->seat_device->input_device); + } +} + +void sway_keyboard_configure(struct sway_keyboard *keyboard) { + struct input_config *input_config = + input_device_get_config(keyboard->seat_device->input_device); + + if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr), + "sway_keyboard_configure should not be called with a " + "keyboard group's keyboard")) { + return; + } + + int repeat_rate = 25; + if (input_config && input_config->repeat_rate != INT_MIN) { + repeat_rate = input_config->repeat_rate; + } + int repeat_delay = 600; + if (input_config && input_config->repeat_delay != INT_MIN) { + repeat_delay = input_config->repeat_delay; + } + + bool repeat_info_changed = keyboard->repeat_rate != repeat_rate || + keyboard->repeat_delay != repeat_delay; + + if (repeat_info_changed || config->reloading) { + keyboard->repeat_rate = repeat_rate; + keyboard->repeat_delay = repeat_delay; + + wlr_keyboard_set_repeat_info(keyboard->wlr, + keyboard->repeat_rate, keyboard->repeat_delay); + } + + if (!keyboard->seat_device->input_device->is_virtual) { + sway_keyboard_set_layout(keyboard, input_config); + } + // If the seat has no active keyboard, set this one struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat; struct wlr_keyboard *current_keyboard = seat->keyboard_state.keyboard; @@ -1067,13 +1124,6 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) { &keyboard->keyboard_modifiers); keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; - if (keymap_changed) { - ipc_event_input("xkb_keymap", - keyboard->seat_device->input_device); - } else if (effective_layout_changed) { - ipc_event_input("xkb_layout", - keyboard->seat_device->input_device); - } } void sway_keyboard_destroy(struct sway_keyboard *keyboard) { diff --git a/sway/input/libinput.c b/sway/input/libinput.c index 0266c7a93..fd7533d99 100644 --- a/sway/input/libinput.c +++ b/sway/input/libinput.c @@ -132,6 +132,16 @@ static bool set_click_method(struct libinput_device *device, return true; } +static bool set_clickfinger_button_map(struct libinput_device *device, + enum libinput_config_clickfinger_button_map map) { + if (libinput_device_config_click_get_clickfinger_button_map(device) == map) { + return false; + } + sway_log(SWAY_DEBUG, "clickfinger_set_button_map(%d)", map); + log_status(libinput_device_config_click_set_clickfinger_button_map(device, map)); + return true; +} + static bool set_middle_emulation(struct libinput_device *dev, enum libinput_config_middle_emulation_state mid) { if (!libinput_device_config_middle_emulation_is_available(dev) || @@ -281,6 +291,9 @@ bool sway_input_configure_libinput_device(struct sway_input_device *input_device if (ic->click_method != INT_MIN) { changed |= set_click_method(device, ic->click_method); } + if (ic->clickfinger_button_map != INT_MIN) { + changed |= set_clickfinger_button_map(device, ic->clickfinger_button_map); + } if (ic->middle_emulation != INT_MIN) { changed |= set_middle_emulation(device, ic->middle_emulation); } @@ -356,6 +369,8 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) { libinput_device_config_left_handed_get_default(device)); changed |= set_click_method(device, libinput_device_config_click_get_default_method(device)); + changed |= set_clickfinger_button_map(device, + libinput_device_config_click_get_default_clickfinger_button_map(device)); changed |= set_middle_emulation(device, libinput_device_config_middle_emulation_get_default_enabled(device)); changed |= set_scroll_method(device, @@ -376,6 +391,19 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) { } } +static bool sway_udev_device_is_builtin(struct udev_device *udev_device) { + const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH"); + if (!id_path) { + return false; + } + + if (has_prefix(id_path, "platform-")) { + return true; + } + + return has_prefix(id_path, "pci-") && strstr(id_path, "-platform-"); +} + bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) { if (!wlr_input_device_is_libinput(sway_device->wlr_device)) { return false; @@ -389,18 +417,7 @@ bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) { return false; } - const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH"); - if (!id_path) { - return false; - } - - const char prefix_platform[] = "platform-"; - if (strncmp(id_path, prefix_platform, strlen(prefix_platform)) != 0) { - return false; - } - - const char prefix_pci[] = "pci-"; - const char infix_platform[] = "-platform-"; - return (strncmp(id_path, prefix_pci, strlen(prefix_pci)) == 0) && - strstr(id_path, infix_platform); + bool is_builtin = sway_udev_device_is_builtin(udev_device); + udev_device_unref(udev_device); + return is_builtin; } diff --git a/sway/input/seat.c b/sway/input/seat.c index 7ef06509c..83bb93fc7 100644 --- a/sway/input/seat.c +++ b/sway/input/seat.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include "config.h" #include "list.h" #include "log.h" #include "sway/config.h" @@ -190,7 +190,7 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) { node->sway_container->view : NULL; if (view && seat_is_input_allowed(seat, view->surface)) { -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (view->type == SWAY_VIEW_XWAYLAND) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); @@ -647,10 +647,6 @@ static void seat_reset_input_config(struct sway_seat *seat, sway_device->input_device->wlr_device, NULL); } -static bool has_prefix(const char *str, const char *prefix) { - return strncmp(str, prefix, strlen(prefix)) == 0; -} - /** * Get the name of the built-in output, if any. Returns NULL if there isn't * exactly one built-in output. @@ -802,11 +798,10 @@ static void seat_configure_keyboard(struct sway_seat *seat, return; } - // force notify reenter to pick up the new configuration. This reuses + // Notify reenter to pick up the new configuration. This reuses // the current focused surface to avoid breaking input grabs. struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface; if (surface) { - wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); seat_keyboard_notify_enter(seat, surface); } } @@ -1002,7 +997,7 @@ void seat_configure_xcursor(struct sway_seat *seat) { setenv("XCURSOR_THEME", cursor_theme, 1); } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (server.xwayland.wlr_xwayland && (!server.xwayland.xcursor_manager || !xcursor_manager_is_named(server.xwayland.xcursor_manager, cursor_theme) || @@ -1020,9 +1015,9 @@ void seat_configure_xcursor(struct sway_seat *seat) { server.xwayland.xcursor_manager, "default", 1); if (xcursor != NULL) { struct wlr_xcursor_image *image = xcursor->images[0]; + struct wlr_buffer *buffer = wlr_xcursor_image_get_buffer(image); wlr_xwayland_set_cursor( - server.xwayland.wlr_xwayland, image->buffer, - image->width * 4, image->width, image->height, + server.xwayland.wlr_xwayland, buffer, image->hotspot_x, image->hotspot_y); } } @@ -1076,6 +1071,7 @@ bool seat_is_input_allowed(struct sway_seat *seat, static void send_unfocus(struct sway_container *con, void *data) { if (con->view) { + view_close_popups(con->view); view_set_activated(con->view, false); } } @@ -1095,6 +1091,7 @@ static void seat_send_unfocus(struct sway_node *node, struct sway_seat *seat) { static int handle_urgent_timeout(void *data) { struct sway_view *view = data; view_set_urgent(view, false); + container_update_itself_and_parents(view->container); return 0; } @@ -1136,13 +1133,7 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n return; } - struct sway_workspace *last_workspace = seat_get_focused_workspace(seat); - if (node == NULL) { - // Close any popups on the old focus - if (node_is_view(last_focus)) { - view_close_popups(last_focus->sway_container->view); - } seat_send_unfocus(last_focus, seat); sway_input_method_relay_set_focus(&seat->im_relay, NULL); seat->has_focus = false; @@ -1164,17 +1155,15 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n return; } + // Find the output's last workspace, which might have to be removed if empty struct sway_output *new_output = new_workspace ? new_workspace->output : NULL; - + struct sway_workspace *last_workspace = + new_output ? output_get_active_workspace(new_output) : NULL; if (last_workspace != new_workspace && new_output) { node_set_dirty(&new_output->node); } - // find new output's old workspace, which might have to be removed if empty - struct sway_workspace *new_output_last_ws = - new_output ? output_get_active_workspace(new_output) : NULL; - // Unfocus the previous focus if (last_focus) { seat_send_unfocus(last_focus, seat); @@ -1208,12 +1197,21 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n ipc_event_window(container, "focus"); } + if (last_workspace && last_workspace != new_workspace) { + wlr_ext_workspace_handle_v1_set_active(last_workspace->ext_workspace, + workspace_is_visible(last_workspace)); + } + if (new_workspace) { + wlr_ext_workspace_handle_v1_set_active(new_workspace->ext_workspace, + workspace_is_visible(new_workspace)); + } + // Move sticky containers to new workspace - if (new_workspace && new_output_last_ws - && new_workspace != new_output_last_ws) { - for (int i = 0; i < new_output_last_ws->floating->length; ++i) { + if (new_workspace && last_workspace + && new_workspace != last_workspace) { + for (int i = 0; i < last_workspace->floating->length; ++i) { struct sway_container *floater = - new_output_last_ws->floating->items[i]; + last_workspace->floating->items[i]; if (container_is_sticky(floater)) { container_detach(floater); workspace_add_floating(new_workspace, floater); @@ -1222,11 +1220,6 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n } } - // Close any popups on the old focus - if (last_focus && node_is_view(last_focus)) { - view_close_popups(last_focus->sway_container->view); - } - // If urgent, either unset the urgency or start a timer to unset it if (container && container->view && view_is_urgent(container->view) && !container->view->urgent_timer) { @@ -1247,13 +1240,9 @@ static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *n } } - if (new_output_last_ws) { - workspace_consider_destroy(new_output_last_ws); - } - if (last_workspace && last_workspace != new_output_last_ws) { + if (last_workspace) { workspace_consider_destroy(last_workspace); } - seat->has_focus = true; if (config->smart_gaps && new_workspace) { diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c index 0c6f7c5e3..df8232afc 100644 --- a/sway/input/seatop_default.c +++ b/sway/input/seatop_default.c @@ -11,11 +11,13 @@ #include "sway/input/tablet.h" #include "sway/layers.h" #include "sway/output.h" +#include "sway/server.h" #include "sway/scene_descriptor.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "log.h" -#if HAVE_XWAYLAND +#include "util.h" +#if WLR_HAS_XWAYLAND #include "sway/xwayland.h" #endif @@ -234,7 +236,7 @@ static void handle_tablet_tool_tip(struct sway_seat *seat, node->sway_container : NULL; struct wlr_layer_surface_v1 *layer; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *xsurface; #endif if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface)) && @@ -268,11 +270,11 @@ static void handle_tablet_tool_tip(struct sway_seat *seat, seat_set_focus_container(seat, cont); seatop_begin_down(seat, node->sway_container, sx, sy); } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND // Handle tapping on an xwayland unmanaged view else if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) && xsurface->override_redirect && - wlr_xwayland_or_surface_wants_focus(xsurface)) { + wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); seat_set_focus_surface(seat, xsurface->surface, false); @@ -354,6 +356,13 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + bool mod_pressed = modifiers & config->floating_mod; + uint32_t mod_move_btn = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; + uint32_t mod_resize_btn = config->floating_mod_inverse ? BTN_LEFT : BTN_RIGHT; + bool mod_move_btn_pressed = mod_pressed && button == mod_move_btn; + bool mod_resize_btn_pressed = mod_pressed && button == mod_resize_btn; + bool titlebar_left_btn_pressed = on_titlebar && button == BTN_LEFT; + // Handle mouse bindings if (trigger_pointer_button_binding(seat, device, button, state, modifiers, on_titlebar, on_border, on_contents, on_workspace)) { @@ -402,33 +411,28 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, } // Handle tiling resize via mod - bool mod_pressed = modifiers & config->floating_mod; - if (cont && !is_floating_or_child && mod_pressed && + if (cont && !is_floating_or_child && mod_pressed && mod_resize_btn_pressed && state == WL_POINTER_BUTTON_STATE_PRESSED) { - uint32_t btn_resize = config->floating_mod_inverse ? - BTN_LEFT : BTN_RIGHT; - if (button == btn_resize) { - edge = 0; - edge |= cursor->cursor->x > cont->pending.x + cont->pending.width / 2 ? - WLR_EDGE_RIGHT : WLR_EDGE_LEFT; - edge |= cursor->cursor->y > cont->pending.y + cont->pending.height / 2 ? - WLR_EDGE_BOTTOM : WLR_EDGE_TOP; + edge = 0; + edge |= cursor->cursor->x > cont->pending.x + cont->pending.width / 2 ? + WLR_EDGE_RIGHT : WLR_EDGE_LEFT; + edge |= cursor->cursor->y > cont->pending.y + cont->pending.height / 2 ? + WLR_EDGE_BOTTOM : WLR_EDGE_TOP; - const char *image = NULL; - if (edge == (WLR_EDGE_LEFT | WLR_EDGE_TOP)) { - image = "nw-resize"; - } else if (edge == (WLR_EDGE_TOP | WLR_EDGE_RIGHT)) { - image = "ne-resize"; - } else if (edge == (WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM)) { - image = "se-resize"; - } else if (edge == (WLR_EDGE_BOTTOM | WLR_EDGE_LEFT)) { - image = "sw-resize"; - } - cursor_set_image(seat->cursor, image, NULL); - seat_set_focus_container(seat, cont); - seatop_begin_resize_tiling(seat, cont, edge); - return; + const char *image = NULL; + if (edge == (WLR_EDGE_LEFT | WLR_EDGE_TOP)) { + image = "nw-resize"; + } else if (edge == (WLR_EDGE_TOP | WLR_EDGE_RIGHT)) { + image = "ne-resize"; + } else if (edge == (WLR_EDGE_RIGHT | WLR_EDGE_BOTTOM)) { + image = "se-resize"; + } else if (edge == (WLR_EDGE_BOTTOM | WLR_EDGE_LEFT)) { + image = "sw-resize"; } + cursor_set_image(seat->cursor, image, NULL); + seat_set_focus_container(seat, cont); + seatop_begin_resize_tiling(seat, cont, edge); + return; } // Handle changing focus when clicking on a container @@ -453,12 +457,10 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, // Handle beginning floating move if (cont && is_floating_or_child && !is_fullscreen_or_child && - state == WL_POINTER_BUTTON_STATE_PRESSED) { - uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; - if (button == btn_move && (mod_pressed || on_titlebar)) { - seatop_begin_move_floating(seat, container_toplevel_ancestor(cont)); - return; - } + state == WL_POINTER_BUTTON_STATE_PRESSED && + (mod_move_btn_pressed || titlebar_left_btn_pressed)) { + seatop_begin_move_floating(seat, container_toplevel_ancestor(cont)); + return; } // Handle beginning floating resize @@ -472,9 +474,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, } // Via mod+click - uint32_t btn_resize = config->floating_mod_inverse ? - BTN_LEFT : BTN_RIGHT; - if (mod_pressed && button == btn_resize) { + if (mod_resize_btn_pressed) { struct sway_container *floater = container_toplevel_ancestor(cont); edge = 0; edge |= cursor->cursor->x > floater->pending.x + floater->pending.width / 2 ? @@ -488,7 +488,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, } // Handle moving a tiling container - if (config->tiling_drag && (mod_pressed || on_titlebar) && + if (config->tiling_drag && (mod_move_btn_pressed || titlebar_left_btn_pressed) && state == WL_POINTER_BUTTON_STATE_PRESSED && !is_floating_or_child && cont && cont->pending.fullscreen_mode == FULLSCREEN_NONE) { // If moving a container by its title bar, use a threshold for the drag @@ -497,7 +497,6 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, } else { seatop_begin_move_tiling(seat, cont); } - return; } @@ -514,13 +513,13 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec, return; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND // Handle clicking on xwayland unmanaged view struct wlr_xwayland_surface *xsurface; if (surface && (xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) && xsurface->override_redirect && - wlr_xwayland_or_surface_wants_focus(xsurface)) { + wlr_xwayland_surface_override_redirect_wants_focus(xsurface)) { struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; wlr_xwayland_set_seat(xwayland, seat->wlr_seat); seat_set_focus_surface(seat, xsurface->surface, false); @@ -666,7 +665,7 @@ static void handle_touch_down(struct sway_seat *seat, double sx, sy; node_at_coords(seat, seat->touch_x, seat->touch_y, &surface, &sx, &sy); - if (surface && wlr_surface_accepts_touch(wlr_seat, surface)) { + if (surface && wlr_surface_accepts_touch(surface, wlr_seat)) { if (seat_is_input_allowed(seat, surface)) { cursor->simulating_pointer_from_touch = false; seatop_begin_touch_down(seat, surface, event, sx, sy, lx, ly); @@ -795,7 +794,7 @@ static void handle_pointer_axis(struct sway_seat *seat, if (!handled) { wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, - event->orientation, scroll_factor * event->delta, + event->orientation, scroll_factor * event->delta, roundf(scroll_factor * event->delta_discrete), event->source, event->relative_direction); } @@ -1112,7 +1111,7 @@ static void handle_rebase(struct sway_seat *seat, uint32_t time_msec) { cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); if (surface) { - if (seat_is_input_allowed(seat, surface)) { + if (seat_is_input_allowed(seat, surface) && !cursor->hidden) { wlr_seat_pointer_notify_enter(seat->wlr_seat, surface, sx, sy); wlr_seat_pointer_notify_motion(seat->wlr_seat, time_msec, sx, sy); } @@ -1150,5 +1149,7 @@ void seatop_begin_default(struct sway_seat *seat) { seat->seatop_impl = &seatop_impl; seat->seatop_data = e; - seatop_rebase(seat, 0); + + uint32_t time_msec = get_current_time_in_msec(); + seatop_rebase(seat, time_msec); } diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c index 35fd3bcb2..f64e5a4f2 100644 --- a/sway/input/seatop_down.c +++ b/sway/input/seatop_down.c @@ -100,6 +100,7 @@ static void handle_touch_down(struct sway_seat *seat, if (focused_node) { seat_set_focus(seat, focused_node); + transaction_commit_dirty(); } } @@ -117,7 +118,11 @@ static void handle_touch_cancel(struct sway_seat *seat, } if (e->surface) { - wlr_seat_touch_notify_cancel(seat->wlr_seat, e->surface); + struct wl_client *client = wl_resource_get_client(e->surface->resource); + struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat->wlr_seat, client); + if (seat_client != NULL) { + wlr_seat_touch_notify_cancel(seat->wlr_seat, seat_client); + } } if (wl_list_empty(&e->point_events)) { diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c index c525b77a9..025197852 100644 --- a/sway/input/seatop_move_tiling.c +++ b/sway/input/seatop_move_tiling.c @@ -234,26 +234,31 @@ static void handle_motion_postthreshold(struct sway_seat *seat) { if (layout == L_HORIZ || layout == L_TABBED) { if (cursor->cursor->y < thresh_top) { edge = WLR_EDGE_TOP; + if (thresh_top < box.y) thresh_top = box.y; box.height = thresh_top - box.y; } else if (cursor->cursor->y > thresh_bottom) { edge = WLR_EDGE_BOTTOM; + if (thresh_bottom > box.y + box.height) thresh_bottom = box.y + box.height; box.height = box.y + box.height - thresh_bottom; box.y = thresh_bottom; } } else if (layout == L_VERT || layout == L_STACKED) { if (cursor->cursor->x < thresh_left) { edge = WLR_EDGE_LEFT; + if (thresh_left < box.x) thresh_left = box.x; box.width = thresh_left - box.x; } else if (cursor->cursor->x > thresh_right) { edge = WLR_EDGE_RIGHT; + if (thresh_right > box.x + box.width) thresh_right = box.x + box.width; box.width = box.x + box.width - thresh_right; box.x = thresh_right; } } if (edge) { e->target_node = node_get_parent(&con->node); - if (e->target_node == &e->con->node) { - e->target_node = node_get_parent(e->target_node); + if (e->target_node && (e->target_node == &e->con->node || + node_has_ancestor(e->target_node, &e->con->node))) { + e->target_node = node_get_parent(&e->con->node); } e->target_edge = edge; update_indicator(e, &box); @@ -373,7 +378,7 @@ static void finalize_move(struct sway_seat *seat) { enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; workspace_split(new_ws, new_layout); - workspace_insert_tiling(new_ws, con, after); + workspace_insert_tiling(new_ws, con, after ? new_ws->tiling->length : 0); } if (old_parent) { diff --git a/sway/input/seatop_resize_tiling.c b/sway/input/seatop_resize_tiling.c index 15fd333b3..be7b3c128 100644 --- a/sway/input/seatop_resize_tiling.c +++ b/sway/input/seatop_resize_tiling.c @@ -105,10 +105,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { static void handle_unref(struct sway_seat *seat, struct sway_container *con) { struct seatop_resize_tiling_event *e = seat->seatop_data; - if (e->con == con) { - seatop_begin_default(seat); - } - if (e->h_sib == con || e->v_sib == con) { + if (e->con == con || e->h_sib == con || e->v_sib == con) { seatop_begin_default(seat); } } diff --git a/sway/input/switch.c b/sway/input/switch.c index 831f4dbf6..6aab4ad0d 100644 --- a/sway/input/switch.c +++ b/sway/input/switch.c @@ -1,5 +1,6 @@ #include "sway/config.h" #include "sway/input/switch.h" +#include "sway/server.h" #include "log.h" struct sway_switch *sway_switch_create(struct sway_seat *seat, diff --git a/sway/input/tablet.c b/sway/input/tablet.c index 2863642a7..19d5debf9 100644 --- a/sway/input/tablet.c +++ b/sway/input/tablet.c @@ -7,6 +7,7 @@ #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/input/tablet.h" +#include "sway/server.h" #if WLR_HAS_LIBINPUT_BACKEND #include @@ -362,7 +363,7 @@ void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad, } if (surface == NULL || - !wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) { + !wlr_surface_accepts_tablet_v2(surface, tablet_pad->tablet->tablet_v2)) { return; } diff --git a/sway/input/text_input.c b/sway/input/text_input.c index c38a3bb20..e496bff1b 100644 --- a/sway/input/text_input.c +++ b/sway/input/text_input.c @@ -9,7 +9,8 @@ #include "sway/input/text_input.h" #include "sway/input/text_input_popup.h" #include "sway/layers.h" -static void input_popup_update(struct sway_input_popup *popup); +#include "sway/server.h" +#include static struct sway_text_input *relay_get_focusable_text_input( struct sway_input_method_relay *relay) { @@ -41,23 +42,21 @@ static void handle_im_commit(struct wl_listener *listener, void *data) { if (!text_input) { return; } - struct wlr_input_method_v2 *context = data; - assert(context == relay->input_method); - if (context->current.preedit.text) { + if (relay->input_method->current.preedit.text) { wlr_text_input_v3_send_preedit_string(text_input->input, - context->current.preedit.text, - context->current.preedit.cursor_begin, - context->current.preedit.cursor_end); + relay->input_method->current.preedit.text, + relay->input_method->current.preedit.cursor_begin, + relay->input_method->current.preedit.cursor_end); } - if (context->current.commit_text) { + if (relay->input_method->current.commit_text) { wlr_text_input_v3_send_commit_string(text_input->input, - context->current.commit_text); + relay->input_method->current.commit_text); } - if (context->current.delete.before_length - || context->current.delete.after_length) { + if (relay->input_method->current.delete.before_length + || relay->input_method->current.delete.after_length) { wlr_text_input_v3_send_delete_surrounding_text(text_input->input, - context->current.delete.before_length, - context->current.delete.after_length); + relay->input_method->current.delete.before_length, + relay->input_method->current.delete.after_length); } wlr_text_input_v3_send_done(text_input->input); } @@ -65,12 +64,14 @@ static void handle_im_commit(struct wl_listener *listener, void *data) { static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_keyboard_grab_destroy); - struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = relay->input_method->keyboard_grab; + struct wlr_seat *wlr_seat = keyboard_grab->input_method->seat; wl_list_remove(&relay->input_method_keyboard_grab_destroy.link); if (keyboard_grab->keyboard) { // send modifier state to original client - wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat, + wlr_seat_set_keyboard(wlr_seat, keyboard_grab->keyboard); + wlr_seat_keyboard_notify_modifiers(wlr_seat, &keyboard_grab->keyboard->modifiers); } } @@ -107,8 +108,6 @@ static void text_input_set_pending_focused_surface( static void handle_im_destroy(struct wl_listener *listener, void *data) { struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_destroy); - struct wlr_input_method_v2 *context = data; - assert(context == relay->input_method); wl_list_remove(&relay->input_method_commit.link); wl_list_remove(&relay->input_method_grab_keyboard.link); wl_list_remove(&relay->input_method_destroy.link); @@ -124,6 +123,89 @@ static void handle_im_destroy(struct wl_listener *listener, void *data) { } } +static void constrain_popup(struct sway_input_popup *popup) { + struct sway_text_input *text_input = + relay_get_focused_text_input(popup->relay); + + if (!popup->desc.relative) { + return; + } + + struct wlr_box parent = {0}; + wlr_scene_node_coords(&popup->desc.relative->parent->node, &parent.x, &parent.y); + + struct wlr_box geo = {0}; + struct wlr_output *output; + + if (popup->desc.view) { + struct sway_view *view = popup->desc.view; + output = wlr_output_layout_output_at(root->output_layout, + view->container->pending.content_x + view->geometry.x, + view->container->pending.content_y + view->geometry.y); + + parent.width = view->geometry.width; + parent.height = view->geometry.height; + geo = view->geometry; + } else { + output = popup->fixed_output; + } + + struct wlr_box output_box; + wlr_output_layout_get_box(root->output_layout, output, &output_box); + + bool cursor_rect = text_input->input->current.features & + WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; + struct wlr_box cursor_area; + if (cursor_rect) { + cursor_area = text_input->input->current.cursor_rectangle; + } else { + cursor_area = (struct wlr_box) { + .width = parent.width, + .height = parent.height, + }; + } + + int popup_width = popup->popup_surface->surface->current.width; + int popup_height = popup->popup_surface->surface->current.height; + int x1 = parent.x + cursor_area.x; + int x2 = parent.x + cursor_area.x + cursor_area.width; + int y1 = parent.y + cursor_area.y; + int y2 = parent.y + cursor_area.y + cursor_area.height; + int x = x1; + int y = y2; + + int available_right = output_box.x + output_box.width - x1; + int available_left = x2 - output_box.x; + if (available_right < popup_width && available_left > available_right) { + x = x2 - popup_width; + } + + int available_down = output_box.y + output_box.height - y2; + int available_up = y1 - output_box.y; + if (available_down < popup_height && available_up > available_down) { + y = y1 - popup_height; + } + + wlr_scene_node_set_position(popup->desc.relative, x - parent.x - geo.x, y - parent.y - geo.y); + if (cursor_rect) { + struct wlr_box box = { + .x = x1 - x, + .y = y1 - y, + .width = cursor_area.width, + .height = cursor_area.height, + }; + wlr_input_popup_surface_v2_send_text_input_rectangle( + popup->popup_surface, &box); + } + + if (popup->scene_tree) { + wlr_scene_node_set_position(&popup->scene_tree->node, x - geo.x, y - geo.y); + } +} + +static void input_popup_set_focus(struct sway_input_popup *popup, + struct wlr_surface *surface); + static void relay_send_im_state(struct sway_input_method_relay *relay, struct wlr_text_input_v3 *input) { struct wlr_input_method_v2 *input_method = relay->input_method; @@ -144,10 +226,16 @@ static void relay_send_im_state(struct sway_input_method_relay *relay, input->current.content_type.hint, input->current.content_type.purpose); } + + struct sway_text_input *text_input = relay_get_focused_text_input(relay); + struct sway_input_popup *popup; wl_list_for_each(popup, &relay->input_popups, link) { - // send_text_input_rectangle is called in this function - input_popup_update(popup); + if (text_input != NULL) { + input_popup_set_focus(popup, text_input->input->focused_surface); + } else { + input_popup_set_focus(popup, NULL); + } } wlr_input_method_v2_send_done(input_method); // TODO: pass intent, display popup size @@ -156,6 +244,10 @@ static void relay_send_im_state(struct sway_input_method_relay *relay, static void handle_text_input_enable(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_enable); + if (text_input->input->focused_surface == NULL) { + sway_log(SWAY_DEBUG, "Enabling text input, but no longer focused"); + return; + } if (text_input->relay->input_method == NULL) { sway_log(SWAY_INFO, "Enabling text input when input method is gone"); return; @@ -168,6 +260,10 @@ static void handle_text_input_commit(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, text_input_commit); + if (text_input->input->focused_surface == NULL) { + sway_log(SWAY_DEBUG, "Unfocused text input tried to commit an update"); + return; + } if (!text_input->input->current_enabled) { sway_log(SWAY_INFO, "Inactive text input tried to commit an update"); return; @@ -222,8 +318,6 @@ static void handle_pending_focused_surface_destroy(struct wl_listener *listener, void *data) { struct sway_text_input *text_input = wl_container_of(listener, text_input, pending_focused_surface_destroy); - struct wlr_surface *surface = data; - assert(text_input->pending_focused_surface == surface); text_input->pending_focused_surface = NULL; wl_list_remove(&text_input->pending_focused_surface_destroy.link); wl_list_init(&text_input->pending_focused_surface_destroy.link); @@ -271,72 +365,76 @@ static void relay_handle_text_input(struct wl_listener *listener, sway_text_input_create(relay, wlr_text_input); } -static void input_popup_update(struct sway_input_popup *popup) { - struct sway_text_input *text_input = - relay_get_focused_text_input(popup->relay); +static void input_popup_set_focus(struct sway_input_popup *popup, + struct wlr_surface *surface) { + wl_list_remove(&popup->focused_surface_unmap.link); - if (text_input == NULL || text_input->input->focused_surface == NULL) { + if (!popup->scene_tree) { + wl_list_init(&popup->focused_surface_unmap.link); return; } - if (popup->scene_tree != NULL) { - wlr_scene_node_destroy(&popup->scene_tree->node); - popup->scene_tree = NULL; - } - if (popup->desc.relative != NULL) { + if (popup->desc.relative) { + scene_descriptor_destroy(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP); wlr_scene_node_destroy(popup->desc.relative); popup->desc.relative = NULL; } - popup->desc.view = NULL; - if (!popup->popup_surface->surface->mapped) { + if (surface == NULL) { + wl_list_init(&popup->focused_surface_unmap.link); + wlr_scene_node_set_enabled(&popup->scene_tree->node, false); return; } - bool cursor_rect = text_input->input->current.features - & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE; - struct wlr_surface *focused_surface = text_input->input->focused_surface; - struct wlr_box cursor_area = text_input->input->current.cursor_rectangle; - - struct wlr_box output_box; - struct wlr_box parent = {0}; struct wlr_layer_surface_v1 *layer_surface = - wlr_layer_surface_v1_try_from_wlr_surface(focused_surface); + wlr_layer_surface_v1_try_from_wlr_surface(surface); + struct wlr_session_lock_surface_v1 *lock_surface = + wlr_session_lock_surface_v1_try_from_wlr_surface(surface); + struct wlr_scene_tree *relative_parent; + if (layer_surface) { + wl_signal_add(&layer_surface->surface->events.unmap, + &popup->focused_surface_unmap); - struct wlr_box geo = {0}; - - popup->scene_tree = wlr_scene_subsurface_tree_create(root->layers.popup, popup->popup_surface->surface); - if (layer_surface != NULL) { - struct sway_layer_surface *layer = - layer_surface->data; + struct sway_layer_surface *layer = layer_surface->data; if (layer == NULL) { return; } relative_parent = layer->scene->tree; - struct wlr_output *output = layer->layer_surface->output; - wlr_output_layout_get_box(root->output_layout, output, &output_box); - int lx, ly; - wlr_scene_node_coords(&layer->tree->node, &lx, &ly); - parent.x = lx; - parent.y = ly; popup->desc.view = NULL; - } else { - struct sway_view *view = view_from_wlr_surface(focused_surface); - relative_parent = view->scene_tree; - geo = view->geometry; - int lx, ly; - wlr_scene_node_coords(&view->scene_tree->node, &lx, &ly); - struct wlr_output *output = wlr_output_layout_output_at(root->output_layout, - view->container->pending.content_x + view->geometry.x, - view->container->pending.content_y + view->geometry.y); - wlr_output_layout_get_box(root->output_layout, output, &output_box); - parent.x = lx; - parent.y = ly; - parent.width = view->geometry.width; - parent.height = view->geometry.height; + // we don't need to add an event here to NULL out this field because + // this field will only be initialized if the popup is part of a layer + // surface. Layer surfaces get destroyed as part of the output being + // destroyed, thus also trickling down to popups. + popup->fixed_output = layer->layer_surface->output; + } else if (lock_surface) { + wl_signal_add(&lock_surface->surface->events.unmap, + &popup->focused_surface_unmap); + + struct sway_layer_surface *lock = lock_surface->data; + if (lock == NULL) { + return; + } + + relative_parent = lock->scene->tree; + popup->desc.view = NULL; + + // we don't need to add an event here to NULL out this field because + // this field will only be initialized if the popup is part of a layer + // surface. Layer surfaces get destroyed as part of the output being + // destroyed, thus also trickling down to popups. + popup->fixed_output = lock->layer_surface->output; + } else { + struct sway_view *view = view_from_wlr_surface(surface); + // In the future there may be other shells been added, so we also need to check here. + if (view == NULL) { + sway_log(SWAY_DEBUG, "Unsupported IME focus surface"); + return; + } + wl_signal_add(&view->events.unmap, &popup->focused_surface_unmap); + relative_parent = view->scene_tree; popup->desc.view = view; } @@ -350,93 +448,59 @@ static void input_popup_update(struct sway_input_popup *popup) { return; } - if (!cursor_rect) { - cursor_area.x = 0; - cursor_area.y = 0; - cursor_area.width = parent.width; - cursor_area.height = parent.height; - } - - int popup_width = popup->popup_surface->surface->current.width; - int popup_height = popup->popup_surface->surface->current.height; - int x1 = parent.x + cursor_area.x; - int x2 = parent.x + cursor_area.x + cursor_area.width; - int y1 = parent.y + cursor_area.y; - int y2 = parent.y + cursor_area.y + cursor_area.height; - int x = x1; - int y = y2; - - int available_right = output_box.x + output_box.width - x1; - int available_left = x2 - output_box.x; - if (available_right < popup_width && available_left > available_right) { - x = x2 - popup_width; - } - - int available_down = output_box.y + output_box.height - y2; - int available_up = y1 - output_box.y; - if (available_down < popup_height && available_up > available_down) { - y = y1 - popup_height; - } - - wlr_scene_node_set_position(&relative->node, x - parent.x - geo.x, y - parent.y - geo.y); - if (cursor_rect) { - struct wlr_box box = { - .x = x1 - x, - .y = y1 - y, - .width = cursor_area.width, - .height = cursor_area.height, - }; - wlr_input_popup_surface_v2_send_text_input_rectangle( - popup->popup_surface, &box); - } - wlr_scene_node_set_position(&popup->scene_tree->node, x - geo.x, y - geo.y); -} - -static void input_popup_set_focus(struct sway_input_popup *popup, - struct wlr_surface *surface) { - wl_list_remove(&popup->focused_surface_unmap.link); - - if (surface == NULL) { - wl_list_init(&popup->focused_surface_unmap.link); - input_popup_update(popup); - return; - } - struct wlr_layer_surface_v1 *layer_surface = - wlr_layer_surface_v1_try_from_wlr_surface(surface); - if (layer_surface != NULL) { - wl_signal_add( - &layer_surface->surface->events.unmap, &popup->focused_surface_unmap); - input_popup_update(popup); - return; - } - - struct sway_view *view = view_from_wlr_surface(surface); - wl_signal_add(&view->events.unmap, &popup->focused_surface_unmap); + constrain_popup(popup); + wlr_scene_node_set_enabled(&popup->scene_tree->node, true); } static void handle_im_popup_destroy(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_destroy); + wlr_scene_node_destroy(&popup->scene_tree->node); wl_list_remove(&popup->focused_surface_unmap.link); wl_list_remove(&popup->popup_surface_commit.link); + wl_list_remove(&popup->popup_surface_map.link); + wl_list_remove(&popup->popup_surface_unmap.link); wl_list_remove(&popup->popup_destroy.link); wl_list_remove(&popup->link); free(popup); } -static void handle_im_popup_surface_commit(struct wl_listener *listener, - void *data) { +static void handle_im_popup_surface_map(struct wl_listener *listener, void *data) { + struct sway_input_popup *popup = + wl_container_of(listener, popup, popup_surface_map); + struct sway_text_input *text_input = relay_get_focused_text_input(popup->relay); + if (text_input != NULL) { + input_popup_set_focus(popup, text_input->input->focused_surface); + } else { + input_popup_set_focus(popup, NULL); + } +} + +static void handle_im_popup_surface_unmap(struct wl_listener *listener, void *data) { + struct sway_input_popup *popup = + wl_container_of(listener, popup, popup_surface_unmap); + + scene_descriptor_destroy(&popup->scene_tree->node, SWAY_SCENE_DESC_POPUP); + // relative should already be freed as it should be a child of the just unmapped scene + popup->desc.relative = NULL; + + input_popup_set_focus(popup, NULL); +} + +static void handle_im_popup_surface_commit(struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, popup_surface_commit); - input_popup_update(popup); + + constrain_popup(popup); } static void handle_im_focused_surface_unmap( struct wl_listener *listener, void *data) { struct sway_input_popup *popup = wl_container_of(listener, popup, focused_surface_unmap); - input_popup_update(popup); + + input_popup_set_focus(popup, NULL); } static void handle_im_new_popup_surface(struct wl_listener *listener, @@ -444,16 +508,38 @@ static void handle_im_new_popup_surface(struct wl_listener *listener, struct sway_input_method_relay *relay = wl_container_of(listener, relay, input_method_new_popup_surface); struct sway_input_popup *popup = calloc(1, sizeof(*popup)); + if (!popup) { + sway_log(SWAY_ERROR, "Failed to allocate an input method popup"); + return; + } + popup->relay = relay; popup->popup_surface = data; popup->popup_surface->data = popup; - wl_signal_add( - &popup->popup_surface->events.destroy, &popup->popup_destroy); + popup->scene_tree = wlr_scene_tree_create(root->layers.popup); + if (!popup->scene_tree) { + sway_log(SWAY_ERROR, "Failed to allocate scene tree"); + free(popup); + return; + } + + if (!wlr_scene_subsurface_tree_create(popup->scene_tree, + popup->popup_surface->surface)) { + sway_log(SWAY_ERROR, "Failed to allocate subsurface tree"); + wlr_scene_node_destroy(&popup->scene_tree->node); + free(popup); + return; + } + + wl_signal_add(&popup->popup_surface->events.destroy, &popup->popup_destroy); popup->popup_destroy.notify = handle_im_popup_destroy; - wl_signal_add(&popup->popup_surface->surface->events.commit, - &popup->popup_surface_commit); + wl_signal_add(&popup->popup_surface->surface->events.commit, &popup->popup_surface_commit); popup->popup_surface_commit.notify = handle_im_popup_surface_commit; + wl_signal_add(&popup->popup_surface->surface->events.map, &popup->popup_surface_map); + popup->popup_surface_map.notify = handle_im_popup_surface_map; + wl_signal_add(&popup->popup_surface->surface->events.unmap, &popup->popup_surface_unmap); + popup->popup_surface_unmap.notify = handle_im_popup_surface_unmap; wl_list_init(&popup->focused_surface_unmap.link); popup->focused_surface_unmap.notify = handle_im_focused_surface_unmap; @@ -513,6 +599,34 @@ static void relay_handle_input_method(struct wl_listener *listener, } } +static void sway_input_method_relay_finish_text_input(struct sway_input_method_relay *relay) { + wl_list_remove(&relay->text_input_new.link); + wl_list_remove(&relay->text_input_manager_destroy.link); + wl_list_init(&relay->text_input_new.link); + wl_list_init(&relay->text_input_manager_destroy.link); +} + +static void relay_handle_text_input_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + text_input_manager_destroy); + + sway_input_method_relay_finish_text_input(relay); +} + +static void sway_input_method_relay_finish_input_method(struct sway_input_method_relay *relay) { + wl_list_remove(&relay->input_method_new.link); + wl_list_remove(&relay->input_method_manager_destroy.link); + wl_list_init(&relay->input_method_new.link); + wl_list_init(&relay->input_method_manager_destroy.link); +} + +static void relay_handle_input_method_manager_destroy(struct wl_listener *listener, void *data) { + struct sway_input_method_relay *relay = wl_container_of(listener, relay, + input_method_manager_destroy); + + sway_input_method_relay_finish_input_method(relay); +} + void sway_input_method_relay_init(struct sway_seat *seat, struct sway_input_method_relay *relay) { relay->seat = seat; @@ -520,18 +634,24 @@ void sway_input_method_relay_init(struct sway_seat *seat, wl_list_init(&relay->input_popups); relay->text_input_new.notify = relay_handle_text_input; - wl_signal_add(&server.text_input->events.text_input, + wl_signal_add(&server.text_input->events.new_text_input, &relay->text_input_new); + relay->text_input_manager_destroy.notify = relay_handle_text_input_manager_destroy; + wl_signal_add(&server.text_input->events.destroy, + &relay->text_input_manager_destroy); relay->input_method_new.notify = relay_handle_input_method; wl_signal_add( - &server.input_method->events.input_method, + &server.input_method->events.new_input_method, &relay->input_method_new); + relay->input_method_manager_destroy.notify = relay_handle_input_method_manager_destroy; + wl_signal_add(&server.input_method->events.destroy, + &relay->input_method_manager_destroy); } void sway_input_method_relay_finish(struct sway_input_method_relay *relay) { - wl_list_remove(&relay->input_method_new.link); - wl_list_remove(&relay->text_input_new.link); + sway_input_method_relay_finish_text_input(relay); + sway_input_method_relay_finish_input_method(relay); } void sway_input_method_relay_set_focus(struct sway_input_method_relay *relay, diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 81ca34831..3b69ad384 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -6,11 +6,13 @@ #include #include #include +#include #include #include "config.h" #include "log.h" #include "sway/config.h" #include "sway/ipc-json.h" +#include "sway/server.h" #include "sway/tree/container.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" @@ -154,7 +156,7 @@ static json_object *ipc_json_output_mode_description( return mode_object; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND static const char *ipc_json_xwindow_type_description(struct sway_view *view) { struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; struct sway_xwayland *xwayland = &server.xwayland; @@ -316,6 +318,14 @@ static void ipc_json_describe_wlr_output(struct wlr_output *wlr_output, json_obj json_object_array_add(modes_array, mode_object); } json_object_object_add(object, "modes", modes_array); + + json_object *features_object = json_object_new_object(); + json_object_object_add(features_object, "adaptive_sync", + json_object_new_boolean(wlr_output->adaptive_sync_supported || + wlr_output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)); + json_object_object_add(features_object, "hdr", + json_object_new_boolean(output_supports_hdr(wlr_output, NULL))); + json_object_object_add(object, "features", features_object); } static void ipc_json_describe_output(struct sway_output *output, @@ -398,6 +408,8 @@ static void ipc_json_describe_enabled_output(struct sway_output *output, } json_object_object_add(object, "max_render_time", json_object_new_int(output->max_render_time)); + json_object_object_add(object, "allow_tearing", json_object_new_boolean(output->allow_tearing)); + json_object_object_add(object, "hdr", json_object_new_boolean(output->hdr)); } json_object *ipc_json_describe_disabled_output(struct sway_output *output) { @@ -574,12 +586,17 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object json_object_object_add(object, "app_id", app_id ? json_object_new_string(app_id) : NULL); + json_object_object_add(object, "foreign_toplevel_identifier", + c->view->ext_foreign_toplevel ? + json_object_new_string(c->view->ext_foreign_toplevel->identifier) : NULL); + bool visible = view_is_visible(c->view); json_object_object_add(object, "visible", json_object_new_boolean(visible)); + bool has_titlebar = c->title_bar.tree->node.enabled; struct wlr_box window_box = { c->pending.content_x - c->pending.x, - (c->current.border == B_PIXEL) ? c->pending.content_y - c->pending.y : 0, + has_titlebar ? 0 : c->pending.content_y - c->pending.y, c->pending.content_width, c->pending.content_height }; @@ -591,11 +608,28 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object json_object_object_add(object, "max_render_time", json_object_new_int(c->view->max_render_time)); + json_object_object_add(object, "allow_tearing", json_object_new_boolean(view_can_tear(c->view))); + json_object_object_add(object, "shell", json_object_new_string(view_get_shell(c->view))); json_object_object_add(object, "inhibit_idle", json_object_new_boolean(view_inhibit_idle(c->view))); + const char *sandbox_engine = view_get_sandbox_engine(c->view); + json_object_object_add(object, "sandbox_engine", + sandbox_engine ? json_object_new_string(sandbox_engine) : NULL); + + const char *sandbox_app_id = view_get_sandbox_app_id(c->view); + json_object_object_add(object, "sandbox_app_id", + sandbox_app_id ? json_object_new_string(sandbox_app_id) : NULL); + + const char *sandbox_instance_id = view_get_sandbox_instance_id(c->view); + json_object_object_add(object, "sandbox_instance_id", + sandbox_instance_id ? json_object_new_string(sandbox_instance_id) : NULL); + + const char *tag = view_get_tag(c->view); + json_object_object_add(object, "tag", tag ? json_object_new_string(tag) : NULL); + json_object *idle_inhibitors = json_object_new_object(); struct sway_idle_inhibitor_v1 *user_inhibitor = @@ -633,7 +667,7 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object json_object_new_string(ipc_json_content_type_description(content_type))); } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (c->view->type == SWAY_VIEW_XWAYLAND) { json_object_object_add(object, "window", json_object_new_int(view_get_x11_window_id(c->view))); @@ -925,6 +959,11 @@ static json_object *describe_libinput_device(struct libinput_device *device) { case LIBINPUT_CONFIG_DRAG_LOCK_DISABLED: drag_lock = "disabled"; break; +#if HAVE_LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY + case LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY: + drag_lock = "enabled_sticky"; + break; +#endif } json_object_object_add(object, "tap_drag_lock", json_object_new_string(drag_lock)); @@ -990,6 +1029,18 @@ static json_object *describe_libinput_device(struct libinput_device *device) { } json_object_object_add(object, "click_method", json_object_new_string(click_method)); + + const char *button_map = "unknown"; + switch (libinput_device_config_click_get_clickfinger_button_map(device)) { + case LIBINPUT_CONFIG_CLICKFINGER_MAP_LRM: + button_map = "lrm"; + break; + case LIBINPUT_CONFIG_CLICKFINGER_MAP_LMR: + button_map = "lmr"; + break; + } + json_object_object_add(object, "clickfinger_button_map", + json_object_new_string(button_map)); } if (libinput_device_config_middle_emulation_is_available(device)) { @@ -1107,15 +1158,17 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) { struct xkb_keymap *keymap = keyboard->keymap; struct xkb_state *state = keyboard->xkb_state; - json_object_object_add(object, "repeat_delay", + json_object_object_add(object, "repeat_delay", json_object_new_int(keyboard->repeat_info.delay)); - json_object_object_add(object, "repeat_rate", + json_object_object_add(object, "repeat_rate", json_object_new_int(keyboard->repeat_info.rate)); json_object *layouts_arr = json_object_new_array(); json_object_object_add(object, "xkb_layout_names", layouts_arr); - xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(keymap); + xkb_layout_index_t num_layouts = + keymap ? xkb_keymap_num_layouts(keymap) : 0; + // Virtual keyboards might have null keymap xkb_layout_index_t layout_idx; for (layout_idx = 0; layout_idx < num_layouts; layout_idx++) { const char *layout = xkb_keymap_layout_get_name(keymap, layout_idx); diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 7f353c0ec..b934bb568 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -648,6 +648,12 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt } list_t *res_list = execute_command(buf, NULL, NULL); + if (modeset_is_pending()) { + // IPC expects commands to have taken immediate effect, so we need + // to force a modeset after output commands. We do a single modeset + // here to avoid modesetting for every output command in sequence. + force_modeset(); + } transaction_commit_dirty(); char *json = cmd_results_to_json(res_list); int length = strlen(json); diff --git a/sway/lock.c b/sway/lock.c index 289e8ca46..b1df65718 100644 --- a/sway/lock.c +++ b/sway/lock.c @@ -8,6 +8,7 @@ #include "sway/layers.h" #include "sway/output.h" #include "sway/server.h" +#include "sway/lock.h" struct sway_session_lock_output { struct wlr_scene_tree *tree; @@ -19,7 +20,6 @@ struct sway_session_lock_output { struct wl_list link; // sway_session_lock::outputs struct wl_listener destroy; - struct wl_listener commit; struct wlr_session_lock_surface_v1 *surface; @@ -89,6 +89,17 @@ static void lock_output_reconfigure(struct sway_session_lock_output *output) { } } +void arrange_locks(void) { + if (server.session_lock.lock == NULL) { + return; + } + + struct sway_session_lock_output *lock_output; + wl_list_for_each(lock_output, &server.session_lock.lock->outputs, link) { + lock_output_reconfigure(lock_output); + } +} + static void handle_new_surface(struct wl_listener *listener, void *data) { struct sway_session_lock *lock = wl_container_of(listener, lock, new_surface); struct wlr_session_lock_surface_v1 *lock_surface = data; @@ -125,7 +136,6 @@ static void sway_session_lock_output_destroy(struct sway_session_lock_output *ou wl_list_remove(&output->surface_map.link); } - wl_list_remove(&output->commit.link); wl_list_remove(&output->destroy.link); wl_list_remove(&output->link); @@ -138,18 +148,6 @@ static void lock_node_handle_destroy(struct wl_listener *listener, void *data) { sway_session_lock_output_destroy(output); } -static void lock_output_handle_commit(struct wl_listener *listener, void *data) { - struct wlr_output_event_commit *event = data; - struct sway_session_lock_output *output = - wl_container_of(listener, output, commit); - if (event->state->committed & ( - WLR_OUTPUT_STATE_MODE | - WLR_OUTPUT_STATE_SCALE | - WLR_OUTPUT_STATE_TRANSFORM)) { - lock_output_reconfigure(output); - } -} - static struct sway_session_lock_output *session_lock_output_create( struct sway_session_lock *lock, struct sway_output *output) { struct sway_session_lock_output *lock_output = calloc(1, sizeof(*lock_output)); @@ -186,9 +184,6 @@ static struct sway_session_lock_output *session_lock_output_create( lock_output->destroy.notify = lock_node_handle_destroy; wl_signal_add(&tree->node.events.destroy, &lock_output->destroy); - lock_output->commit.notify = lock_output_handle_commit; - wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit); - lock_output_reconfigure(lock_output); wl_list_insert(&lock->outputs, &lock_output->link); @@ -239,6 +234,9 @@ static void handle_unlock(struct wl_listener *listener, void *data) { struct sway_output *output = root->outputs->items[i]; arrange_layers(output); } + + // Views are now visible, so check if we need to activate inhibition again. + sway_idle_inhibit_v1_check_active(); } static void handle_abandon(struct wl_listener *listener, void *data) { @@ -302,6 +300,10 @@ static void handle_session_lock(struct wl_listener *listener, void *data) { wlr_session_lock_v1_send_locked(lock); server.session_lock.lock = sway_lock; + + // The lock screen covers everything, so check if any active inhibition got + // deactivated due to lost visibility. + sway_idle_inhibit_v1_check_active(); } static void handle_session_lock_destroy(struct wl_listener *listener, void *data) { @@ -342,8 +344,12 @@ bool sway_session_lock_has_surface(struct sway_session_lock *lock, return false; } -void sway_session_lock_init(void) { +bool sway_session_lock_init(void) { server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display); + if (!server.session_lock.manager) { + sway_log(SWAY_ERROR, "Failed to create session lock manager"); + return false; + } server.session_lock.new_lock.notify = handle_session_lock; server.session_lock.manager_destroy.notify = handle_session_lock_destroy; @@ -351,4 +357,5 @@ void sway_session_lock_init(void) { &server.session_lock.new_lock); wl_signal_add(&server.session_lock.manager->events.destroy, &server.session_lock.manager_destroy); + return true; } diff --git a/sway/main.c b/sway/main.c index 1c4939aa0..a94389266 100644 --- a/sway/main.c +++ b/sway/main.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -44,10 +45,6 @@ void sway_terminate(int exit_code) { } } -void sig_handler(int signal) { - sway_terminate(EXIT_SUCCESS); -} - void run_as_ipc_client(char *command, char *socket_path) { int socketfd = ipc_open_socket(socket_path); uint32_t len = strlen(command); @@ -111,18 +108,14 @@ static void log_kernel(void) { pclose(f); } -static bool detect_suid(void) { - if (geteuid() != 0 && getegid() != 0) { - return false; +static void restore_nofile_limit(void) { + if (original_nofile_rlimit.rlim_cur == 0) { + return; } - - if (getuid() == geteuid() && getgid() == getegid()) { - return false; + if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { + sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: " + "setrlimit(NOFILE) failed"); } - - sway_log(SWAY_ERROR, "SUID operation is no longer supported, refusing to start. " - "This check will be removed in a future release."); - return true; } static void increase_nofile_limit(void) { @@ -139,17 +132,38 @@ static void increase_nofile_limit(void) { "setrlimit(NOFILE) failed"); sway_log(SWAY_INFO, "Running with %d max open files", (int)original_nofile_rlimit.rlim_cur); - } -} - -void restore_nofile_limit(void) { - if (original_nofile_rlimit.rlim_cur == 0) { return; } - if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { - sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: " - "setrlimit(NOFILE) failed"); - } + + pthread_atfork(NULL, NULL, restore_nofile_limit); +} + +static int term_signal(int signal, void *data) { + sway_terminate(EXIT_SUCCESS); + return 0; +} + +static void restore_signals(void) { + sigset_t set; + sigemptyset(&set); + sigprocmask(SIG_SETMASK, &set, NULL); + + struct sigaction sa_dfl = { .sa_handler = SIG_DFL }; + sigaction(SIGCHLD, &sa_dfl, NULL); + sigaction(SIGPIPE, &sa_dfl, NULL); +} + +static void init_signals(void) { + wl_event_loop_add_signal(server.wl_event_loop, SIGTERM, term_signal, NULL); + wl_event_loop_add_signal(server.wl_event_loop, SIGINT, term_signal, NULL); + + struct sigaction sa_ign = { .sa_handler = SIG_IGN }; + // avoid need to reap children + sigaction(SIGCHLD, &sa_ign, NULL); + // prevent ipc write errors from crashing sway + sigaction(SIGPIPE, &sa_ign, NULL); + + pthread_atfork(NULL, NULL, restore_signals); } void enable_debug_flag(const char *flag) { @@ -159,10 +173,8 @@ void enable_debug_flag(const char *flag) { debug.txn_wait = true; } else if (strcmp(flag, "txn-timings") == 0) { debug.txn_timings = true; - } else if (strncmp(flag, "txn-timeout=", 12) == 0) { - server.txn_timeout_ms = atoi(&flag[12]); - } else if (strcmp(flag, "legacy-wl-drm") == 0) { - debug.legacy_wl_drm = true; + } else if (has_prefix(flag, "txn-timeout=")) { + server.txn_timeout_ms = atoi(&flag[strlen("txn-timeout=")]); } else { sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag); } @@ -212,7 +224,7 @@ static const char usage[] = "\n"; int main(int argc, char **argv) { - static bool verbose = false, debug = false, validate = false; + bool verbose = false, debug = false, validate = false, allow_unsupported_gpu = false; char *config_path = NULL; @@ -266,11 +278,6 @@ int main(int argc, char **argv) { } } - // SUID operation is deprecated, so block it for now. - if (detect_suid()) { - exit(EXIT_FAILURE); - } - // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the // clear error message (when not running as an IPC client). if (!getenv("XDG_RUNTIME_DIR") && optind == argc) { @@ -279,6 +286,12 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } + char *unsupported_gpu_env = getenv("SWAY_UNSUPPORTED_GPU"); + // we let the flag override the environment variable + if (!allow_unsupported_gpu && unsupported_gpu_env) { + allow_unsupported_gpu = parse_boolean(unsupported_gpu_env, false); + } + // As the 'callback' function for wlr_log is equivalent to that for // sway, we do not need to override it. if (debug) { @@ -322,19 +335,14 @@ int main(int argc, char **argv) { increase_nofile_limit(); - // handle SIGTERM signals - signal(SIGTERM, sig_handler); - signal(SIGINT, sig_handler); - - // prevent ipc from crashing sway - signal(SIGPIPE, SIG_IGN); - sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION); if (!server_init(&server)) { return 1; } + init_signals(); + if (server.linux_dmabuf_v1) { wlr_scene_set_linux_dmabuf_v1(root->root_scene, server.linux_dmabuf_v1); } @@ -361,6 +369,7 @@ int main(int argc, char **argv) { } config->active = true; + force_modeset(); load_swaybars(); run_deferred_commands(); run_deferred_bindings(); @@ -370,6 +379,20 @@ int main(int argc, char **argv) { swaynag_show(&config->swaynag_config_errors); } + struct swaynag_instance nag_gpu = (struct swaynag_instance){ + .args = "--type error " + "--message 'Proprietary GPU drivers are not supported by sway. Do not report issues.' " + "--detailed-message", + .detailed = true, + }; + + if (unsupported_gpu_detected && !allow_unsupported_gpu) { + swaynag_log(config->swaynag_command, &nag_gpu, + "To remove this message, launch sway with --unsupported-gpu " + "or set the environment variable SWAY_UNSUPPORTED_GPU=true."); + swaynag_show(&nag_gpu); + } + server_run(&server); shutdown: @@ -382,6 +405,10 @@ shutdown: free(config_path); free_config(config); + if (nag_gpu.client != NULL) { + wl_client_destroy(nag_gpu.client); + } + pango_cairo_font_map_set_default(NULL); return exit_value; diff --git a/sway/meson.build b/sway/meson.build index d937e4256..cb03a4d28 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -18,6 +18,7 @@ sway_sources = files( 'desktop/idle_inhibit_v1.c', 'desktop/layer_shell.c', 'desktop/output.c', + 'desktop/tearing.c', 'desktop/transaction.c', 'desktop/xdg_shell.c', 'desktop/launcher.c', @@ -41,6 +42,7 @@ sway_sources = files( 'config/seat.c', 'config/input.c', + 'commands/allow_tearing.c', 'commands/assign.c', 'commands/bar.c', 'commands/bind.c', @@ -154,6 +156,7 @@ sway_sources = files( 'commands/input/accel_profile.c', 'commands/input/calibration_matrix.c', 'commands/input/click_method.c', + 'commands/input/clickfinger_button_map.c', 'commands/input/drag.c', 'commands/input/drag_lock.c', 'commands/input/dwt.c', @@ -187,10 +190,12 @@ sway_sources = files( 'commands/input/xkb_variant.c', 'commands/output/adaptive_sync.c', + 'commands/output/allow_tearing.c', 'commands/output/background.c', 'commands/output/disable.c', 'commands/output/dpms.c', 'commands/output/enable.c', + 'commands/output/hdr.c', 'commands/output/max_render_time.c', 'commands/output/mode.c', 'commands/output/position.c', @@ -202,6 +207,7 @@ sway_sources = files( 'commands/output/toggle.c', 'commands/output/transform.c', 'commands/output/unplug.c', + 'commands/output/color_profile.c', 'tree/arrange.c', 'tree/container.c', @@ -231,7 +237,7 @@ sway_deps = [ xcb_icccm, ] -if have_xwayland +if wlroots_features['xwayland'] sway_sources += 'desktop/xwayland.c' endif diff --git a/sway/scene_descriptor.c b/sway/scene_descriptor.c index a30d46646..92bdda00c 100644 --- a/sway/scene_descriptor.c +++ b/sway/scene_descriptor.c @@ -39,6 +39,9 @@ void *scene_descriptor_try_get(struct wlr_scene_node *node, void scene_descriptor_destroy(struct wlr_scene_node *node, enum sway_scene_descriptor_type type) { struct scene_descriptor *desc = scene_node_get_descriptor(node, type); + if (!desc) { + return; + } descriptor_destroy(desc); } diff --git a/sway/server.c b/sway/server.c index d159dc9bd..8c55c0395 100644 --- a/sway/server.c +++ b/sway/server.c @@ -9,20 +9,27 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include #include -#include #include #include +#include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -44,6 +51,7 @@ #include #include #include +#include #include #include "config.h" #include "list.h" @@ -55,8 +63,9 @@ #include "sway/server.h" #include "sway/input/cursor.h" #include "sway/tree/root.h" +#include "sway/tree/workspace.h" -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND #include #include "sway/xwayland.h" #endif @@ -66,10 +75,11 @@ #endif #define SWAY_XDG_SHELL_VERSION 5 -#define SWAY_LAYER_SHELL_VERSION 4 +#define SWAY_LAYER_SHELL_VERSION 5 #define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1 +#define SWAY_PRESENTATION_VERSION 2 -bool allow_unsupported_gpu = false; +bool unsupported_gpu_detected = false; #if WLR_HAS_DRM_BACKEND static void handle_drm_lease_request(struct wl_listener *listener, void *data) { @@ -103,8 +113,10 @@ static bool is_privileged(const struct wl_global *global) { global == server.input_method->global || global == server.foreign_toplevel_list->global || global == server.foreign_toplevel_manager->global || - global == server.data_control_manager_v1->global || + global == server.wlr_data_control_manager_v1->global || + global == server.ext_data_control_manager_v1->global || global == server.screencopy_manager_v1->global || + global == server.ext_image_copy_capture_manager_v1->global || global == server.export_dmabuf_manager_v1->global || global == server.security_context_manager_v1->global || global == server.gamma_control_manager_v1->global || @@ -113,12 +125,14 @@ static bool is_privileged(const struct wl_global *global) { global == server.input->keyboard_shortcuts_inhibit->global || global == server.input->virtual_keyboard->global || global == server.input->virtual_pointer->global || - global == server.input->transient_seat_manager->global; + global == server.input->transient_seat_manager->global || + global == server.xdg_output_manager_v1->global || + global == server.workspace_manager_v1->global; } static bool filter_global(const struct wl_client *client, const struct wl_global *global, void *data) { -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; if (xwayland && global == xwayland->shell_v1->global) { return xwayland->server != NULL && client == xwayland->server->client; @@ -149,35 +163,24 @@ static void detect_proprietary(struct wlr_backend *backend, void *data) { return; } - bool is_unsupported = false; if (strcmp(version->name, "nvidia-drm") == 0) { - is_unsupported = true; + unsupported_gpu_detected = true; sway_log(SWAY_ERROR, "!!! Proprietary Nvidia drivers are in use !!!"); - if (!allow_unsupported_gpu) { - sway_log(SWAY_ERROR, "Use Nouveau instead"); - } } if (strcmp(version->name, "evdi") == 0) { - is_unsupported = true; + unsupported_gpu_detected = true; sway_log(SWAY_ERROR, "!!! Proprietary DisplayLink drivers are in use !!!"); } - if (!allow_unsupported_gpu && is_unsupported) { - sway_log(SWAY_ERROR, - "Proprietary drivers are NOT supported. To launch sway anyway, " - "launch with --unsupported-gpu and DO NOT report issues."); - exit(EXIT_FAILURE); - } - drmFreeVersion(version); } -static void handle_renderer_lost(struct wl_listener *listener, void *data) { - struct sway_server *server = wl_container_of(listener, server, renderer_lost); +static void do_renderer_recreate(void *data) { + struct sway_server *server = data; + server->recreating_renderer = NULL; sway_log(SWAY_INFO, "Re-creating renderer after GPU reset"); - struct wlr_renderer *renderer = wlr_renderer_autocreate(server->backend); if (renderer == NULL) { sway_log(SWAY_ERROR, "Unable to create renderer"); @@ -202,8 +205,8 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) { wlr_compositor_set_renderer(server->compositor, renderer); - for (int i = 0; i < root->outputs->length; ++i) { - struct sway_output *output = root->outputs->items[i]; + struct sway_output *output; + wl_list_for_each(output, &root->all_outputs, link) { wlr_output_init_render(output->wlr_output, server->allocator, server->renderer); } @@ -212,14 +215,55 @@ static void handle_renderer_lost(struct wl_listener *listener, void *data) { wlr_renderer_destroy(old_renderer); } +static void handle_renderer_lost(struct wl_listener *listener, void *data) { + struct sway_server *server = wl_container_of(listener, server, renderer_lost); + + if (server->recreating_renderer != NULL) { + sway_log(SWAY_DEBUG, "Re-creation of renderer already scheduled"); + return; + } + + sway_log(SWAY_INFO, "Scheduling re-creation of renderer after GPU reset"); + server->recreating_renderer = wl_event_loop_add_idle(server->wl_event_loop, do_renderer_recreate, server); +} + +static void handle_new_foreign_toplevel_capture_request(struct wl_listener *listener, void *data) { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_event *request = data; + struct sway_view *view = request->toplevel_handle->data; + + if (view->image_capture_source == NULL) { + view->image_capture_source = wlr_ext_image_capture_source_v1_create_with_scene_node( + &view->image_capture_scene->tree.node, server.wl_event_loop, server.allocator, server.renderer); + if (view->image_capture_source == NULL) { + return; + } + } + + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept(request, view->image_capture_source); +} + bool server_init(struct sway_server *server) { sway_log(SWAY_DEBUG, "Initializing Wayland server"); server->wl_display = wl_display_create(); + if (!server->wl_display) { + sway_log(SWAY_ERROR, "Failed to create wl_display"); + return false; + } server->wl_event_loop = wl_display_get_event_loop(server->wl_display); wl_display_set_global_filter(server->wl_display, filter_global, NULL); + wl_display_set_default_max_buffer_size(server->wl_display, 1024 * 1024); + if (!wlr_fixes_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create wp_fixes global"); + return false; + } root = root_create(server->wl_display); + if (!root) { + sway_log(SWAY_ERROR, "Failed to create root"); + wl_display_destroy(server->wl_display); + return false; + } server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session); if (!server->backend) { @@ -240,13 +284,20 @@ bool server_init(struct sway_server *server) { wlr_renderer_init_wl_shm(server->renderer, server->wl_display); - if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL) { + if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && + wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) { server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer( - server->wl_display, 4, server->renderer); + server->wl_display, 5, server->renderer); + if (!server->linux_dmabuf_v1) { + sway_log(SWAY_ERROR, "Failed to create linux-dmabuf v1"); + return false; + } } - if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL && - debug.legacy_wl_drm) { - wlr_drm_create(server->wl_display, server->renderer); + if (wlr_renderer_get_drm_fd(server->renderer) >= 0 && + server->renderer->features.timeline && + server->backend->features.timeline) { + wlr_linux_drm_syncobj_manager_v1_create(server->wl_display, 1, + wlr_renderer_get_drm_fd(server->renderer)); } server->allocator = wlr_allocator_autocreate(server->backend, @@ -258,45 +309,84 @@ bool server_init(struct sway_server *server) { server->compositor = wlr_compositor_create(server->wl_display, 6, server->renderer); + if (!server->compositor) { + sway_log(SWAY_ERROR, "Failed to create compositor"); + return false; + } - wlr_subcompositor_create(server->wl_display); + if (!wlr_subcompositor_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create subcompositor"); + return false; + } server->data_device_manager = wlr_data_device_manager_create(server->wl_display); + if (!server->data_device_manager) { + sway_log(SWAY_ERROR, "Failed to create data device manager"); + return false; + } server->gamma_control_manager_v1 = wlr_gamma_control_manager_v1_create(server->wl_display); - server->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma; - wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma, - &server->gamma_control_set_gamma); + if (!server->gamma_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create gamma control manager"); + return false; + } + wlr_scene_set_gamma_control_manager_v1(root->root_scene, + server->gamma_control_manager_v1); server->new_output.notify = handle_new_output; wl_signal_add(&server->backend->events.new_output, &server->new_output); - server->output_layout_change.notify = handle_output_layout_change; - wl_signal_add(&root->output_layout->events.change, - &server->output_layout_change); - wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); + server->xdg_output_manager_v1 = + wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); + if (!server->xdg_output_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG output manager"); + return false; + } server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display); - sway_idle_inhibit_manager_v1_init(); + if (!server->idle_notifier_v1) { + sway_log(SWAY_ERROR, "Failed to create idle notifier"); + return false; + } + if (!sway_idle_inhibit_manager_v1_init()) { + sway_log(SWAY_ERROR, "Failed to init idle inhibit manager"); + return false; + } server->layer_shell = wlr_layer_shell_v1_create(server->wl_display, SWAY_LAYER_SHELL_VERSION); + if (!server->layer_shell) { + sway_log(SWAY_ERROR, "Failed to create layer shell"); + return false; + } wl_signal_add(&server->layer_shell->events.new_surface, &server->layer_shell_surface); server->layer_shell_surface.notify = handle_layer_shell_surface; server->xdg_shell = wlr_xdg_shell_create(server->wl_display, SWAY_XDG_SHELL_VERSION); + if (!server->xdg_shell) { + sway_log(SWAY_ERROR, "Failed to create XDG shell"); + return false; + } wl_signal_add(&server->xdg_shell->events.new_toplevel, &server->xdg_shell_toplevel); server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel; server->tablet_v2 = wlr_tablet_v2_create(server->wl_display); + if (!server->tablet_v2) { + sway_log(SWAY_ERROR, "Failed to create tablet manager"); + return false; + } server->server_decoration_manager = wlr_server_decoration_manager_create(server->wl_display); + if (!server->server_decoration_manager) { + sway_log(SWAY_ERROR, "Failed to create server decoration manager"); + return false; + } wlr_server_decoration_manager_set_default_mode( server->server_decoration_manager, WLR_SERVER_DECORATION_MANAGER_MODE_SERVER); @@ -307,6 +397,10 @@ bool server_init(struct sway_server *server) { server->xdg_decoration_manager = wlr_xdg_decoration_manager_v1_create(server->wl_display); + if (!server->xdg_decoration_manager) { + sway_log(SWAY_ERROR, "Failed to create XDG decoration manager"); + return false; + } wl_signal_add( &server->xdg_decoration_manager->events.new_toplevel_decoration, &server->xdg_decoration); @@ -315,17 +409,36 @@ bool server_init(struct sway_server *server) { server->relative_pointer_manager = wlr_relative_pointer_manager_v1_create(server->wl_display); + if (!server->relative_pointer_manager) { + sway_log(SWAY_ERROR, "Failed to create relative pointer manager"); + return false; + } server->pointer_constraints = wlr_pointer_constraints_v1_create(server->wl_display); + if (!server->pointer_constraints) { + sway_log(SWAY_ERROR, "Failed to create pointer constraints"); + return false; + } server->pointer_constraint.notify = handle_pointer_constraint; wl_signal_add(&server->pointer_constraints->events.new_constraint, &server->pointer_constraint); - wlr_presentation_create(server->wl_display, server->backend); + if (!wlr_presentation_create(server->wl_display, server->backend, SWAY_PRESENTATION_VERSION)) { + sway_log(SWAY_ERROR, "Failed to create presentation"); + return false; + } + if (!wlr_alpha_modifier_v1_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create alpha modifier"); + return false; + } server->output_manager_v1 = wlr_output_manager_v1_create(server->wl_display); + if (!server->output_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create output manager"); + return false; + } server->output_manager_apply.notify = handle_output_manager_apply; wl_signal_add(&server->output_manager_v1->events.apply, &server->output_manager_apply); @@ -335,18 +448,43 @@ bool server_init(struct sway_server *server) { server->output_power_manager_v1 = wlr_output_power_manager_v1_create(server->wl_display); + if (!server->output_power_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create output power manager"); + return false; + } server->output_power_manager_set_mode.notify = handle_output_power_manager_set_mode; wl_signal_add(&server->output_power_manager_v1->events.set_mode, &server->output_power_manager_set_mode); server->input_method = wlr_input_method_manager_v2_create(server->wl_display); + if (!server->input_method) { + sway_log(SWAY_ERROR, "Failed to create input method manager"); + return false; + } server->text_input = wlr_text_input_manager_v3_create(server->wl_display); + if (!server->text_input) { + sway_log(SWAY_ERROR, "Failed to create text input manager"); + return false; + } server->foreign_toplevel_list = wlr_ext_foreign_toplevel_list_v1_create(server->wl_display, SWAY_FOREIGN_TOPLEVEL_LIST_VERSION); + if (!server->foreign_toplevel_list) { + sway_log(SWAY_ERROR, "Failed to create foreign toplevel list"); + return false; + } server->foreign_toplevel_manager = wlr_foreign_toplevel_manager_v1_create(server->wl_display); + if (!server->foreign_toplevel_manager) { + sway_log(SWAY_ERROR, "Failed to create foreign toplevel manager"); + return false; + } - sway_session_lock_init(); + if (!sway_session_lock_init()) { + return false; + } + if (!sway_ext_workspace_init()) { + return false; + } #if WLR_HAS_DRM_BACKEND server->drm_lease_manager= @@ -362,21 +500,99 @@ bool server_init(struct sway_server *server) { #endif server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display); + if (!server->export_dmabuf_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create export dmabuf manager"); + return false; + } server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display); - server->data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display); + if (!server->screencopy_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create screencopy manager"); + return false; + } + server->ext_image_copy_capture_manager_v1 = wlr_ext_image_copy_capture_manager_v1_create(server->wl_display, 1); + if (!server->ext_image_copy_capture_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext image copy capture manager"); + return false; + } + if (!wlr_ext_output_image_capture_source_manager_v1_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create ext output image capture source manager"); + return false; + } + server->wlr_data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display); + if (!server->wlr_data_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create data control manager"); + return false; + } + server->ext_data_control_manager_v1 = wlr_ext_data_control_manager_v1_create(server->wl_display, 1); + if (!server->ext_data_control_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext data control manager"); + return false; + } server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display); - wlr_viewporter_create(server->wl_display); - wlr_single_pixel_buffer_manager_v1_create(server->wl_display); + if (!server->security_context_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create security context manager"); + return false; + } + if (!wlr_viewporter_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create viewporter"); + return false; + } + if (!wlr_single_pixel_buffer_manager_v1_create(server->wl_display)) { + sway_log(SWAY_ERROR, "Failed to create single pixel buffer manager"); + return false; + } server->content_type_manager_v1 = wlr_content_type_manager_v1_create(server->wl_display, 1); - wlr_fractional_scale_manager_v1_create(server->wl_display, 1); + if (!server->content_type_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create content type manager"); + return false; + } + if (!wlr_fractional_scale_manager_v1_create(server->wl_display, 1)) { + sway_log(SWAY_ERROR, "Failed to create fractional scale manager"); + return false; + } + + server->ext_foreign_toplevel_image_capture_source_manager_v1 = + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create(server->wl_display, 1); + if (!server->ext_foreign_toplevel_image_capture_source_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext foreign toplevel image capture source manager"); + return false; + } + server->new_foreign_toplevel_capture_request.notify = handle_new_foreign_toplevel_capture_request; + wl_signal_add(&server->ext_foreign_toplevel_image_capture_source_manager_v1->events.capture_request, + &server->new_foreign_toplevel_capture_request); + + server->tearing_control_v1 = + wlr_tearing_control_manager_v1_create(server->wl_display, 1); + if (!server->tearing_control_v1) { + sway_log(SWAY_ERROR, "Failed to create tearing control manager"); + return false; + } + server->tearing_control_new_object.notify = handle_new_tearing_hint; + wl_signal_add(&server->tearing_control_v1->events.new_object, + &server->tearing_control_new_object); + wl_list_init(&server->tearing_controllers); struct wlr_xdg_foreign_registry *foreign_registry = wlr_xdg_foreign_registry_create(server->wl_display); - wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry); - wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry); + if (!foreign_registry) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign registry"); + return false; + } + if (!wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry)) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign v1"); + return false; + } + if (!wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry)) { + sway_log(SWAY_ERROR, "Failed to create XDG foreign v2"); + return false; + } server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display); + if (!server->xdg_activation_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG activation"); + return false; + } server->xdg_activation_v1_request_activate.notify = xdg_activation_v1_handle_request_activate; wl_signal_add(&server->xdg_activation_v1->events.request_activate, @@ -386,11 +602,64 @@ bool server_init(struct sway_server *server) { wl_signal_add(&server->xdg_activation_v1->events.new_token, &server->xdg_activation_v1_new_token); + struct wlr_xdg_toplevel_tag_manager_v1 *xdg_toplevel_tag_manager_v1 = + wlr_xdg_toplevel_tag_manager_v1_create(server->wl_display, 1); + if (!xdg_toplevel_tag_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create XDG toplevel tag manager"); + return false; + } + server->xdg_toplevel_tag_manager_v1_set_tag.notify = + xdg_toplevel_tag_manager_v1_handle_set_tag; + wl_signal_add(&xdg_toplevel_tag_manager_v1->events.set_tag, + &server->xdg_toplevel_tag_manager_v1_set_tag); + struct wlr_cursor_shape_manager_v1 *cursor_shape_manager = - wlr_cursor_shape_manager_v1_create(server->wl_display, 1); + wlr_cursor_shape_manager_v1_create(server->wl_display, 2); + if (!cursor_shape_manager) { + sway_log(SWAY_ERROR, "Failed to create cursor shape manager"); + return false; + } server->request_set_cursor_shape.notify = handle_request_set_cursor_shape; wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape); + if (server->renderer->features.input_color_transform) { + const enum wp_color_manager_v1_render_intent render_intents[] = { + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL, + }; + size_t transfer_functions_len = 0; + enum wp_color_manager_v1_transfer_function *transfer_functions = + wlr_color_manager_v1_transfer_function_list_from_renderer(server->renderer, &transfer_functions_len); + size_t primaries_len = 0; + enum wp_color_manager_v1_primaries *primaries = + wlr_color_manager_v1_primaries_list_from_renderer(server->renderer, &primaries_len); + struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create( + server->wl_display, 2, &(struct wlr_color_manager_v1_options){ + .features = { + .parametric = true, + .set_mastering_display_primaries = true, + }, + .render_intents = render_intents, + .render_intents_len = sizeof(render_intents) / sizeof(render_intents[0]), + .transfer_functions = transfer_functions, + .transfer_functions_len = transfer_functions_len, + .primaries = primaries, + .primaries_len = primaries_len, + }); + free(transfer_functions); + free(primaries); + if (!cm) { + sway_log(SWAY_ERROR, "Failed to create color manager"); + return false; + } + wlr_scene_set_color_manager_v1(root->root_scene, cm); + } + + if (!wlr_color_representation_manager_v1_create_with_renderer( + server->wl_display, 1, server->renderer)) { + sway_log(SWAY_ERROR, "Failed to create color representation manager"); + return false; + } + wl_list_init(&server->pending_launcher_ctxs); // Avoid using "wayland-0" as display socket @@ -429,26 +698,64 @@ bool server_init(struct sway_server *server) { } server->dirty_nodes = create_list(); + if (!server->dirty_nodes) { + sway_log(SWAY_ERROR, "Failed to create dirty nodes list"); + return false; + } server->input = input_manager_create(server); + if (!server->input) { + sway_log(SWAY_ERROR, "Failed to create input manager"); + return false; + } input_manager_get_default_seat(); // create seat0 return true; } void server_fini(struct sway_server *server) { + // remove listeners + wl_list_remove(&server->renderer_lost.link); + wl_list_remove(&server->new_output.link); + wl_list_remove(&server->layer_shell_surface.link); + wl_list_remove(&server->xdg_shell_toplevel.link); + wl_list_remove(&server->server_decoration.link); + wl_list_remove(&server->xdg_decoration.link); + wl_list_remove(&server->pointer_constraint.link); + wl_list_remove(&server->output_manager_apply.link); + wl_list_remove(&server->output_manager_test.link); + wl_list_remove(&server->output_power_manager_set_mode.link); +#if WLR_HAS_DRM_BACKEND + if (server->drm_lease_manager) { + wl_list_remove(&server->drm_lease_request.link); + } +#endif + wl_list_remove(&server->tearing_control_new_object.link); + wl_list_remove(&server->xdg_activation_v1_request_activate.link); + wl_list_remove(&server->xdg_activation_v1_new_token.link); + wl_list_remove(&server->xdg_toplevel_tag_manager_v1_set_tag.link); + wl_list_remove(&server->request_set_cursor_shape.link); + wl_list_remove(&server->new_foreign_toplevel_capture_request.link); + wl_list_remove(&server->workspace_manager_v1_commit.link); + input_manager_finish(server->input); + // TODO: free sway-specific resources -#if HAVE_XWAYLAND - wlr_xwayland_destroy(server->xwayland.wlr_xwayland); +#if WLR_HAS_XWAYLAND + if (server->xwayland.wlr_xwayland != NULL) { + wl_list_remove(&server->xwayland_surface.link); + wl_list_remove(&server->xwayland_ready.link); + wlr_xwayland_destroy(server->xwayland.wlr_xwayland); + } #endif wl_display_destroy_clients(server->wl_display); wlr_backend_destroy(server->backend); wl_display_destroy(server->wl_display); list_free(server->dirty_nodes); + free(server->socket); } bool server_start(struct sway_server *server) { -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND if (config->xwayland != XWAYLAND_MODE_DISABLED) { sway_log(SWAY_DEBUG, "Initializing Xwayland (lazy=%d)", config->xwayland == XWAYLAND_MODE_LAZY); diff --git a/sway/sway-bar.5.scd b/sway/sway-bar.5.scd index 42e59d57d..a1733aa2b 100644 --- a/sway/sway-bar.5.scd +++ b/sway/sway-bar.5.scd @@ -143,6 +143,12 @@ runtime. This setting also applies to the current binding mode indicator. +The following commands may only be used at runtime. + +*mode* toggle [] + Toggles the current mode between _hide_ and _dock_. Any other mode + is treated as _hide_. + ## TRAY Swaybar provides a system tray where third-party applications may place icons. diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd index 442311bb9..389b20570 100644 --- a/sway/sway-input.5.scd +++ b/sway/sway-input.5.scd @@ -143,11 +143,18 @@ The following commands may only be used in the configuration file. *input* click_method none|button_areas|clickfinger Changes the click method for the specified device. +*input* clickfinger_button_map lrm|lmr + Specifies which button mapping to use for clickfinger. _lrm_ treats 1 finger as + left click, 2 fingers as right click, and 3 fingers as middle click. _lmr_ + treats 1 finger as left click, 2 fingers as middle click, and 3 fingers as + right click. + *input* drag enabled|disabled Enables or disables tap-and-drag for specified input device. -*input* drag_lock enabled|disabled - Enables or disables drag lock for specified input device. +*input* drag_lock enabled|disabled|enabled_sticky + Enables or disables drag lock for specified input device. The default is + _disabled_. *input* dwt enabled|disabled Enables or disables disable-while-typing for the specified input device. diff --git a/sway/sway-ipc.7.scd b/sway/sway-ipc.7.scd index c9895e52c..70c831375 100644 --- a/sway/sway-ipc.7.scd +++ b/sway/sway-ipc.7.scd @@ -138,7 +138,7 @@ has the following properties: : Whether the workspace is currently focused by the default seat (_seat0_) |- urgent : boolean -: Whether a view on the workspace has the urgent flag set +: Whether a window on the workspace has the urgent flag set |- rect : object : The bounds of the workspace. It consists of _x_, _y_, _width_, and _height_ @@ -244,6 +244,9 @@ following properties: |- rect : object : The bounds for the output consisting of _x_, _y_, _width_, and _height_ +|- hdr +: boolean +: Whether HDR is enabled *Example Reply:* @@ -374,7 +377,7 @@ node and will have the following properties: that can be used as an aid in submitting reproduction steps for bug reports |- fullscreen_mode : integer -: (Only containers and views) The fullscreen mode of the node. 0 means none, 1 means +: (Only containers and windows) The fullscreen mode of the node. 0 means none, 1 means full workspace, and 2 means global fullscreen |- floating : string @@ -384,32 +387,49 @@ node and will have the following properties: : Whether the window is in the scratchpad. Can be either "none" or "fresh" |- app_id : string -: (Only views) For an xdg-shell view, the name of the application, if set. +: (Only windows) For an xdg-shell window, the name of the application, if set. Otherwise, _null_ |- pid : integer -: (Only views) The PID of the application that owns the view +: (Only windows) The PID of the application that owns the window +|- foreign_toplevel_identifier +: string +: (Only windows) The ext-foreign-toplevel-list-v1 toplevel identifier of this node. |- visible : boolean -: (Only views) Whether the node is visible +: (Only windows) Whether the node is visible |- shell : string -: (Only views) The shell of the view, such as _xdg\_shell_ or _xwayland_ +: (Only windows) The shell of the window, such as _xdg\_shell_ or _xwayland_ |- inhibit_idle : boolean -: (Only views) Whether the view is inhibiting the idle state +: (Only windows) Whether the window is inhibiting the idle state |- idle_inhibitors : object -: (Only views) An object containing the state of the _application_ and _user_ idle inhibitors. +: (Only windows) An object containing the state of the _application_ and _user_ idle inhibitors. _application_ can be _enabled_ or _none_. _user_ can be _focus_, _fullscreen_, _open_, _visible_ or _none_. +|- sandbox_engine +: string +: (Only windows) The associated sandbox engine (or _null_) +|- sandbox_app_id +: string +: (Only windows) The app ID provided by the associated sandbox engine (or _null_) +|- sandbox_instance_id +: string +: (Only windows) The instance ID provided by the associated sandbox engine (or + _null_) +|- tag +: string +: (Only windows) For an xdg-shell window, tag of the toplevel, if set. + Otherwise, _null_ |- window : integer -: (Only xwayland views) The X11 window ID for the xwayland view +: (Only xwayland windows) The X11 window ID for the xwayland window |- window_properties : object -: (Only xwayland views) An object containing the _title_, _class_, _instance_, - _window\_role_, _window\_type_, and _transient\_for_ for the view +: (Only xwayland windows) An object containing the _title_, _class_, _instance_, + _window\_role_, _window\_type_, and _transient\_for_ for the window *Example Reply:* @@ -782,7 +802,7 @@ node and will have the following properties: Retrieve the currently set marks *REPLY*++ -An array of marks current set. Since each mark can only be set for one +An array of marks currently set. Since each mark can only be set for one container, this is a set so each value is unique and the order is undefined. *Example Reply:* @@ -813,13 +833,13 @@ An array of bar IDs, which are strings *MESSAGE*++ When sent with a bar ID as the payload, this retrieves the config associated -with the specified by the bar ID in the payload. This is used by swaybar, but -could also be used for third party bars +with the bar ID specified in the payload. This is used by swaybar, but could +also be used for third-party bars *REPLY*++ An object that represents the configuration for the bar with the bar ID sent as -the payload. The following properties exists and more information about what -their value mean can be found in *sway-bar*(5): +the payload. The following properties exist, with more information about their +values in *sway-bar*(5): [- *PROPERTY* :- *DATA TYPE* @@ -914,13 +934,13 @@ containing the _#RRGGBBAA_ representation of the color: that are not visible |- urgent_workspace_text : The color to use for the text of the workspace buttons for workspaces that - contain an urgent view + contain an urgent window |- urgent_workspace_bg : The color to use for the background of the workspace buttons for workspaces - that contain an urgent view + that contain an urgent window |- urgent_workspace_border : The color to use for the border of the workspace buttons for workspaces that - contain an urgent view + contain an urgent window |- binding_mode_text : The color to use for the text of the binding mode indicator |- binding_mode_bg @@ -1020,7 +1040,7 @@ An object containing the following properties: ## 8. GET_BINDING_MODES *MESSAGE*++ -Retrieve the list of binding modes that currently configured +Retrieve the list of binding modes that are currently configured *REPLY*++ An array of strings, with each string being the name of a binding mode. This @@ -1046,7 +1066,7 @@ An object with a single string property containing the contents of the config *Example Reply:* ``` { - "config": "set $mod Mod4\nbindsym $mod+q exit\n" + "config": "set $mod Mod4\\nbindsym $mod+q exit\\n" } ``` @@ -1096,7 +1116,7 @@ Returns the currently active binding mode. A single object that contains the property _name_, which is set to the currently active binding mode as a string. -*Exact Reply:* +*Example Reply:* ``` { "name": "default" @@ -1134,13 +1154,13 @@ following properties: _tablet\_tool_, _tablet\_pad_, or _switch_ |- xkb_active_layout_name : string -: (Only keyboards) The name of the active keyboard layout in use +: (Only keyboards) The name of the active keyboard layout |- xkb_layout_names : array : (Only keyboards) A list a layout names configured for the keyboard |- xkb_active_layout_index : integer -: (Only keyboards) The index of the active keyboard layout in use +: (Only keyboards) The index of the active keyboard layout |- scroll_factor : floating : (Only pointers) Multiplier applied on scroll event values. @@ -1168,13 +1188,14 @@ following properties will be included for devices that support them: : Whether tap to click is enabled. It can be _enabled_ or _disabled_ |- tap_button_map : string -: The finger to button mapping in use. It can be _lmr_ or _lrm_ +: The finger to button mapping in use for tapping. It can be _lmr_ or _lrm_ |- tap_drag : string : Whether tap-and-drag is enabled. It can be _enabled_ or _disabled_ |- tap_drag_lock : string -: Whether drag-lock is enabled. It can be _enabled_ or _disabled_ +: Whether drag-lock is enabled. It can be _enabled_, _disabled_ or + _enabled_sticky_ |- accel_speed : double : The pointer-acceleration in use @@ -1190,6 +1211,9 @@ following properties will be included for devices that support them: |- click_method : string : The click method in use. It can be _none_, _button_areas_, or _clickfinger_ +|- click_button_map +: string +: The finger to button mapping in use for clickfinger. It can be _lmr_ or _lrm_ |- middle_emulation : string : Whether middle emulation is enabled. It can be _enabled_ or _disabled_ @@ -1443,7 +1467,7 @@ one seat. Each object has the following properties: # EVENTS -Events are a way for client to get notified of changes to sway. A client can +Events are a way for clients to get notified of changes to sway. A client can subscribe to any events it wants to be notified of changes for. The event is sent in the same format as a reply. The following events are currently available: @@ -1454,7 +1478,7 @@ available: |- 0x80000000 : workspace :[ Sent whenever an event involving a workspace occurs such as initialization - of a new workspace or a different workspace gains focus + of a new workspace or another workspace gaining focus |- 0x80000001 : output : Sent when outputs are updated @@ -1463,7 +1487,7 @@ available: : Sent whenever the binding mode changes |- 0x80000003 : window -: Sent whenever an event involving a view occurs such as being reparented, +: Sent whenever an event involving a window occurs such as it being reparented, focused, or closed |- 0x80000004 : barconfig_update @@ -1479,7 +1503,7 @@ available: : Sent when an ipc client sends a _SEND\_TICK_ message |- 0x80000014 : bar_state_update -: Send when the visibility of a bar should change due to a modifier +: Sent when the visibility of a bar should change due to a modifier |- 0x80000015 : input : Sent when something related to input devices changes @@ -1498,10 +1522,10 @@ single object with the following properties: :[ The type of change that occurred. See below for more information |- current : object -: An object representing the workspace effected or _null_ for _reload_ changes +: An object representing the affected workspace or _null_ for _reload_ changes |- old : object -: For a _focus_ change, this is will be an object representing the workspace +: For a _focus_ change, this will be an object representing the workspace being switched from. Otherwise, it is _null_ @@ -1519,8 +1543,8 @@ The following change types are currently available: |- rename : The workspace was renamed |- urgent -: A view on the workspace has had their urgency hint set or all urgency hints - for views on the workspace have been cleared +: A window on the workspace has had its urgency hint set or all urgency hints + for windows on the workspace have been cleared |- reload : The configuration file has been reloaded @@ -1618,7 +1642,7 @@ with the following properties: ## 0x80000003. WINDOW -Sent whenever a change involving a view occurs. The event consists of a single +Sent whenever a change involving a window occurs. The event consists of a single object with the following properties: [- *PROPERTY* @@ -1629,30 +1653,30 @@ object with the following properties: :[ The type of change that occurred. See below for more information |- container : object -: An object representing the view effected +: An object representing the affected window The following change types are currently available: [- *TYPE* :- *DESCRIPTION* |- new -:[ The view was created +:[ The window was created |- close -: The view was closed +: The window was closed |- focus -: The view was focused +: The window was focused |- title -: The view's title has changed +: The window's title has changed |- fullscreen_mode -: The view's fullscreen mode has changed +: The window's fullscreen mode has changed |- move -: The view has been reparented in the tree +: The window has been reparented in the tree |- floating -: The view has become floating or is no longer floating +: The window has become floating or is no longer floating |- urgent -: The view's urgency hint has changed status +: The window's urgency hint has changed status |- mark -: A mark has been added or removed from the view +: A mark has been added or removed from the window *Example Event:* @@ -1792,7 +1816,7 @@ event is a single object with the following properties: : Whether this event was triggered by subscribing to the tick events |- payload : string -: The payload given with a _SEND\_TICK_ message, if any. Otherwise, an empty +: The payload provided in a _SEND\_TICK_ message, if any. Otherwise, an empty string @@ -1814,7 +1838,7 @@ event is a single object with the following properties: :- *DESCRIPTION* |- id : string -:[ The bar ID effected +:[ The bar ID affected |- visible_by_modifier : boolean : Whether the bar should be made visible due to a modifier being pressed @@ -1830,7 +1854,7 @@ event is a single object with the following properties: ## 0x80000015. INPUT -Sent when something related to the input devices changes. The event is a single +Sent when something related to input devices changes. The event is a single object with the following properties: [- *PROPERTY* @@ -1841,7 +1865,7 @@ object with the following properties: :[ What has changed |- input : object -: An object representing the input that is identical the ones GET_INPUTS gives +: An object representing the input that is identical to the ones GET_INPUTS gives The following change types are currently available: [- *TYPE* diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd index 7d088d5db..faf59a1f2 100644 --- a/sway/sway-output.5.scd +++ b/sway/sway-output.5.scd @@ -154,7 +154,7 @@ must be separated by one space. For example: This setting only has an effect on Wayland and DRM backends, as support for presentation timestamps and predicted output refresh rate is required. -*output* adaptive_sync on|off +*output* adaptive_sync on|off|toggle Enables or disables adaptive synchronization (often referred to as Variable Refresh Rate, or by the vendor-specific names FreeSync/G-Sync). @@ -163,9 +163,9 @@ must be separated by one space. For example: adaptive sync can improve latency, but can cause flickering on some hardware. -*output* render_bit_depth 8|10 - Controls the color channel bit depth at which frames are rendered; the - default is currently 8 bits per channel. +*output* render_bit_depth 6|8|10 + Controls the maximum color channel bit depth at which frames are + rendered; the default is currently 8 bits per channel. Setting higher values will not have an effect if hardware and software lack support for such bit depths. Successfully increasing the render bit depth @@ -178,6 +178,59 @@ must be separated by one space. For example: updated to work with different bit depths. This command is experimental, and may be removed or changed in the future. +*output* color_profile [--device-primaries] gamma22|srgb + Sets the color profile for an output. The default is _gamma22_. + + _--device-primaries_ will use the output's self-reported color primaries + when available (e.g. from display EDID). + + Not all renderers support this feature; currently it only works with the + the Vulkan renderer. It is not compatible with HDR support features. + +*output* color_profile icc + Sets the color profile for an output. + + should be a path to a display ICC profile. + + Not all renderers support this feature; currently it only works with the + the Vulkan renderer. Even where supported, the application of the color + profile may be inaccurate. + + This command is experimental, and may be removed or changed in the future. It + may have no effect or produce unexpected output when used together with future + HDR support features. + +*output* allow_tearing yes|no + Allows or disallows screen tearing as a result of immediate page flips, + and an immediate presentation mode from a client. The default is that no + screen tearing is allowed. + + With immediate page flips, frames from the client are presented as soon + as possible instead of synchronizing with the monitor's vblank interval + (VSync). + + It is recommended to set *max_render_time* to *off*. In that case a page flip + happens as soon as a client updates. Otherwise, tearing will only happen if + rendering takes longer than the configured milliseconds before the next + display refresh. + + To adjust whether tearing is allowed for specific applications, see + *allow_tearing* in *sway*(5). Note that tearing will only be enabled + when it's allowed for both the output and the application. + + This setting only has effect when a window is fullscreen on the output. + +*output* hdr on|off|toggle + Enables or disables HDR (High Dynamic Range). HDR enables a larger color + gamut and brightness range. HDR uses the BT2020 primaries and the PQ + transfer function. + + When HDR is enabled, _render_bit_depth_ is implicitly set to 10 unless + explicitly configured. Using a lower render bit depth may result in color + banding artifacts. + + HDR needs to be supported by the output and renderer to be enabled. + # SEE ALSO *sway*(5) *sway-input*(5) diff --git a/sway/sway.5.scd b/sway/sway.5.scd index 17d57049d..3301a644b 100644 --- a/sway/sway.5.scd +++ b/sway/sway.5.scd @@ -57,6 +57,7 @@ which you may only select one. *[...]* is used for optional arguments, and This section only lists general commands. For input and output commands, refer to *sway-input*(5) and *sway-output*(5). +## Config only commands The following commands may only be used in the configuration file. *bar* [] @@ -65,8 +66,8 @@ The following commands may only be used in the configuration file. *default_orientation* horizontal|vertical|auto Sets the default container layout for tiled containers. -*include* - Includes another file from _path_. _path_ can be either a full path or a +*include* + Include files from _paths_. _paths_ can include either a full path or a path relative to the parent config, and expands shell syntax (see *wordexp*(3) for details). The same include file can only be included once; subsequent attempts will be ignored. @@ -98,15 +99,16 @@ The following commands may only be used in the configuration file. machines, it may be desirable to have Xwayland started immediately by using _force_ instead of _enable_. +## Runtime only commands The following commands cannot be used directly in the configuration file. They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). *border* none|normal|csd|pixel [] Set border style for focused window. _normal_ includes a border of thickness _n_ and a title bar. _pixel_ is a border without title bar _n_ - pixels thick. Default is _normal_ with border thickness 2. _csd_ is short - for client-side-decorations, which allows the client to draw its own - decorations. + pixels thick. The title bar always shows in stacking or tabbed layouts. + _csd_ is short for client-side-decorations, which allows the client to draw + its own decorations. Default is _normal_ with border thickness 2. *border* toggle Cycles through the available border styles. @@ -115,7 +117,7 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). Exit sway and end your Wayland session. *floating* enable|disable|toggle - Make focused view floating, non-floating, or the opposite of what it is now. + Make focused window floating, non-floating, or the opposite of what it is now. *focus* Moves focus to the container that matches the specified criteria. @@ -150,9 +152,9 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1). Moves focus between the floating and tiled layers. *fullscreen* [enable|disable|toggle] [global] - Makes focused view fullscreen, non-fullscreen, or the opposite of what it + Makes focused window fullscreen, non-fullscreen, or the opposite of what it is now. If no argument is given, it does the same as _toggle_. If _global_ - is specified, the view will be fullscreen across all outputs. + is specified, the window will be fullscreen across all outputs. *gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current set|plus|minus|toggle @@ -162,16 +164,16 @@ set|plus|minus|toggle _vertical_. *inhibit_idle* focus|fullscreen|open|none|visible - Set/unset an idle inhibitor for the view. _focus_ will inhibit idle when - the view is focused by any seat. _fullscreen_ will inhibit idle when the + Set/unset an idle inhibitor for the window. _focus_ will inhibit idle when + the window is focused by any seat. _fullscreen_ will inhibit idle when the view is fullscreen (or a descendant of a fullscreen container) and is - visible. _open_ will inhibit idle until the view is closed (or the - inhibitor is unset/changed). _visible_ will inhibit idle when the view is + visible. _open_ will inhibit idle until the window is closed (or the + inhibitor is unset/changed). _visible_ will inhibit idle when the window is visible on any output. _none_ will remove any existing idle inhibitor for - the view. + the window. This can also be used with criteria to set an idle inhibitor for any - existing view or with _for_window_ to set idle inhibitors for future views. + existing window or with _for_window_ to set idle inhibitors for future windows. *layout* default|splith|splitv|stacking|tabbed Sets the layout mode of the focused container. @@ -215,6 +217,20 @@ set|plus|minus|toggle effect on the output the window is currently on. See *sway-output*(5) for further details. +*allow_tearing* yes|no + Allows or disallows screen tearing as a result of immediate page flips + for a fullscreen application. + + When this option is not set, the tearing hints provided by the application + determine whether tearing is allowed. When _yes_ is specified, + the application allows tearing regardless of the tearing hints. + When _no_ is specified, tearing will never be allowed on the application, + regardless of the tearing hints. + + This setting only has an effect if tearing is allowed on the output through + the per-output *allow_tearing* setting. See *sway-output*(5) for further + details. + *move* left|right|up|down [ px] Moves the focused container in the direction specified. The optional _px_ argument specifies how many pixels to move the container. If unspecified, @@ -286,28 +302,34 @@ set|plus|minus|toggle *rename* workspace [] to Rename either or the focused workspace to the -*resize* shrink|grow width|height [ [px|ppt]] +*resize* shrink|grow up|right|down|left|width|height [ [px|ppt]] Resizes the currently focused container by _amount_, specified in pixels or percentage points. If the units are omitted, floating containers are resized in px and tiled containers by ppt. _amount_ will default to 10 if omitted. + For tiling containers, space is taken/given from the container in the + specified direction. If _width_ or _height_ is specified, space will be + taken/given from all other containers. *resize* set height [px|ppt] Sets the height of the container to _height_, specified in pixels or percentage points. If the units are omitted, floating containers are resized in px and tiled containers by ppt. If _height_ is 0, the container - will not be resized. + will not be resized. For tiling containers, space is taken/given from all + other containers. *resize* set [width] [px|ppt] Sets the width of the container to _width_, specified in pixels or percentage points. If the units are omitted, floating containers are resized in px and tiled containers by ppt. If _width_ is 0, the container - will not be resized. + will not be resized. For tiling containers, space is taken/given from all + other containers. *resize* set [width] [px|ppt] [height] [px|ppt] Sets the width and height of the container to _width_ and _height_, specified in pixels or percentage points. If the units are omitted, floating containers are resized in px and tiled containers by ppt. If _width_ or _height_ is 0, the container will not be resized on that axis. + For tiling containers, space is taken/given from all other containers. *scratchpad* show Shows a window from the scratchpad. Repeatedly using this command will @@ -315,12 +337,12 @@ set|plus|minus|toggle *shortcuts_inhibitor* enable|disable Enables or disables the ability of clients to inhibit keyboard - shortcuts for a view. This is primarily useful for virtualization and - remote desktop software. It affects either the currently focused view - or a set of views selected by criteria. Subcommand _disable_ - additionally deactivates any active inhibitors for the given view(s). + shortcuts for a window. This is primarily useful for virtualization and + remote desktop software. It affects either the currently focused window + or a set of windows selected by criteria. Subcommand _disable_ + additionally deactivates any active inhibitors for the given window(s). Criteria are particularly useful with the *for_window* command to - configure a class of views differently from the per-seat defaults + configure a class of windows differently from the per-seat defaults established by the *seat* subcommand of the same name. See *sway-input*(5) for more ways to affect inhibitors. @@ -348,7 +370,7 @@ set|plus|minus|toggle Swaps the position, geometry, and fullscreen status of two containers. The first container can be selected either by criteria or focus. The second container can be selected by _id_, _con_id_, or _mark_. _id_ can only be - used with xwayland views. If the first container has focus, it will retain + used with xwayland windows. If the first container has focus, it will retain focus unless it is moved to a different workspace or the second container becomes fullscreen on the same workspace as the first container. In either of those cases, the second container will gain focus. @@ -356,12 +378,29 @@ set|plus|minus|toggle *title_format* Sets the format of window titles. The following placeholders may be used: - %title - The title supplied by the window ++ - %app_id - The wayland app ID (applicable to wayland windows only) ++ - %class - The X11 classname (applicable to xwayland windows only) ++ - %instance - The X11 instance (applicable to xwayland windows only) ++ - %shell - The protocol the window is using (typically xwayland or - xdg_shell) + *%title* + The title supplied by the window + + *%app_id* + The wayland app ID (applicable to wayland windows only) + + *%class* + The X11 classname (applicable to xwayland windows only) + + *%instance* + The X11 instance (applicable to xwayland windows only) + + *%shell* + The protocol the window is using (typically xwayland or xdg_shell) + + *%sandbox_engine* + The associated sandbox engine + + *%sandbox_app_id* + The app ID provided by the associated sandbox engine + + *%sandbox_instance_id* + The instance ID provided by the associated sandbox engine This command is typically used with *for_window* criteria. For example: @@ -371,18 +410,19 @@ set|plus|minus|toggle The default format is "%title". +## Config or runtime commands The following commands may be used either in the configuration file or at runtime. *assign* [→] [workspace] [number] - Assigns views matching _criteria_ (see *CRITERIA* for details) to + Assigns windows matching _criteria_ (see *CRITERIA* for details) to _workspace_. The → (U+2192) is optional and cosmetic. This command is equivalent to: for_window move container to workspace *assign* [→] output left|right|up|down| - Assigns views matching _criteria_ (see *CRITERIA* for details) to the + Assigns windows matching _criteria_ (see *CRITERIA* for details) to the specified output. The → (U+2192) is optional and cosmetic. This command is equivalent to: @@ -400,6 +440,12 @@ runtime. only be available for that group. By default, if you overwrite a binding, swaynag will give you a warning. To silence this, use the _--no-warn_ flag. + For specifying modifier keys, you can use the XKB modifier names _Shift_, + _Lock_ (for Caps Lock), _Control_, _Mod1_ (for Alt), _Mod2_ (for Num Lock), + _Mod3_ (for XKB modifier Mod3), _Mod4_ (for the Logo key), and _Mod5_ (for + AltGr). In addition, you can use the aliases _Ctrl_ (for Control), _Alt_ + (for Alt), and _Super_ (for the Logo key). + Unless the flag _--locked_ is set, the command will not be run when a screen locking program is active. If there is a matching binding with and without _--locked_, the one with will be preferred when locked and the @@ -460,11 +506,12 @@ runtime. *bindswitch* [--locked] [--no-warn] [--reload] : Binds to execute the sway command _command_ on state changes. - Supported switches are _lid_ (laptop lid) and _tablet_ (tablet mode) - switches. Valid values for _state_ are _on_, _off_ and _toggle_. These - switches are on when the device lid is shut and when tablet mode is active - respectively. _toggle_ is also supported to run a command both when the - switch is toggled on or off. + Supported switches are _lid_ (laptop lid), _tablet_ (tablet mode) and + _keypad_slide_ (whether the device keypad is exposed or not) switches. Valid + values for _state_ are _on_, _off_ and _toggle_. These switches are on when + the device lid is shut, when tablet mode is active and when the keypad is + exposed respectively. _toggle_ is also supported to run a command both when + the switch is toggled on or off. Unless the flag _--locked_ is set, the command will not be run when a screen locking program is active. If there is a matching binding with @@ -559,10 +606,10 @@ runtime. The window that has focus. *client.focused_inactive* - The most recently focused view within a container which is not focused. + The most recently focused window within a container which is not focused. *client.focused_tab_title* - A view that has focused descendant container. + A window that has focused descendant container. Tab or stack container title that is the parent of the focused container but is not directly focused. Defaults to focused_inactive if not specified and does not use the indicator and child_border colors. @@ -571,10 +618,10 @@ runtime. Ignored (present for i3 compatibility). *client.unfocused* - A view that does not have focus. + A window that does not have focus. *client.urgent* - A view with an urgency hint. *Note*: Native Wayland windows do not + A window with an urgency hint. *Note*: Native Wayland windows do not support urgency. Urgency only works for Xwayland windows. The meaning of each color is: @@ -589,12 +636,12 @@ runtime. The text color of the title bar. _indicator_ - The color used to indicate where a new view will open. In a tiled - container, this would paint the right border of the current view if a - new view would be opened to the right. + The color used to indicate where a new window will open. In a tiled + container, this would paint the right border of the current window if a + new window would be opened to the right. _child_border_ - The border around the view itself. + The border around the window itself. The default colors are: @@ -732,7 +779,7 @@ The default colors are: *gaps* inner|outer|horizontal|vertical|top|right|bottom|left Sets default _amount_ pixels of _inner_ or _outer_ gap, where the inner - affects spacing around each view and outer affects the spacing around each + affects spacing around each window and outer affects the spacing around each workspace. Outer gaps are in addition to inner gaps. To reduce or remove outer gaps, outer gaps can be set to a negative value. _outer_ gaps can also be specified per side with _top_, _right_, _bottom_, and _left_ or @@ -809,9 +856,9 @@ The default colors are: A list of output names may be obtained via *swaymsg -t get_outputs*. *popup_during_fullscreen* smart|ignore|leave_fullscreen - Determines what to do when a fullscreen view opens a dialog. + Determines what to do when a fullscreen window opens a dialog. If _smart_ (the default), the dialog will be displayed. If _ignore_, the - dialog will not be rendered. If _leave_fullscreen_, the view will exit + dialog will not be rendered. If _leave_fullscreen_, the window will exit fullscreen mode and the dialog will be rendered. *primary_selection* enabled|disabled @@ -937,18 +984,18 @@ The default colors are: A criteria is a string in the form of, for example: ``` -[class="[Rr]egex.*" title="some title"] +[app_id="some-application" title="[Rr]egex.*"] ``` The string contains one or more (space separated) attribute/value pairs. They -are used by some commands to choose which views to execute actions on. All +are used by some commands to choose which windows to execute actions on. All attributes must match for the criteria to match. Criteria is retained across commands separated by a *,*, but will be reset (and allow for new criteria, if desired) for commands separated by a *;*. Criteria may be used with either the *for_window* or *assign* commands to -specify operations to perform on new views. A criteria may also be used to -perform specific commands (ones that normally act upon one window) on all views +specify operations to perform on new windows. A criteria may also be used to +perform specific commands (ones that normally act upon one window) on all windows that match that criteria. For example: Focus on a window with the mark "IRC": @@ -957,10 +1004,19 @@ Focus on a window with the mark "IRC": [con_mark="IRC"] focus ``` -Kill all windows with the title "Emacs": +Kill all windows where the title contains "Emacs": ``` -[class="Emacs"] kill +[title="Emacs"] kill +``` + +Several attributes allow regular expressions. These use Perl-compatible regular +expressions (PCRE2), which are documented in *pcre2pattern*(3) and summarized in +*pcre2syntax*(3). For example, this moves all windows with titles ending in +"sway" or "Sway" to workspace 1: + +``` +[title="[Ss]way$"] move workspace 1 ``` You may like to use swaymsg -t get_tree for finding the values of these @@ -1011,6 +1067,9 @@ The following attributes may be matched with: Can be a regular expression. If value is \_\_focused\_\_, then the shell must be the same as that of the currently focused window. +*tag* + Compare value against the tag. _tag_ is specific to Wayland applications. + *tiling* Matches tiling windows. @@ -1036,10 +1095,27 @@ The following attributes may be matched with: applications and requires XWayland. *workspace* - Compare against the workspace name for this view. Can be a regular - expression. If the value is \_\_focused\_\_, then all the views on the + Compare against the workspace name for this window. Can be a regular + expression. If the value is \_\_focused\_\_, then all the windows on the currently focused workspace matches. +*sandbox_engine* + Compare against the associated sandbox engine. Can be a regular expression. + If the value is \_\_focused\_\_, then the sandbox engine must be the same as + that of the currently focused window. + +*sandbox_app_id* + Compare against the app ID provided by the associated sandbox engine. Can be + a regular expression. If the value is \_\_focused\_\_, then the sandbox app + ID must be the same as that of the currently focused window. + +*sandbox_instance_id* + Compare against the instance ID provided by the associated sandbox engine. + Can be a regular expression. If the value is \_\_focused\_\_, then the + sandbox instance ID must be the same as that of the currently focused + window. + # SEE ALSO *sway*(1) *sway-input*(5) *sway-output*(5) *sway-bar*(5) *sway-ipc*(7) +*pcre2pattern*(3) *pcre2syntax*(3) diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c index 5eba53ba4..c4fd3a260 100644 --- a/sway/sway_text_node.c +++ b/sway/sway_text_node.c @@ -58,31 +58,24 @@ struct text_buffer { static int get_text_width(struct sway_text_node *props) { int width = props->width; - if (props->max_width) { + if (props->max_width >= 0) { width = MIN(width, props->max_width); } return MAX(width, 0); } -static void update_source_box(struct text_buffer *buffer) { - struct sway_text_node *props = &buffer->props; - struct wlr_fbox source_box = { - .x = 0, - .y = 0, - .width = ceil(get_text_width(props) * buffer->scale), - .height = ceil(props->height * buffer->scale), - }; - - wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box); -} - static void render_backing_buffer(struct text_buffer *buffer) { if (!buffer->visible) { return; } + if (buffer->props.max_width == 0) { + wlr_scene_buffer_set_buffer(buffer->buffer_node, NULL); + return; + } + float scale = buffer->scale; - int width = ceil(buffer->props.width * scale); + int width = ceil(get_text_width(&buffer->props) * scale); int height = ceil(buffer->props.height * scale); float *color = (float *)&buffer->props.color; float *background = (float *)&buffer->props.background; @@ -142,13 +135,12 @@ static void render_backing_buffer(struct text_buffer *buffer) { wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base); wlr_buffer_drop(&cairo_buffer->base); - update_source_box(buffer); pixman_region32_t opaque; pixman_region32_init(&opaque); if (background[3] == 1) { pixman_region32_union_rect(&opaque, &opaque, 0, 0, - buffer->props.width, buffer->props.height); + get_text_width(&buffer->props), buffer->props.height); } wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque); pixman_region32_fini(&opaque); @@ -206,19 +198,24 @@ static void handle_destroy(struct wl_listener *listener, void *data) { static void text_calc_size(struct text_buffer *buffer) { struct sway_text_node *props = &buffer->props; - cairo_t *c = cairo_create(NULL); - if (!c) { - sway_log(SWAY_ERROR, "cairo_t allocation failed"); - return; + cairo_surface_t *recorder = cairo_recording_surface_create( + CAIRO_CONTENT_COLOR_ALPHA, NULL); + cairo_t *c = cairo_create(recorder); + cairo_surface_destroy(recorder); + if (cairo_status(c) != CAIRO_STATUS_SUCCESS) { + sway_log(SWAY_ERROR, "cairo_t allocation failed: %s", + cairo_status_to_string(cairo_status(c))); + goto out; } cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); get_text_size(c, config->font_description, &props->width, NULL, &props->baseline, 1, props->pango_markup, "%s", buffer->text); - cairo_destroy(c); wlr_scene_buffer_set_dest_size(buffer->buffer_node, get_text_width(props), props->height); +out: + cairo_destroy(c); } struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, @@ -236,6 +233,7 @@ struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, buffer->buffer_node = node; buffer->props.node = &node->node; + buffer->props.max_width = -1; buffer->text = strdup(text); if (!buffer->text) { free(buffer); @@ -288,15 +286,20 @@ void sway_text_node_set_text(struct sway_text_node *node, char *text) { void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) { struct text_buffer *buffer = wl_container_of(node, buffer, props); + if (max_width == buffer->props.max_width) { + return; + } buffer->props.max_width = max_width; wlr_scene_buffer_set_dest_size(buffer->buffer_node, get_text_width(&buffer->props), buffer->props.height); - update_source_box(buffer); render_backing_buffer(buffer); } void sway_text_node_set_background(struct sway_text_node *node, float background[4]) { struct text_buffer *buffer = wl_container_of(node, buffer, props); + if (memcmp(&node->background, background, sizeof(*background) * 4) == 0) { + return; + } memcpy(&node->background, background, sizeof(*background) * 4); render_backing_buffer(buffer); } diff --git a/sway/swaynag.c b/sway/swaynag.c index bc5e23ea4..204a5791a 100644 --- a/sway/swaynag.c +++ b/sway/swaynag.c @@ -63,36 +63,27 @@ bool swaynag_spawn(const char *swaynag_command, sway_log(SWAY_ERROR, "Failed to create fork for swaynag"); goto failed; } else if (pid == 0) { - restore_nofile_limit(); - - pid = fork(); - if (pid < 0) { - sway_log_errno(SWAY_ERROR, "fork failed"); - _exit(EXIT_FAILURE); - } else if (pid == 0) { - if (!sway_set_cloexec(sockets[1], false)) { - _exit(EXIT_FAILURE); - } - - if (swaynag->detailed) { - close(swaynag->fd[1]); - dup2(swaynag->fd[0], STDIN_FILENO); - close(swaynag->fd[0]); - } - - char wayland_socket_str[16]; - snprintf(wayland_socket_str, sizeof(wayland_socket_str), - "%d", sockets[1]); - setenv("WAYLAND_SOCKET", wayland_socket_str, true); - - size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2; - char *cmd = malloc(length); - snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args); - execlp("sh", "sh", "-c", cmd, NULL); - sway_log_errno(SWAY_ERROR, "execlp failed"); + if (!sway_set_cloexec(sockets[1], false)) { _exit(EXIT_FAILURE); } - _exit(EXIT_SUCCESS); + + if (swaynag->detailed) { + close(swaynag->fd[1]); + dup2(swaynag->fd[0], STDIN_FILENO); + close(swaynag->fd[0]); + } + + char wayland_socket_str[16]; + snprintf(wayland_socket_str, sizeof(wayland_socket_str), + "%d", sockets[1]); + setenv("WAYLAND_SOCKET", wayland_socket_str, true); + + size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2; + char *cmd = malloc(length); + snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args); + execlp("sh", "sh", "-c", cmd, NULL); + sway_log_errno(SWAY_ERROR, "execlp failed"); + _exit(EXIT_FAILURE); } if (swaynag->detailed) { @@ -107,11 +98,6 @@ bool swaynag_spawn(const char *swaynag_command, return false; } - if (waitpid(pid, NULL, 0) < 0) { - sway_log_errno(SWAY_ERROR, "waitpid failed"); - return false; - } - return true; failed: @@ -161,4 +147,3 @@ void swaynag_show(struct swaynag_instance *swaynag) { close(swaynag->fd[1]); } } - diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c index d4003fe65..faf54d02a 100644 --- a/sway/tree/arrange.c +++ b/sway/tree/arrange.c @@ -29,7 +29,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) { } } - // Calculate each height fraction + // Calculate each width fraction double total_width_fraction = 0; for (int i = 0; i < children->length; ++i) { struct sway_container *child = children->items[i]; @@ -82,12 +82,18 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) { child->pending.y = parent->y; child->pending.width = round(child->width_fraction * child_total_width); child->pending.height = parent->height; - child_x += child->pending.width + inner_gap; // Make last child use remaining width of parent if (i == children->length - 1) { child->pending.width = parent->x + parent->width - child->pending.x; } + + // Arbitrary lower bound for window size + if (child->pending.width < 10 || child->pending.height < 10) { + child->pending.width = 0; + child->pending.height = 0; + } + child_x += child->pending.width + inner_gap; } } @@ -161,12 +167,18 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) { child->pending.y = child_y; child->pending.width = parent->width; child->pending.height = round(child->height_fraction * child_total_height); - child_y += child->pending.height + inner_gap; // Make last child use remaining height of parent if (i == children->length - 1) { child->pending.height = parent->y + parent->height - child->pending.y; } + + // Arbitrary lower bound for window size + if (child->pending.width < 10 || child->pending.height < 10) { + child->pending.width = 0; + child->pending.height = 0; + } + child_y += child->pending.height + inner_gap; } } @@ -314,14 +326,9 @@ void arrange_output(struct sway_output *output) { if (config->reloading) { return; } - struct wlr_box output_box; - wlr_output_layout_get_box(root->output_layout, - output->wlr_output, &output_box); - output->lx = output_box.x; - output->ly = output_box.y; - output->width = output_box.width; - output->height = output_box.height; - + if (!output->wlr_output->enabled) { + return; + } for (int i = 0; i < output->workspaces->length; ++i) { struct sway_workspace *workspace = output->workspaces->items[i]; arrange_workspace(workspace); diff --git a/sway/tree/container.c b/sway/tree/container.c index 9224b4fb4..6880841bd 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -7,7 +7,6 @@ #include #include #include -#include "linux-dmabuf-unstable-v1-protocol.h" #include "sway/config.h" #include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" @@ -22,38 +21,10 @@ #include "sway/tree/workspace.h" #include "sway/xdg_decoration.h" #include "list.h" +#include "pango.h" #include "log.h" #include "stringop.h" -static void handle_output_enter( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, output_enter); - struct wlr_scene_output *output = data; - - if (con->view->foreign_toplevel) { - wlr_foreign_toplevel_handle_v1_output_enter( - con->view->foreign_toplevel, output->output); - } -} - -static void handle_output_leave( - struct wl_listener *listener, void *data) { - struct sway_container *con = wl_container_of( - listener, con, output_leave); - struct wlr_scene_output *output = data; - - if (con->view->foreign_toplevel) { - wlr_foreign_toplevel_handle_v1_output_leave( - con->view->foreign_toplevel, output->output); - } -} - -static bool handle_point_accepts_input( - struct wlr_scene_buffer *buffer, double *x, double *y) { - return false; -} - static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent, bool *failed) { if (*failed) { @@ -120,22 +91,6 @@ struct sway_container *container_create(struct sway_view *view) { c->border.bottom = alloc_rect_node(c->border.tree, &failed); c->border.left = alloc_rect_node(c->border.tree, &failed); c->border.right = alloc_rect_node(c->border.tree, &failed); - - c->output_handler = wlr_scene_buffer_create(c->border.tree, NULL); - if (!c->output_handler) { - sway_log(SWAY_ERROR, "Failed to allocate a scene node"); - failed = true; - } - - if (!failed) { - c->output_enter.notify = handle_output_enter; - wl_signal_add(&c->output_handler->events.output_enter, - &c->output_enter); - c->output_leave.notify = handle_output_leave; - wl_signal_add(&c->output_handler->events.output_leave, - &c->output_leave); - c->output_handler->point_accepts_input = handle_point_accepts_input; - } } if (!failed && !scene_descriptor_assign(&c->scene_tree->node, @@ -348,10 +303,12 @@ void container_arrange_title_bar(struct sway_container *con) { h_padding = width - config->titlebar_h_padding - marks_buffer_width; } - h_padding = MAX(h_padding, 0); + h_padding = MAX(h_padding, config->titlebar_h_padding); int alloc_width = MIN((int)node->width, width - h_padding - config->titlebar_h_padding); + alloc_width = MAX(alloc_width, 0); + sway_text_node_set_max_width(node, alloc_width); wlr_scene_node_set_position(node->node, h_padding, (height - node->height) >> 1); @@ -372,10 +329,12 @@ void container_arrange_title_bar(struct sway_container *con) { h_padding = config->titlebar_h_padding; } - h_padding = MAX(h_padding, 0); + h_padding = MAX(h_padding, config->titlebar_h_padding); int alloc_width = MIN((int) node->width, width - h_padding - config->titlebar_h_padding); + alloc_width = MAX(alloc_width, 0); + sway_text_node_set_max_width(node, alloc_width); wlr_scene_node_set_position(node->node, h_padding, (height - node->height) >> 1); @@ -495,6 +454,7 @@ void container_destroy(struct sway_container *con) { } free(con->title); free(con->formatted_title); + free(con->title_format); list_free(con->pending.children); list_free(con->current.children); @@ -502,7 +462,6 @@ void container_destroy(struct sway_container *con) { if (con->view && con->view->container == con) { con->view->container = NULL; - wlr_scene_node_destroy(&con->output_handler->node); if (con->view->destroying) { view_destroy(con->view); } @@ -530,8 +489,8 @@ void container_begin_destroy(struct sway_container *con) { container_end_mouse_operation(con); - con->node.destroying = true; node_set_dirty(&con->node); + con->node.destroying = true; if (con->scratchpad) { root_scratchpad_remove_container(con); @@ -641,6 +600,100 @@ bool container_has_ancestor(struct sway_container *descendant, return false; } +static char *escape_pango_markup(const char *buffer) { + size_t length = escape_markup_text(buffer, NULL); + char *escaped_title = calloc(length + 1, sizeof(char)); + escape_markup_text(buffer, escaped_title); + return escaped_title; +} + +static size_t append_prop(char *buffer, const char *value) { + if (!value) { + return 0; + } + // If using pango_markup in font, we need to escape all markup chars + // from values to make sure tags are not inserted by clients + if (config->pango_markup) { + char *escaped_value = escape_pango_markup(value); + lenient_strcat(buffer, escaped_value); + size_t len = strlen(escaped_value); + free(escaped_value); + return len; + } else { + lenient_strcat(buffer, value); + return strlen(value); + } +} + +/** + * Calculate and return the length of the formatted title. + * If buffer is not NULL, also populate the buffer with the formatted title. + */ +size_t parse_title_format(struct sway_container *container, char *buffer) { + if (!container->title_format || strcmp(container->title_format, "%title") == 0) { + if (container->view) { + return append_prop(buffer, view_get_title(container->view)); + } else { + return container_build_representation(container->pending.layout, container->pending.children, buffer); + } + } + + size_t len = 0; + char *format = container->title_format; + char *next = strchr(format, '%'); + while (next) { + // Copy everything up to the % + lenient_strncat(buffer, format, next - format); + len += next - format; + format = next; + + if (has_prefix(next, "%title")) { + if (container->view) { + len += append_prop(buffer, view_get_title(container->view)); + } else { + len += container_build_representation(container->pending.layout, container->pending.children, buffer); + } + format += strlen("%title"); + } else if (container->view) { + if (has_prefix(next, "%app_id")) { + len += append_prop(buffer, view_get_app_id(container->view)); + format += strlen("%app_id"); + } else if (has_prefix(next, "%class")) { + len += append_prop(buffer, view_get_class(container->view)); + format += strlen("%class"); + } else if (has_prefix(next, "%instance")) { + len += append_prop(buffer, view_get_instance(container->view)); + format += strlen("%instance"); + } else if (has_prefix(next, "%shell")) { + len += append_prop(buffer, view_get_shell(container->view)); + format += strlen("%shell"); + } else if (has_prefix(next, "%sandbox_engine")) { + len += append_prop(buffer, view_get_sandbox_engine(container->view)); + format += strlen("%sandbox_engine"); + } else if (has_prefix(next, "%sandbox_app_id")) { + len += append_prop(buffer, view_get_sandbox_app_id(container->view)); + format += strlen("%sandbox_app_id"); + } else if (has_prefix(next, "%sandbox_instance_id")) { + len += append_prop(buffer, view_get_sandbox_instance_id(container->view)); + format += strlen("%sandbox_instance_id"); + } else { + lenient_strcat(buffer, "%"); + ++format; + ++len; + } + } else { + lenient_strcat(buffer, "%"); + ++format; + ++len; + } + next = strchr(format, '%'); + } + lenient_strcat(buffer, format); + len += strlen(format); + + return len; +} + /** * Calculate and return the length of the tree representation. * An example tree representation is: V[Terminal, Firefox] @@ -685,7 +738,7 @@ size_t container_build_representation(enum sway_container_layout layout, len += strlen(identifier); lenient_strcat(buffer, identifier); } else { - len += 6; + len += strlen("(null)"); lenient_strcat(buffer, "(null)"); } } @@ -696,16 +749,14 @@ size_t container_build_representation(enum sway_container_layout layout, void container_update_representation(struct sway_container *con) { if (!con->view) { - size_t len = container_build_representation(con->pending.layout, - con->pending.children, NULL); + size_t len = parse_title_format(con, NULL); free(con->formatted_title); con->formatted_title = calloc(len + 1, sizeof(char)); if (!sway_assert(con->formatted_title, "Unable to allocate title string")) { return; } - container_build_representation(con->pending.layout, con->pending.children, - con->formatted_title); + parse_title_format(con, con->formatted_title); if (con->title_bar.title_text) { sway_text_node_set_text(con->title_bar.title_text, con->formatted_title); @@ -769,11 +820,11 @@ void floating_fix_coordinates(struct sway_container *con, struct wlr_box *old, s // Fall back to centering on the workspace. container_floating_move_to_center(con); } else { - int rel_x = con->pending.x - old->x + (con->pending.width / 2); - int rel_y = con->pending.y - old->y + (con->pending.height / 2); + double rel_x = con->pending.x - old->x + (con->pending.width / 2); + double rel_y = con->pending.y - old->y + (con->pending.height / 2); - con->pending.x = new->x + (double)(rel_x * new->width) / old->width - (con->pending.width / 2); - con->pending.y = new->y + (double)(rel_y * new->height) / old->height - (con->pending.height / 2); + con->pending.x = new->x + (rel_x * new->width) / old->width - (con->pending.width / 2); + con->pending.y = new->y + (rel_y * new->height) / old->height - (con->pending.height / 2); sway_log(SWAY_DEBUG, "Transformed container %p to coords (%f, %f)", con, con->pending.x, con->pending.y); } diff --git a/sway/tree/node.c b/sway/tree/node.c index 7aaf97627..48ae325e4 100644 --- a/sway/tree/node.c +++ b/sway/tree/node.c @@ -29,7 +29,7 @@ const char *node_type_to_str(enum sway_node_type type) { } void node_set_dirty(struct sway_node *node) { - if (node->dirty) { + if (node->dirty || node->destroying) { return; } node->dirty = true; diff --git a/sway/tree/output.c b/sway/tree/output.c index 2d11195e4..c401d0f1d 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include "sway/tree/workspace.h" #include "sway/ipc-server.h" #include "sway/layers.h" #include "sway/output.h" @@ -37,7 +39,7 @@ static void restore_workspaces(struct sway_output *output) { for (int j = 0; j < other->workspaces->length; j++) { struct sway_workspace *ws = other->workspaces->items[j]; struct sway_output *highest = - workspace_output_get_highest_available(ws, NULL); + workspace_output_get_highest_available(ws); if (highest == output) { workspace_detach(ws); output_add_workspace(output, ws); @@ -136,12 +138,11 @@ struct sway_output *output_create(struct wlr_output *wlr_output) { output->detected_subpixel = wlr_output->subpixel; output->scale_filter = SCALE_FILTER_NEAREST; - wl_signal_init(&output->events.disable); - wl_list_insert(&root->all_outputs, &output->link); output->workspaces = create_list(); output->current.workspaces = create_list(); + wl_list_init(&output->layer_surfaces); return output; } @@ -154,6 +155,7 @@ void output_enable(struct sway_output *output) { output->enabled = true; list_add(root->outputs, output); + sway_ext_workspace_output_enable(output); restore_workspaces(output); struct sway_workspace *ws = NULL; @@ -180,12 +182,7 @@ void output_enable(struct sway_output *output) { ws->layout = output_get_default_layout(output); } - input_manager_configure_xcursor(); - wl_signal_emit_mutable(&root->events.new_node, &output->node); - - arrange_layers(output); - arrange_root(); } static void evacuate_sticky(struct sway_workspace *old_ws, @@ -210,11 +207,8 @@ static void output_evacuate(struct sway_output *output) { return; } struct sway_output *fallback_output = NULL; - if (root->outputs->length > 1) { + if (root->outputs->length > 0) { fallback_output = root->outputs->items[0]; - if (fallback_output == output) { - fallback_output = root->outputs->items[1]; - } } while (output->workspaces->length) { @@ -223,7 +217,7 @@ static void output_evacuate(struct sway_output *output) { workspace_detach(workspace); struct sway_output *new_output = - workspace_output_get_highest_available(workspace, output); + workspace_output_get_highest_available(workspace); if (!new_output) { new_output = fallback_output; } @@ -278,7 +272,7 @@ void output_destroy(struct sway_output *output) { destroy_scene_layers(output); list_free(output->workspaces); list_free(output->current.workspaces); - wl_event_source_remove(output->repaint_timer); + wlr_color_transform_unref(output->color_transform); free(output); } @@ -292,20 +286,16 @@ void output_disable(struct sway_output *output) { } sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name); - wl_signal_emit_mutable(&output->events.disable, output); - - output_evacuate(output); + // Remove the output now to avoid interacting with it during e.g., + // transactions, as the output might be physically removed with the scene + // output destroyed. list_del(root->outputs, index); - output->enabled = false; - arrange_root(); - - // Reconfigure all devices, since devices with map_to_output directives for - // an output that goes offline should stop sending events as long as the - // output remains offline. - input_manager_configure_all_input_mappings(); + destroy_layers(output); + output_evacuate(output); + sway_ext_workspace_output_disable(output); } void output_begin_destroy(struct sway_output *output) { @@ -315,8 +305,8 @@ void output_begin_destroy(struct sway_output *output) { sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name); wl_signal_emit_mutable(&output->node.events.destroy, &output->node); - output->node.destroying = true; node_set_dirty(&output->node); + output->node.destroying = true; } struct sway_output *output_from_wlr_output(struct wlr_output *output) { @@ -347,6 +337,10 @@ void output_add_workspace(struct sway_output *output, } list_add(output->workspaces, workspace); workspace->output = output; + if (workspace->output && workspace->output->ext_workspace_group) { + wlr_ext_workspace_handle_v1_set_group(workspace->ext_workspace, + workspace->output->ext_workspace_group); + } node_set_dirty(&output->node); node_set_dirty(&workspace->node); } diff --git a/sway/tree/root.c b/sway/tree/root.c index ae3c3cb20..cf7170a48 100644 --- a/sway/tree/root.c +++ b/sway/tree/root.c @@ -19,12 +19,6 @@ struct sway_root *root; -static void output_layout_handle_change(struct wl_listener *listener, - void *data) { - arrange_root(); - transaction_commit_dirty(); -} - struct sway_root *root_create(struct wl_display *wl_display) { struct sway_root *root = calloc(1, sizeof(struct sway_root)); if (!root) { @@ -53,7 +47,7 @@ struct sway_root *root_create(struct wl_display *wl_display) { root->layers.shell_top = alloc_scene_tree(root->layer_tree, &failed); root->layers.fullscreen = alloc_scene_tree(root->layer_tree, &failed); root->layers.fullscreen_global = alloc_scene_tree(root->layer_tree, &failed); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND root->layers.unmanaged = alloc_scene_tree(root->layer_tree, &failed); #endif root->layers.shell_overlay = alloc_scene_tree(root->layer_tree, &failed); @@ -81,14 +75,10 @@ struct sway_root *root_create(struct wl_display *wl_display) { root->non_desktop_outputs = create_list(); root->scratchpad = create_list(); - root->output_layout_change.notify = output_layout_handle_change; - wl_signal_add(&root->output_layout->events.change, - &root->output_layout_change); return root; } void root_destroy(struct sway_root *root) { - wl_list_remove(&root->output_layout_change.link); list_free(root->scratchpad); list_free(root->non_desktop_outputs); list_free(root->outputs); @@ -209,6 +199,8 @@ void root_scratchpad_show(struct sway_container *con) { if (old_ws) { workspace_consider_destroy(old_ws); } + + container_raise_floating(con); } static void disable_fullscreen(struct sway_container *con, void *data) { @@ -222,9 +214,7 @@ void root_scratchpad_hide(struct sway_container *con) { struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); struct sway_workspace *ws = con->pending.workspace; - if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL && !con->pending.workspace) { - // If the container was made fullscreen global while in the scratchpad, - // it should be shown until fullscreen has been disabled + if (!con->pending.workspace) { return; } diff --git a/sway/tree/view.c b/sway/tree/view.c index 35b4b73f4..83b4972b1 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -1,16 +1,19 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #include #include #include -#include "config.h" -#if HAVE_XWAYLAND +#include +#if WLR_HAS_XWAYLAND #include #endif #include "list.h" @@ -33,32 +36,85 @@ #include "sway/tree/workspace.h" #include "sway/config.h" #include "sway/xdg_decoration.h" -#include "pango.h" #include "stringop.h" +static void handle_outputs_update( + struct wl_listener *listener, void *data) { + struct sway_view *view = wl_container_of(listener, view, outputs_update); + struct wlr_scene_outputs_update_event *event = data; + + struct wlr_foreign_toplevel_handle_v1 *toplevel = view->foreign_toplevel; + if (toplevel) { + struct wlr_foreign_toplevel_handle_v1_output *toplevel_output, *tmp; + wl_list_for_each_safe(toplevel_output, tmp, &toplevel->outputs, link) { + bool active = false; + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + if (scene_output->output == toplevel_output->output) { + active = true; + break; + } + } + + if (!active) { + wlr_foreign_toplevel_handle_v1_output_leave(toplevel, toplevel_output->output); + } + } + + for (size_t i = 0; i < event->size; i++) { + struct wlr_scene_output *scene_output = event->active[i]; + wlr_foreign_toplevel_handle_v1_output_enter(toplevel, scene_output->output); + } + } +} + +static bool handle_point_accepts_input( + struct wlr_scene_buffer *buffer, double *x, double *y) { + return false; +} + bool view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl) { bool failed = false; view->scene_tree = alloc_scene_tree(root->staging, &failed); view->content_tree = alloc_scene_tree(view->scene_tree, &failed); - - if (!failed && !scene_descriptor_assign(&view->scene_tree->node, - SWAY_SCENE_DESC_VIEW, view)) { - failed = true; - } - if (failed) { - wlr_scene_node_destroy(&view->scene_tree->node); - return false; + goto err; } + if (!scene_descriptor_assign(&view->scene_tree->node, SWAY_SCENE_DESC_VIEW, view)) { + goto err; + } + + view->output_handler = wlr_scene_buffer_create(view->scene_tree, NULL); + if (!view->output_handler) { + sway_log(SWAY_ERROR, "Failed to allocate a scene node"); + goto err; + } + + view->image_capture_scene = wlr_scene_create(); + if (view->image_capture_scene == NULL) { + goto err; + } + view->image_capture_scene->restack_xwayland_surfaces = false; + + view->outputs_update.notify = handle_outputs_update; + wl_signal_add(&view->output_handler->events.outputs_update, + &view->outputs_update); + view->output_handler->point_accepts_input = handle_point_accepts_input; + view->type = type; view->impl = impl; view->executed_criteria = create_list(); view->allow_request_urgent = true; view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT; + view->tearing_mode = TEARING_WINDOW_HINT; wl_signal_init(&view->events.unmap); return true; + +err: + wlr_scene_node_destroy(&view->scene_tree->node); + return false; } void view_destroy(struct sway_view *view) { @@ -78,9 +134,8 @@ void view_destroy(struct sway_view *view) { list_free(view->executed_criteria); view_assign_ctx(view, NULL); + wlr_scene_node_destroy(&view->image_capture_scene->tree.node); wlr_scene_node_destroy(&view->scene_tree->node); - free(view->title_format); - if (view->impl->destroy) { view->impl->destroy(view); } else { @@ -93,6 +148,7 @@ void view_begin_destroy(struct sway_view *view) { return; } view->destroying = true; + wl_list_remove(&view->outputs_update.link); if (!view->container) { view_destroy(view); @@ -126,7 +182,7 @@ const char *view_get_instance(struct sway_view *view) { } return NULL; } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND uint32_t view_get_x11_window_id(struct sway_view *view) { if (view->impl->get_int_prop) { return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); @@ -155,11 +211,46 @@ uint32_t view_get_window_type(struct sway_view *view) { return 0; } +static const struct wlr_security_context_v1_state *security_context_from_view( + struct sway_view *view) { + const struct wl_client *client = + wl_resource_get_client(view->surface->resource); + const struct wlr_security_context_v1_state *security_context = + wlr_security_context_manager_v1_lookup_client( + server.security_context_manager_v1, client); + return security_context; +} + +const char *view_get_sandbox_engine(struct sway_view *view) { + const struct wlr_security_context_v1_state *security_context = + security_context_from_view(view); + return security_context ? security_context->sandbox_engine : NULL; +} + +const char *view_get_sandbox_app_id(struct sway_view *view) { + const struct wlr_security_context_v1_state *security_context = + security_context_from_view(view); + return security_context ? security_context->app_id : NULL; +} + +const char *view_get_sandbox_instance_id(struct sway_view *view) { + const struct wlr_security_context_v1_state *security_context = + security_context_from_view(view); + return security_context ? security_context->instance_id : NULL; +} + +const char *view_get_tag(struct sway_view *view) { + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_TAG); + } + return NULL; +} + const char *view_get_shell(struct sway_view *view) { switch(view->type) { case SWAY_VIEW_XDG_SHELL: return "xdg_shell"; -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND case SWAY_VIEW_XWAYLAND: return "xwayland"; #endif @@ -173,9 +264,9 @@ void view_get_constraints(struct sway_view *view, double *min_width, view->impl->get_constraints(view, min_width, max_width, min_height, max_height); } else { - *min_width = DBL_MIN; + *min_width = 1; *max_width = DBL_MAX; - *min_height = DBL_MIN; + *min_height = 1; *max_height = DBL_MAX; } } @@ -189,6 +280,10 @@ uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, } bool view_inhibit_idle(struct sway_view *view) { + if (server.session_lock.lock) { + return false; + } + struct sway_idle_inhibitor_v1 *user_inhibitor = sway_idle_inhibit_v1_user_inhibitor_for_view(view); @@ -261,7 +356,7 @@ void view_autoconfigure(struct sway_view *view) { } struct sway_output *output = ws ? ws->output : NULL; - if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) { + if (output && con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) { con->pending.content_x = output->lx; con->pending.content_y = output->ly; con->pending.content_width = output->width; @@ -365,8 +460,8 @@ void view_autoconfigure(struct sway_view *view) { con->pending.content_x = x; con->pending.content_y = y; - con->pending.content_width = width; - con->pending.content_height = height; + con->pending.content_width = fmax(width, 1); + con->pending.content_height = fmax(height, 1); } void view_set_activated(struct sway_view *view, bool activated) { @@ -484,10 +579,12 @@ void view_execute_criteria(struct sway_view *view) { sway_log(SWAY_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", criteria->raw, view, criteria->cmdlist); list_add(view->executed_criteria, criteria); - list_t *res_list = execute_command( - criteria->cmdlist, NULL, view->container); + list_t *res_list = execute_command(criteria->cmdlist, NULL, view->container); while (res_list->length) { struct cmd_results *res = res_list->items[0]; + if (res->status != CMD_SUCCESS) { + sway_log(SWAY_ERROR, "for_window '%s' failed: %s", criteria->raw, res->error); + } free_cmd_results(res); list_del(res_list, 0); } @@ -499,7 +596,7 @@ void view_execute_criteria(struct sway_view *view) { static void view_populate_pid(struct sway_view *view) { pid_t pid; switch (view->type) { -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND case SWAY_VIEW_XWAYLAND:; struct wlr_xwayland_surface *surf = wlr_xwayland_surface_try_from_wlr_surface(view->surface); @@ -741,6 +838,14 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, ws = select_workspace(view); } + if (ws && ws->output) { + // Once the output is determined, we can notify the client early about + // scale to reduce startup jitter. + float scale = ws->output->wlr_output->scale; + wlr_fractional_scale_v1_notify_scale(wlr_surface, scale); + wlr_surface_set_preferred_buffer_scale(wlr_surface, ceil(scale)); + } + struct sway_seat *seat = input_manager_current_seat(); struct sway_node *node = seat_get_focus_inactive(seat, ws ? &ws->node : &root->node); @@ -772,6 +877,7 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, }; view->ext_foreign_toplevel = wlr_ext_foreign_toplevel_handle_v1_create(server.foreign_toplevel_list, &foreign_toplevel_state); + view->ext_foreign_toplevel->data = view; view->foreign_toplevel = wlr_foreign_toplevel_handle_v1_create(server.foreign_toplevel_manager); @@ -838,10 +944,10 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, bool set_focus = should_focus(view); -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *xsurface; if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface))) { - set_focus &= wlr_xwayland_icccm_input_model(xsurface) != + set_focus &= wlr_xwayland_surface_icccm_input_model(xsurface) != WLR_ICCCM_INPUT_MODEL_NONE; } #endif @@ -927,11 +1033,14 @@ void view_update_size(struct sway_view *view) { void view_center_and_clip_surface(struct sway_view *view) { struct sway_container *con = view->container; - if (container_is_floating(con)) { + bool clip_to_geometry = true; + + if (container_is_floating(con) || con->pending.fullscreen_mode != FULLSCREEN_NONE) { // We always center the current coordinates rather than the next, as the // geometry immediately affects the currently active rendering. int x = (int) fmax(0, (con->current.content_width - view->geometry.width) / 2); int y = (int) fmax(0, (con->current.content_height - view->geometry.height) / 2); + clip_to_geometry = !view->using_csd; wlr_scene_node_set_position(&view->content_tree->node, x, y); } else { @@ -940,12 +1049,16 @@ void view_center_and_clip_surface(struct sway_view *view) { // only make sure to clip the content if there is content to clip if (!wl_list_empty(&con->view->content_tree->children)) { - wlr_scene_subsurface_tree_set_clip(&con->view->content_tree->node, &(struct wlr_box){ - .x = con->view->geometry.x, - .y = con->view->geometry.y, - .width = con->current.content_width, - .height = con->current.content_height, - }); + struct wlr_box clip = {0}; + if (clip_to_geometry) { + clip = (struct wlr_box){ + .x = con->view->geometry.x, + .y = con->view->geometry.y, + .width = con->current.content_width, + .height = con->current.content_height, + }; + } + wlr_scene_subsurface_tree_set_clip(&con->view->content_tree->node, &clip); } } @@ -954,7 +1067,7 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { if ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface))) { return view_from_wlr_xdg_surface(xdg_surface); } -#if HAVE_XWAYLAND +#if WLR_HAS_XWAYLAND struct wlr_xwayland_surface *xsurface; if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface))) { return view_from_wlr_xwayland_surface(xsurface); @@ -967,6 +1080,9 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) { return NULL; } + if (wlr_session_lock_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) { + return NULL; + } const char *role = wlr_surface->role ? wlr_surface->role->name : NULL; sway_log(SWAY_DEBUG, "Surface of unknown type (role %s): %p", @@ -974,77 +1090,6 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { return NULL; } -static char *escape_pango_markup(const char *buffer) { - size_t length = escape_markup_text(buffer, NULL); - char *escaped_title = calloc(length + 1, sizeof(char)); - escape_markup_text(buffer, escaped_title); - return escaped_title; -} - -static size_t append_prop(char *buffer, const char *value) { - if (!value) { - return 0; - } - // If using pango_markup in font, we need to escape all markup chars - // from values to make sure tags are not inserted by clients - if (config->pango_markup) { - char *escaped_value = escape_pango_markup(value); - lenient_strcat(buffer, escaped_value); - size_t len = strlen(escaped_value); - free(escaped_value); - return len; - } else { - lenient_strcat(buffer, value); - return strlen(value); - } -} - -/** - * Calculate and return the length of the formatted title. - * If buffer is not NULL, also populate the buffer with the formatted title. - */ -static size_t parse_title_format(struct sway_view *view, char *buffer) { - if (!view->title_format || strcmp(view->title_format, "%title") == 0) { - return append_prop(buffer, view_get_title(view)); - } - - size_t len = 0; - char *format = view->title_format; - char *next = strchr(format, '%'); - while (next) { - // Copy everything up to the % - lenient_strncat(buffer, format, next - format); - len += next - format; - format = next; - - if (strncmp(next, "%title", 6) == 0) { - len += append_prop(buffer, view_get_title(view)); - format += 6; - } else if (strncmp(next, "%app_id", 7) == 0) { - len += append_prop(buffer, view_get_app_id(view)); - format += 7; - } else if (strncmp(next, "%class", 6) == 0) { - len += append_prop(buffer, view_get_class(view)); - format += 6; - } else if (strncmp(next, "%instance", 9) == 0) { - len += append_prop(buffer, view_get_instance(view)); - format += 9; - } else if (strncmp(next, "%shell", 6) == 0) { - len += append_prop(buffer, view_get_shell(view)); - format += 6; - } else { - lenient_strcat(buffer, "%"); - ++format; - ++len; - } - next = strchr(format, '%'); - } - lenient_strcat(buffer, format); - len += strlen(format); - - return len; -} - void view_update_app_id(struct sway_view *view) { const char *app_id = view_get_app_id(view); @@ -1073,7 +1118,7 @@ void view_update_title(struct sway_view *view, bool force) { free(view->container->title); free(view->container->formatted_title); - size_t len = parse_title_format(view, NULL); + size_t len = parse_title_format(view->container, NULL); if (len) { char *buffer = calloc(len + 1, sizeof(char)); @@ -1081,7 +1126,7 @@ void view_update_title(struct sway_view *view, bool force) { return; } - parse_title_format(view, buffer); + parse_title_format(view->container, buffer); view->container->formatted_title = buffer; } else { view->container->formatted_title = NULL; @@ -1178,7 +1223,7 @@ void view_set_urgent(struct sway_view *view, bool enable) { ipc_event_window(view->container, "urgent"); - if (!container_is_scratchpad_hidden(view->container)) { + if (!container_is_scratchpad_hidden_or_child(view->container)) { workspace_detect_urgent(view->container->pending.workspace); } } @@ -1210,6 +1255,10 @@ static void view_save_buffer_iterator(struct wlr_scene_buffer *buffer, wlr_scene_buffer_set_dest_size(sbuf, buffer->dst_width, buffer->dst_height); wlr_scene_buffer_set_opaque_region(sbuf, &buffer->opaque_region); + wlr_scene_buffer_set_opacity(sbuf, buffer->opacity); + wlr_scene_buffer_set_filter_mode(sbuf, buffer->filter_mode); + wlr_scene_buffer_set_transfer_function(sbuf, buffer->transfer_function); + wlr_scene_buffer_set_primaries(sbuf, buffer->primaries); wlr_scene_buffer_set_source_box(sbuf, &buffer->src_box); wlr_scene_node_set_position(&sbuf->node, sx, sy); wlr_scene_buffer_set_transform(sbuf, buffer->transform); @@ -1227,8 +1276,10 @@ void view_save_buffer(struct sway_view *view) { return; } - // Enable and disable the saved surface tree like so to atomitaclly update - // the tree. This will prevent over damaging or other weirdness. + // Make sure the output handler is placed above the saved surface so we don't send + // spurious events to the foreign toplevel handler. Also, make the saved surface tree + // is disabled until it is ready to replace the real surface. + wlr_scene_node_place_below(&view->saved_surface_tree->node, &view->output_handler->node); wlr_scene_node_set_enabled(&view->saved_surface_tree->node, false); wlr_scene_node_for_each_buffer(&view->content_tree->node, @@ -1244,10 +1295,27 @@ bool view_is_transient_for(struct sway_view *child, child->impl->is_transient_for(child, ancestor); } +bool view_can_tear(struct sway_view *view) { + switch (view->tearing_mode) { + case TEARING_OVERRIDE_FALSE: + return false; + case TEARING_OVERRIDE_TRUE: + return true; + case TEARING_WINDOW_HINT: + return view->tearing_hint == + WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC; + } + return false; +} + static void send_frame_done_iterator(struct wlr_scene_buffer *scene_buffer, int x, int y, void *data) { struct timespec *when = data; - wl_signal_emit_mutable(&scene_buffer->events.frame_done, when); + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(scene_buffer); + if (scene_surface == NULL) { + return; + } + wlr_surface_send_frame_done(scene_surface->surface, when); } void view_send_frame_done(struct sway_view *view) { diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index a68dc9277..9d78d7080 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -3,22 +3,143 @@ #include #include #include +#include #include +#include +#include "log.h" #include "stringop.h" +#include "sway/desktop/transaction.h" #include "sway/input/input-manager.h" #include "sway/input/cursor.h" #include "sway/input/seat.h" #include "sway/ipc-server.h" #include "sway/output.h" +#include "sway/server.h" #include "sway/tree/arrange.h" #include "sway/tree/container.h" #include "sway/tree/node.h" #include "sway/tree/view.h" #include "sway/tree/workspace.h" #include "list.h" -#include "log.h" #include "util.h" +static const uint32_t WORKSPACE_CAPABILITIES = + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE | + EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN; + +static const uint32_t GROUP_CAPABILITIES = + EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE; + +// Helper to find the output associated with a workspace group. +static struct sway_output *group_to_output( + struct wlr_ext_workspace_group_handle_v1 *group) { + for (int i = 0; i < root->outputs->length; i++) { + struct sway_output *output = root->outputs->items[i]; + if (output->ext_workspace_group == group) { + return output; + } + } + abort(); // unreachable +} + +// Callback for ext-workspace-v1 commit events. +static void handle_commit(struct wl_listener *listener, void *data) { + struct sway_server *server = + wl_container_of(listener, server, workspace_manager_v1_commit); + struct wlr_ext_workspace_v1_commit_event *event = data; + + struct wlr_ext_workspace_v1_request *req, *tmp; + wl_list_for_each_safe(req, tmp, event->requests, link) { + switch (req->type) { + case WLR_EXT_WORKSPACE_V1_REQUEST_ACTIVATE: + if (req->activate.workspace) { + workspace_switch(req->activate.workspace->data); + } + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_CREATE_WORKSPACE:; + struct sway_output *output = group_to_output(req->create_workspace.group); + sway_assert(output, "NULL output given to create_workspace"); + + char *name; + if (req->create_workspace.name) { + if (workspace_by_name(req->create_workspace.name)) { + sway_log(SWAY_ERROR, "Refusing to create workspace with duplicate name."); + break; // Already exists. + } + name = strdup(req->create_workspace.name); + } else { + name = workspace_next_name(output->wlr_output->name); + } + + struct sway_workspace *new_ws = workspace_create(output, name); + if (new_ws) { + workspace_switch(new_ws); + } + free(name); + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_ASSIGN:; + if (!req->assign.workspace || !req->assign.group) break; + + struct sway_workspace *ws = req->assign.workspace->data; + struct sway_output *new_output = group_to_output(req->assign.group); + struct sway_output *old_output = ws->output; + workspace_move_to_output(ws, new_output); + arrange_output(old_output); + arrange_output(new_output); + break; + case WLR_EXT_WORKSPACE_V1_REQUEST_DEACTIVATE: + case WLR_EXT_WORKSPACE_V1_REQUEST_REMOVE: + break; // No-op. + } + } + + transaction_commit_dirty(); +} + +// Initialize ext-workspace. Must be called once at startup. +bool sway_ext_workspace_init(void) { + server.workspace_manager_v1 = + wlr_ext_workspace_manager_v1_create(server.wl_display, 1); + if (!server.workspace_manager_v1) { + sway_log(SWAY_ERROR, "Failed to create ext_workspace_manager_v1"); + return false; + } + + server.workspace_manager_v1_commit.notify = handle_commit; + wl_signal_add(&server.workspace_manager_v1->events.commit, + &server.workspace_manager_v1_commit); + return true; +} + +// Must be called whenever an output is enabled. +void sway_ext_workspace_output_enable(struct sway_output *output) { + if (!output->wlr_output) { + return; + } + + output->ext_workspace_group = + wlr_ext_workspace_group_handle_v1_create( + server.workspace_manager_v1, GROUP_CAPABILITIES); + if (!output->ext_workspace_group) { + sway_log(SWAY_ERROR, "Failed to create workspace group for output '%s'", + output->wlr_output->name); + return; + } + + wlr_ext_workspace_group_handle_v1_output_enter( + output->ext_workspace_group, output->wlr_output); +} + +// Must be called whenever an output is disabled. +void sway_ext_workspace_output_disable(struct sway_output *output) { + if (!output->ext_workspace_group) { + return; + } + + wlr_ext_workspace_group_handle_v1_destroy(output->ext_workspace_group); + output->ext_workspace_group = NULL; +} + struct workspace_config *workspace_find_config(const char *ws_name) { for (int i = 0; i < config->workspace_configs->length; ++i) { struct workspace_config *wsc = config->workspace_configs->items[i]; @@ -69,6 +190,16 @@ struct sway_workspace *workspace_create(struct sway_output *output, sway_log(SWAY_ERROR, "Unable to allocate sway_workspace"); return NULL; } + + ws->ext_workspace = wlr_ext_workspace_handle_v1_create( + server.workspace_manager_v1, NULL, WORKSPACE_CAPABILITIES); + if (!ws->ext_workspace) { + sway_log(SWAY_ERROR, "Failed to create ext_workspace for '%s'", name); + free(ws); + return NULL; + } + ws->ext_workspace->data = ws; + node_init(&ws->node, N_WORKSPACE, ws); bool failed = false; @@ -78,6 +209,7 @@ struct sway_workspace *workspace_create(struct sway_output *output, if (failed) { wlr_scene_node_destroy(&ws->layers.tiling->node); wlr_scene_node_destroy(&ws->layers.fullscreen->node); + wlr_ext_workspace_handle_v1_destroy(ws->ext_workspace); free(ws); return NULL; } @@ -126,6 +258,13 @@ struct sway_workspace *workspace_create(struct sway_output *output, output_add_workspace(output, ws); output_sort_workspaces(output); + wlr_ext_workspace_handle_v1_set_name(ws->ext_workspace, ws->name); + if (ws->output && ws->output->ext_workspace_group) { + wlr_ext_workspace_handle_v1_set_group(ws->ext_workspace, + ws->output->ext_workspace_group); + } + wlr_ext_workspace_handle_v1_set_active(ws->ext_workspace, + workspace_is_visible(ws)); ipc_event_workspace(NULL, ws, "init"); wl_signal_emit_mutable(&root->events.new_node, &ws->node); @@ -162,11 +301,14 @@ void workspace_begin_destroy(struct sway_workspace *workspace) { ipc_event_workspace(NULL, workspace, "empty"); // intentional wl_signal_emit_mutable(&workspace->node.events.destroy, &workspace->node); + wlr_ext_workspace_handle_v1_destroy(workspace->ext_workspace); + workspace->ext_workspace = NULL; + if (workspace->output) { workspace_detach(workspace); } - workspace->node.destroying = true; node_set_dirty(&workspace->node); + workspace->node.destroying = true; } void workspace_consider_destroy(struct sway_workspace *ws) { @@ -201,8 +343,10 @@ static bool workspace_valid_on_output(const char *output_name, } for (int i = 0; i < wsc->outputs->length; i++) { - if (output_match_name_or_id(output, wsc->outputs->items[i])) { - return true; + struct sway_output *ws_output = + output_by_name_or_id(wsc->outputs->items[i]); + if (ws_output) { + return ws_output == output; } } @@ -245,7 +389,7 @@ static void workspace_name_from_binding(const struct sway_binding * binding, } // If the command is workspace number , isolate the name - if (strncmp(_target, "number ", strlen("number ")) == 0) { + if (has_prefix(_target, "number ")) { size_t length = strlen(_target) - strlen("number ") + 1; char *temp = malloc(length); strncpy(temp, _target + strlen("number "), length - 1); @@ -319,10 +463,14 @@ char *workspace_next_name(const char *output_name) { } bool found = false; for (int j = 0; j < wsc->outputs->length; ++j) { - if (output_match_name_or_id(output, wsc->outputs->items[j])) { - found = true; - free(target); - target = strdup(wsc->workspace); + struct sway_output *ws_output = + output_by_name_or_id(wsc->outputs->items[j]); + if (ws_output) { + if (ws_output == output) { + found = true; + free(target); + target = strdup(wsc->workspace); + } break; } } @@ -658,13 +806,9 @@ void workspace_output_add_priority(struct sway_workspace *workspace, } struct sway_output *workspace_output_get_highest_available( - struct sway_workspace *ws, struct sway_output *exclude) { + struct sway_workspace *ws) { for (int i = 0; i < ws->output_priority->length; i++) { const char *name = ws->output_priority->items[i]; - if (exclude && output_match_name_or_id(exclude, name)) { - continue; - } - struct sway_output *output = output_by_name_or_id(name); if (output) { return output; @@ -684,6 +828,7 @@ void workspace_detect_urgent(struct sway_workspace *workspace) { if (workspace->urgent != new_urgent) { workspace->urgent = new_urgent; + wlr_ext_workspace_handle_v1_set_urgent(workspace->ext_workspace, workspace->urgent); ipc_event_workspace(NULL, workspace, "urgent"); } } @@ -707,6 +852,11 @@ void workspace_for_each_container(struct sway_workspace *ws, struct sway_container *workspace_find_container(struct sway_workspace *ws, bool (*test)(struct sway_container *con, void *data), void *data) { struct sway_container *result = NULL; + if (ws == NULL){ + sway_log(SWAY_ERROR, "Cannot find container with no workspace."); + return NULL; + } + // Tiling for (int i = 0; i < ws->tiling->length; ++i) { struct sway_container *child = ws->tiling->items[i]; @@ -770,7 +920,7 @@ void workspace_unwrap_children(struct sway_workspace *ws, while (wrap->pending.children->length) { struct sway_container *child = wrap->pending.children->items[0]; container_detach(child); - workspace_add_tiling(ws, child); + workspace_insert_tiling_direct(ws, child, ws->tiling->length); } } @@ -977,3 +1127,35 @@ void workspace_squash(struct sway_workspace *workspace) { i += container_squash(child); } } + +void workspace_move_to_output(struct sway_workspace *workspace, + struct sway_output *output) { + if (workspace->output == output) { + return; + } + struct sway_output *old_output = workspace->output; + workspace_detach(workspace); + struct sway_workspace *new_output_old_ws = + output_get_active_workspace(output); + if (!sway_assert(new_output_old_ws, "Expected output to have a workspace")) { + return; + } + + output_add_workspace(output, workspace); + + // If moving the last workspace from the old output, create a new workspace + // on the old output + if (old_output->workspaces->length == 0) { + char *ws_name = workspace_next_name(old_output->wlr_output->name); + struct sway_workspace *ws = workspace_create(old_output, ws_name); + free(ws_name); + struct sway_seat *seat = input_manager_current_seat(); + seat_set_raw_focus(seat, &ws->node); + } + + workspace_consider_destroy(new_output_old_ws); + + output_sort_workspaces(output); + workspace_output_raise_priority(workspace, old_output, output); + ipc_event_workspace(NULL, workspace, "move"); +} diff --git a/sway/xdg_activation_v1.c b/sway/xdg_activation_v1.c index b7c80dd45..fd6048746 100644 --- a/sway/xdg_activation_v1.c +++ b/sway/xdg_activation_v1.c @@ -38,14 +38,14 @@ void xdg_activation_v1_handle_request_activate(struct wl_listener *listener, } // This is an activation request. If this context is internal we have ctx->seat. - struct sway_seat *seat = ctx->seat; - if (!seat) { - // Otherwise, use the seat indicated by the launcher client in set_serial - seat = ctx->token->seat ? ctx->token->seat->data : NULL; + if (ctx->seat) { + view_request_activate(view, ctx->seat); + return; } - if (seat && ctx->had_focused_surface) { - view_request_activate(view, seat); + // Otherwise, activate if passed from another focused client + if (ctx->token->seat && ctx->had_focused_surface) { + view_request_activate(view, ctx->token->seat->data); } else { // The token is valid, but cannot be used to activate a window view_request_urgent(view); diff --git a/swaybar/bar.c b/swaybar/bar.c index 5b1213a8d..4d20f20f0 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -508,7 +508,7 @@ void bar_run(struct swaybar *bar) { } #if HAVE_TRAY if (bar->tray) { - loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus); + loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar); } #endif while (bar->running) { diff --git a/swaybar/ipc.c b/swaybar/ipc.c index 03500bdf3..c3929a35f 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -15,6 +15,7 @@ #include "list.h" #include "log.h" #include "loop.h" +#include "stringop.h" #include "util.h" void ipc_send_workspace_command(struct swaybar *bar, const char *ws) { @@ -45,8 +46,8 @@ void ipc_send_workspace_command(struct swaybar *bar, const char *ws) { char *parse_font(const char *font) { char *new_font = NULL; - if (strncmp("pango:", font, 6) == 0) { - font += 6; + if (has_prefix(font, "pango:")) { + font += strlen("pango:"); } new_font = strdup(font); return new_font; @@ -416,6 +417,28 @@ void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind) { } bool ipc_initialize(struct swaybar *bar) { + if (!bar->id) { + uint32_t len = 0; + char *res = ipc_single_command(bar->ipc_socketfd, + IPC_GET_BAR_CONFIG, "", &len); + json_object *bars = json_tokener_parse(res); + if (!json_object_is_type(bars, json_type_array) + || json_object_array_length(bars) == 0) { + sway_log(SWAY_ERROR, "No bar configuration found, " + "please configure a bar block in your sway config file."); + json_object_put(bars); + free(res); + return false; + } + json_object *first = json_object_array_get_idx(bars, 0); + bar->id = strdup(json_object_get_string(first)); + json_object_put(bars); + free(res); + sway_log(SWAY_INFO, "Using first bar config: %s. " + "Use --bar_id to manually select a different bar configuration.", + bar->id); + } + uint32_t len = strlen(bar->id); char *res = ipc_single_command(bar->ipc_socketfd, IPC_GET_BAR_CONFIG, bar->id, &len); @@ -518,8 +541,7 @@ static bool handle_barconfig_update(struct swaybar *bar, const char *payload, #if HAVE_TRAY if (oldcfg->tray_hidden && !newcfg->tray_hidden) { bar->tray = create_tray(bar); - loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, - bar->tray->bus); + loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar); } else if (bar->tray && newcfg->tray_hidden) { loop_remove_fd(bar->eventloop, bar->tray->fd); destroy_tray(bar->tray); diff --git a/swaybar/main.c b/swaybar/main.c index 3dc672334..c2020ff09 100644 --- a/swaybar/main.c +++ b/swaybar/main.c @@ -72,12 +72,6 @@ int main(int argc, char **argv) { sway_log_init(SWAY_INFO, NULL); } - if (!swaybar.id) { - sway_log(SWAY_ERROR, "No bar_id passed. " - "Provide --bar_id or let sway start swaybar"); - return 1; - } - if (!socket_path) { socket_path = get_socketpath(); if (!socket_path) { @@ -93,8 +87,9 @@ int main(int argc, char **argv) { free(socket_path); - signal(SIGINT, sig_handler); - signal(SIGTERM, sig_handler); + struct sigaction sa = { .sa_handler = sig_handler }; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); swaybar.running = true; bar_run(&swaybar); diff --git a/swaybar/render.c b/swaybar/render.c index 879a4e42a..c51321f18 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -13,7 +13,6 @@ #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/status_line.h" -#include "log.h" #if HAVE_TRAY #include "swaybar/tray/tray.h" #endif @@ -21,7 +20,7 @@ static const int WS_HORIZONTAL_PADDING = 5; static const double WS_VERTICAL_PADDING = 1.5; -static const double BORDER_WIDTH = 1; +static const int BORDER_WIDTH = 1; struct render_context { cairo_t *cairo; @@ -29,6 +28,7 @@ struct render_context { cairo_font_options_t *textaa_sharp; cairo_font_options_t *textaa_safe; uint32_t background_color; + bool has_transparency; }; static void choose_text_aa_mode(struct render_context *ctx, uint32_t fontcolor) { @@ -265,6 +265,7 @@ static uint32_t render_status_block(struct render_context *ctx, uint32_t bg_color = block->urgent ? config->colors.urgent_workspace.background : block->background; + ctx->has_transparency |= (bg_color & 0xFF) != 0xFF; if (bg_color) { render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, block_width, render_height); @@ -291,11 +292,11 @@ static uint32_t render_status_block(struct render_context *ctx, } double offset = 0; - if (strncmp(block->align, "left", 4) == 0) { + if (has_prefix(block->align, "left")) { offset = x_pos; - } else if (strncmp(block->align, "right", 5) == 0) { + } else if (has_prefix(block->align, "right")) { offset = x_pos + width - text_width; - } else if (strncmp(block->align, "center", 6) == 0) { + } else if (has_prefix(block->align, "center")) { offset = x_pos + (width - text_width) / 2; } double text_y = height / 2.0 - text_height / 2.0; @@ -539,6 +540,63 @@ static uint32_t render_status_line(struct render_context *ctx, double *x) { return 0; } +static struct box_size render_box(struct render_context *ctx, double x, + struct box_colors colors, const char *label, bool pango_markup) { + struct swaybar_output *output = ctx->output; + struct swaybar_config *config = output->bar->config; + cairo_t *cairo = ctx->cairo; + + int text_width, text_height; + get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, + 1, pango_markup, "%s", label); + + uint32_t width = text_width + WS_HORIZONTAL_PADDING * 2 + BORDER_WIDTH * 2; + if (width < config->workspace_min_width) { + width = config->workspace_min_width; + } + + uint32_t ideal_height = text_height + WS_VERTICAL_PADDING * 2 + + BORDER_WIDTH * 2; + uint32_t ideal_surface_height = ideal_height; + if (!output->bar->config->height && + output->height < ideal_surface_height) { + return (struct box_size) { + .width = width, + .height = ideal_surface_height, + }; + } + + uint32_t height = output->height; + cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); + cairo_set_source_u32(cairo, colors.background); + ctx->background_color = colors.background; + ctx->has_transparency |= (colors.background & 0xFF) != 0xFF; + cairo_rectangle(cairo, x, 0, width, height); + cairo_fill(cairo); + + cairo_set_source_u32(cairo, colors.border); + cairo_rectangle(cairo, x, 0, width, BORDER_WIDTH); + cairo_fill(cairo); + cairo_rectangle(cairo, x, 0, BORDER_WIDTH, height); + cairo_fill(cairo); + cairo_rectangle(cairo, x + width - BORDER_WIDTH, 0, BORDER_WIDTH, height); + cairo_fill(cairo); + cairo_rectangle(cairo, x, height - BORDER_WIDTH, width, BORDER_WIDTH); + cairo_fill(cairo); + + double text_y = height / 2.0 - text_height / 2.0; + cairo_set_source_u32(cairo, colors.text); + cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); + choose_text_aa_mode(ctx, colors.text); + render_text(cairo, config->font_description, 1, pango_markup, + "%s", label); + + return (struct box_size) { + .width = width, + .height = output->height, + }; +} + static uint32_t render_binding_mode_indicator(struct render_context *ctx, double x) { struct swaybar_output *output = ctx->output; @@ -547,53 +605,9 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx, return 0; } - cairo_t *cairo = ctx->cairo; - struct swaybar_config *config = output->bar->config; - int text_width, text_height; - get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, - 1, output->bar->mode_pango_markup, - "%s", mode); - - int ws_vertical_padding = WS_VERTICAL_PADDING; - int ws_horizontal_padding = WS_HORIZONTAL_PADDING; - int border_width = BORDER_WIDTH; - - uint32_t ideal_height = text_height + ws_vertical_padding * 2 - + border_width * 2; - uint32_t ideal_surface_height = ideal_height; - if (!output->bar->config->height && - output->height < ideal_surface_height) { - return ideal_surface_height; - } - uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; - if (width < config->workspace_min_width) { - width = config->workspace_min_width; - } - - uint32_t height = output->height; - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, config->colors.binding_mode.background); - ctx->background_color = config->colors.binding_mode.background; - cairo_rectangle(cairo, x, 0, width, height); - cairo_fill(cairo); - - cairo_set_source_u32(cairo, config->colors.binding_mode.border); - cairo_rectangle(cairo, x, 0, width, border_width); - cairo_fill(cairo); - cairo_rectangle(cairo, x, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, x + width - border_width, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, x, height - border_width, width, border_width); - cairo_fill(cairo); - - double text_y = height / 2.0 - text_height / 2.0; - cairo_set_source_u32(cairo, config->colors.binding_mode.text); - cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); - choose_text_aa_mode(ctx, config->colors.binding_mode.text); - render_text(cairo, config->font_description, 1, output->bar->mode_pango_markup, - "%s", mode); - return output->height; + struct box_size size = render_box(ctx, x, output->bar->config->colors.binding_mode, + mode, output->bar->mode_pango_markup); + return size.height; } static enum hotspot_event_handling workspace_hotspot_callback( @@ -615,6 +629,7 @@ static uint32_t render_workspace_button(struct render_context *ctx, struct swaybar_workspace *ws, double *x) { struct swaybar_output *output = ctx->output; struct swaybar_config *config = output->bar->config; + struct box_colors box_colors; if (ws->urgent) { box_colors = config->colors.urgent_workspace; @@ -626,65 +641,21 @@ static uint32_t render_workspace_button(struct render_context *ctx, box_colors = config->colors.inactive_workspace; } - uint32_t height = output->height; - - cairo_t *cairo = ctx->cairo; - int text_width, text_height; - get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, - 1, config->pango_markup, "%s", ws->label); - - int ws_vertical_padding = WS_VERTICAL_PADDING; - int ws_horizontal_padding = WS_HORIZONTAL_PADDING; - int border_width = BORDER_WIDTH; - - uint32_t ideal_height = ws_vertical_padding * 2 + text_height - + border_width * 2; - uint32_t ideal_surface_height = ideal_height; - if (!output->bar->config->height && - output->height < ideal_surface_height) { - return ideal_surface_height; - } - - uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; - if (width < config->workspace_min_width) { - width = config->workspace_min_width; - } - - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_set_source_u32(cairo, box_colors.background); - ctx->background_color = box_colors.background; - cairo_rectangle(cairo, *x, 0, width, height); - cairo_fill(cairo); - - cairo_set_source_u32(cairo, box_colors.border); - cairo_rectangle(cairo, *x, 0, width, border_width); - cairo_fill(cairo); - cairo_rectangle(cairo, *x, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, *x + width - border_width, 0, border_width, height); - cairo_fill(cairo); - cairo_rectangle(cairo, *x, height - border_width, width, border_width); - cairo_fill(cairo); - - double text_y = height / 2.0 - text_height / 2.0; - cairo_set_source_u32(cairo, box_colors.text); - cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); - choose_text_aa_mode(ctx, box_colors.text); - render_text(cairo, config->font_description, 1, config->pango_markup, - "%s", ws->label); + struct box_size size = render_box(ctx, *x, box_colors, + ws->label, config->pango_markup); struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); hotspot->x = *x; hotspot->y = 0; - hotspot->width = width; - hotspot->height = height; + hotspot->width = size.width; + hotspot->height = size.height; hotspot->callback = workspace_hotspot_callback; hotspot->destroy = free; hotspot->data = strdup(ws->name); wl_list_insert(&output->hotspots, &hotspot->link); - *x += width; - return output->height; + *x += size.width; + return size.height; } static uint32_t render_to_cairo(struct render_context *ctx) { @@ -760,10 +731,12 @@ void render_frame(struct swaybar_output *output) { background_color = output->bar->config->colors.background; } - struct render_context ctx = { 0 }; - ctx.output = output; - // initial background color used for deciding the best way to antialias text - ctx.background_color = background_color; + struct render_context ctx = { + .output = output, + // initial background color used for deciding the best way to antialias text + .background_color = background_color, + .has_transparency = (background_color & 0xFF) != 0xFF, + }; cairo_surface_t *recorder = cairo_recording_surface_create( CAIRO_CONTENT_COLOR_ALPHA, NULL); @@ -834,8 +807,7 @@ void render_frame(struct swaybar_output *output) { wl_surface_damage(output->surface, 0, 0, output->width, output->height); - uint32_t bg_alpha = background_color & 0xFF; - if (bg_alpha == 0xFF) { + if (!ctx.has_transparency) { struct wl_region *region = wl_compositor_create_region(output->bar->compositor); wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c index ca6c03ad5..12929743b 100644 --- a/swaybar/tray/item.c +++ b/swaybar/tray/item.c @@ -15,6 +15,7 @@ #include "cairo_util.h" #include "list.h" #include "log.h" +#include "stringop.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" // TODO menu @@ -161,7 +162,7 @@ static int get_property_callback(sd_bus_message *msg, void *data, } if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ? - prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) { + prop[0] == 'A' : has_prefix(prop, "Icon")))) { set_sni_dirty(sni); } cleanup: @@ -364,7 +365,7 @@ static void handle_click(struct swaybar_sni *sni, int x, int y, method = "ContextMenu"; } - if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { + if (has_prefix(method, "Scroll")) { char dir = method[strlen("Scroll")]; char *orientation = (dir == 'U' || dir == 'D') ? "vertical" : "horizontal"; int sign = (dir == 'U' || dir == 'L') ? -1 : 1; diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c index b0545f4a7..a4f382bfb 100644 --- a/swaybar/tray/tray.c +++ b/swaybar/tray/tray.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -90,9 +91,16 @@ void destroy_tray(struct swaybar_tray *tray) { } void tray_in(int fd, short mask, void *data) { - sd_bus *bus = data; + struct swaybar *bar = data; int ret; - while ((ret = sd_bus_process(bus, NULL)) > 0) { + + if (mask & (POLLHUP | POLLERR)) { + sway_log(SWAY_ERROR, "D-Bus connection closed unexpectedly"); + bar->running = false; + return; + } + + while ((ret = sd_bus_process(bar->tray->bus, NULL)) > 0) { // This space intentionally left blank } if (ret < 0) { diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c index 3cfea8d8e..284964030 100644 --- a/swaybar/tray/watcher.c +++ b/swaybar/tray/watcher.c @@ -31,13 +31,15 @@ static int handle_lost_service(sd_bus_message *msg, struct swaybar_watcher *watcher = data; for (int idx = 0; idx < watcher->items->length; ++idx) { char *id = watcher->items->items[idx]; - int cmp_res = using_standard_protocol(watcher) ? - cmp_id(id, service) : strncmp(id, service, strlen(service)); - if (cmp_res == 0) { + bool cmp_res = using_standard_protocol(watcher) ? + cmp_id(id, service) == 0 : has_prefix(id, service); + if (cmp_res) { sway_log(SWAY_DEBUG, "Unregistering Status Notifier Item '%s'", id); list_del(watcher->items, idx--); sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, "StatusNotifierItemUnregistered", "s", id); + sd_bus_emit_properties_changed(watcher->bus, obj_path, watcher->interface, + "RegisteredStatusNotifierItems", NULL); free(id); if (using_standard_protocol(watcher)) { break; @@ -50,6 +52,10 @@ static int handle_lost_service(sd_bus_message *msg, sway_log(SWAY_DEBUG, "Unregistering Status Notifier Host '%s'", service); free(watcher->hosts->items[idx]); list_del(watcher->hosts, idx); + if (watcher->hosts->length == 0) { + sd_bus_emit_properties_changed(watcher->bus, obj_path, watcher->interface, + "IsStatusNotifierHostRegistered", NULL); + } } } @@ -82,6 +88,8 @@ static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) { if (list_seq_find(watcher->items, cmp_id, id) == -1) { sway_log(SWAY_DEBUG, "Registering Status Notifier Item '%s'", id); list_add(watcher->items, id); + sd_bus_emit_properties_changed(watcher->bus, obj_path, watcher->interface, + "RegisteredStatusNotifierItems", NULL); sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, "StatusNotifierItemRegistered", "s", id); } else { @@ -104,6 +112,10 @@ static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) { if (list_seq_find(watcher->hosts, cmp_id, service) == -1) { sway_log(SWAY_DEBUG, "Registering Status Notifier Host '%s'", service); list_add(watcher->hosts, strdup(service)); + if (watcher->hosts->length == 1) { + sd_bus_emit_properties_changed(watcher->bus, obj_path, watcher->interface, + "IsStatusNotifierHostRegistered", NULL); + } sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, "StatusNotifierHostRegistered", ""); } else { diff --git a/swaymsg/main.c b/swaymsg/main.c index 573a7b166..d58f29e2c 100644 --- a/swaymsg/main.c +++ b/swaymsg/main.c @@ -16,10 +16,6 @@ #include "ipc-client.h" #include "log.h" -void sway_terminate(int exit_code) { - exit(exit_code); -} - static bool success_object(json_object *result) { json_object *success; @@ -99,7 +95,7 @@ static const char *pretty_type_name(const char *name) { const char *b; } type_names[] = { { "keyboard", "Keyboard" }, - { "pointer", "Mouse" }, + { "pointer", "Pointer" }, { "touchpad", "Touchpad" }, { "tablet_pad", "Tablet pad" }, { "tablet_tool", "Tablet tool" }, @@ -193,7 +189,8 @@ static void pretty_print_output(json_object *o) { json_object_object_get_ex(o, "current_workspace", &ws); json_object_object_get_ex(o, "non_desktop", &non_desktop); json_object *make, *model, *serial, *scale, *scale_filter, *subpixel, - *transform, *max_render_time, *adaptive_sync_status; + *transform, *max_render_time, *adaptive_sync_status, *allow_tearing, + *hdr; json_object_object_get_ex(o, "make", &make); json_object_object_get_ex(o, "model", &model); json_object_object_get_ex(o, "serial", &serial); @@ -203,6 +200,8 @@ static void pretty_print_output(json_object *o) { json_object_object_get_ex(o, "transform", &transform); json_object_object_get_ex(o, "max_render_time", &max_render_time); json_object_object_get_ex(o, "adaptive_sync_status", &adaptive_sync_status); + json_object_object_get_ex(o, "allow_tearing", &allow_tearing); + json_object_object_get_ex(o, "hdr", &hdr); json_object *x, *y; json_object_object_get_ex(rect, "x", &x); json_object_object_get_ex(rect, "y", &y); @@ -213,6 +212,10 @@ static void pretty_print_output(json_object *o) { json_object_object_get_ex(current_mode, "width", &width); json_object_object_get_ex(current_mode, "height", &height); json_object_object_get_ex(current_mode, "refresh", &refresh); + json_object *features, *features_adaptive_sync, *features_hdr; + json_object_object_get_ex(o, "features", &features); + json_object_object_get_ex(features, "adaptive_sync", &features_adaptive_sync); + json_object_object_get_ex(features, "hdr", &features_hdr); if (json_object_get_boolean(non_desktop)) { printf( @@ -255,7 +258,18 @@ static void pretty_print_output(json_object *o) { printf(max_render_time_int == 0 ? "off\n" : "%d ms\n", max_render_time_int); printf(" Adaptive sync: %s\n", - json_object_get_string(adaptive_sync_status)); + json_object_get_boolean(features_adaptive_sync) ? + json_object_get_string(adaptive_sync_status) : + "unsupported"); + + printf(" Allow tearing: %s\n", + json_object_get_boolean(allow_tearing) ? "yes" : "no"); + + const char *hdr_str = "unsupported"; + if (json_object_get_boolean(features_hdr)) { + hdr_str = json_object_get_boolean(hdr) ? "on" : "off"; + } + printf(" HDR: %s\n", hdr_str); } else { printf( "Output %s '%s %s %s' (disabled)\n", @@ -326,6 +340,10 @@ static void pretty_print_tree(json_object *obj, int indent) { const char *instance = json_object_get_string(json_object_object_get(window_props_obj, "instance")); const char *class = json_object_get_string(json_object_object_get(window_props_obj, "class")); int x11_id = json_object_get_int(json_object_object_get(obj, "window")); + const char *foreign_toplevel_id = json_object_get_string(json_object_object_get(obj, "foreign_toplevel_identifier")); + const char *sandbox_engine = json_object_get_string(json_object_object_get(obj, "sandbox_engine")); + const char *sandbox_app_id = json_object_get_string(json_object_object_get(obj, "sandbox_app_id")); + const char *sandbox_instance_id = json_object_get_string(json_object_object_get(obj, "sandbox_instance_id")); printf(" (%s, pid: %d", shell, pid); if (app_id != NULL) { @@ -340,6 +358,18 @@ static void pretty_print_tree(json_object *obj, int indent) { if (x11_id != 0) { printf(", X11 window: 0x%X", x11_id); } + if (foreign_toplevel_id != NULL) { + printf(", foreign_toplevel_id: \"%s\"", foreign_toplevel_id); + } + if (sandbox_engine != NULL) { + printf(", sandbox_engine: \"%s\"", sandbox_engine); + } + if (sandbox_app_id != NULL) { + printf(", sandbox_app_id: \"%s\"", sandbox_app_id); + } + if (sandbox_instance_id != NULL) { + printf(", sandbox_instance_id: \"%s\"", sandbox_instance_id); + } printf(")"); } diff --git a/swaynag/main.c b/swaynag/main.c index 634bddbfc..53c2203ff 100644 --- a/swaynag/main.c +++ b/swaynag/main.c @@ -13,11 +13,6 @@ void sig_handler(int signal) { exit(EXIT_FAILURE); } -void sway_terminate(int code) { - swaynag_destroy(&swaynag); - exit(code); -} - int main(int argc, char **argv) { int status = EXIT_SUCCESS; @@ -50,6 +45,7 @@ int main(int argc, char **argv) { if (config_path) { sway_log(SWAY_DEBUG, "Loading config file: %s", config_path); status = swaynag_load_config(config_path, &swaynag, types); + free(config_path); if (status != 0) { goto cleanup; } @@ -107,7 +103,8 @@ int main(int argc, char **argv) { sway_log(SWAY_DEBUG, "\t[%s] `%s`", button->text, button->action); } - signal(SIGTERM, sig_handler); + struct sigaction sa = { .sa_handler = sig_handler }; + sigaction(SIGTERM, &sa, NULL); swaynag_setup(&swaynag); swaynag_run(&swaynag); diff --git a/swaynag/swaynag.c b/swaynag/swaynag.c index 50eea1483..da32eeb76 100644 --- a/swaynag/swaynag.c +++ b/swaynag/swaynag.c @@ -324,7 +324,9 @@ static void output_scale(void *data, struct wl_output *output, swaynag_output->scale = factor; if (swaynag_output->swaynag->output == swaynag_output) { swaynag_output->swaynag->scale = swaynag_output->scale; - update_all_cursors(swaynag_output->swaynag); + if (!swaynag_output->swaynag->cursor_shape_manager) { + update_all_cursors(swaynag_output->swaynag); + } render_frame(swaynag_output->swaynag); } }