aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.builds/alpine.yml18
-rw-r--r--.builds/archlinux.yml9
-rw-r--r--.builds/freebsd.yml9
-rw-r--r--.clang-format16
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md6
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml4
-rw-r--r--.gitignore1
-rw-r--r--.mailmap1
-rw-r--r--CONTRIBUTING.md5
-rw-r--r--README.ar.md91
-rw-r--r--README.cs.md89
-rw-r--r--README.de.md24
-rw-r--r--README.dk.md69
-rw-r--r--README.es.md16
-rw-r--r--README.fr.md72
-rw-r--r--README.ge.md61
-rw-r--r--README.gr.md67
-rw-r--r--README.hi.md63
-rw-r--r--README.hu.md70
-rw-r--r--README.ir.md54
-rw-r--r--README.it.md66
-rw-r--r--README.ja.md22
-rw-r--r--README.ko.md14
-rw-r--r--README.md70
-rw-r--r--README.nl.md16
-rw-r--r--README.no.md68
-rw-r--r--README.pl.md16
-rw-r--r--README.pt.md16
-rw-r--r--README.ro.md16
-rw-r--r--README.ru.md49
-rw-r--r--README.sv.md83
-rw-r--r--README.tr.md62
-rw-r--r--README.uk.md16
-rw-r--r--README.zh-CN.md53
-rw-r--r--README.zh-TW.md16
-rw-r--r--client/pool-buffer.c68
-rw-r--r--common/cairo.c4
-rw-r--r--common/gesture.c333
-rw-r--r--common/meson.build3
-rw-r--r--common/pango.c50
-rw-r--r--common/stringop.c33
-rw-r--r--common/util.c12
-rw-r--r--completions/bash/sway2
-rw-r--r--completions/bash/swaybar2
-rw-r--r--completions/bash/swaymsg2
-rw-r--r--completions/meson.build57
-rw-r--r--config.in10
-rwxr-xr-xcontrib/_incr_version21
-rwxr-xr-xcontrib/autoname-workspaces.py130
-rwxr-xr-xcontrib/grimshot154
-rw-r--r--contrib/grimshot.1103
-rw-r--r--contrib/grimshot.1.scd76
-rwxr-xr-xcontrib/inactive-windows-transparency.py64
-rw-r--r--include/background-image.h20
-rw-r--r--include/cairo_util.h (renamed from include/cairo.h)6
-rw-r--r--include/gesture.h104
-rw-r--r--include/ipc-client.h3
-rw-r--r--include/pango.h14
-rw-r--r--include/pool-buffer.h2
-rw-r--r--include/stringop.h10
-rw-r--r--include/sway/commands.h24
-rw-r--r--include/sway/config.h102
-rw-r--r--include/sway/criteria.h8
-rw-r--r--include/sway/desktop.h13
-rw-r--r--include/sway/desktop/idle_inhibit_v1.h13
-rw-r--r--include/sway/desktop/launcher.h36
-rw-r--r--include/sway/desktop/transaction.h23
-rw-r--r--include/sway/input/cursor.h17
-rw-r--r--include/sway/input/input-manager.h5
-rw-r--r--include/sway/input/keyboard.h1
-rw-r--r--include/sway/input/libinput.h7
-rw-r--r--include/sway/input/seat.h114
-rw-r--r--include/sway/input/switch.h1
-rw-r--r--include/sway/input/tablet.h3
-rw-r--r--include/sway/input/text_input.h8
-rw-r--r--include/sway/ipc-json.h2
-rw-r--r--include/sway/ipc-server.h1
-rw-r--r--include/sway/layers.h50
-rw-r--r--include/sway/output.h112
-rw-r--r--include/sway/scene_descriptor.h33
-rw-r--r--include/sway/server.h103
-rw-r--r--include/sway/surface.h18
-rw-r--r--include/sway/sway_text_node.h28
-rw-r--r--include/sway/swaynag.h3
-rw-r--r--include/sway/tree/container.h150
-rw-r--r--include/sway/tree/node.h13
-rw-r--r--include/sway/tree/root.h52
-rw-r--r--include/sway/tree/view.h129
-rw-r--r--include/sway/tree/workspace.h20
-rw-r--r--include/sway/xdg_decoration.h2
-rw-r--r--include/swaybar/bar.h3
-rw-r--r--include/swaybar/config.h3
-rw-r--r--include/swaybar/i3bar.h3
-rw-r--r--include/swaybar/image.h7
-rw-r--r--include/swaybar/input.h2
-rw-r--r--include/swaybar/tray/item.h1
-rw-r--r--include/swaynag/swaynag.h7
-rw-r--r--include/swaynag/types.h7
-rw-r--r--include/util.h6
-rw-r--r--meson.build189
-rw-r--r--meson_options.txt4
-rw-r--r--protocols/meson.build77
-rw-r--r--protocols/wlr-input-inhibitor-unstable-v1.xml67
-rw-r--r--sway/commands.c109
-rw-r--r--sway/commands/assign.c2
-rw-r--r--sway/commands/bar.c9
-rw-r--r--sway/commands/bar/bind.c2
-rw-r--r--sway/commands/bar/colors.c2
-rw-r--r--sway/commands/bar/font.c15
-rw-r--r--sway/commands/bar/hidden_state.c2
-rw-r--r--sway/commands/bar/mode.c2
-rw-r--r--sway/commands/bar/tray_bind.c2
-rw-r--r--sway/commands/bind.c27
-rw-r--r--sway/commands/border.c8
-rw-r--r--sway/commands/client.c28
-rw-r--r--sway/commands/exec_always.c50
-rw-r--r--sway/commands/floating.c8
-rw-r--r--sway/commands/floating_minmax_size.c6
-rw-r--r--sway/commands/focus.c59
-rw-r--r--sway/commands/font.c29
-rw-r--r--sway/commands/for_window.c2
-rw-r--r--sway/commands/fullscreen.c34
-rw-r--r--sway/commands/gaps.c14
-rw-r--r--sway/commands/gesture.c166
-rw-r--r--sway/commands/hide_edge_borders.c4
-rw-r--r--sway/commands/inhibit_idle.c2
-rw-r--r--sway/commands/input.c7
-rw-r--r--sway/commands/input/dwtp.c25
-rw-r--r--sway/commands/input/events.c10
-rw-r--r--sway/commands/input/map_from_region.c18
-rw-r--r--sway/commands/input/map_to_region.c3
-rw-r--r--sway/commands/input/rotation_angle.c29
-rw-r--r--sway/commands/input/scroll_button.c2
-rw-r--r--sway/commands/input/scroll_button_lock.c26
-rw-r--r--sway/commands/input/xkb_switch_layout.c36
-rw-r--r--sway/commands/layout.c14
-rw-r--r--sway/commands/mark.c2
-rw-r--r--sway/commands/mode.c5
-rw-r--r--sway/commands/move.c197
-rw-r--r--sway/commands/no_focus.c2
-rw-r--r--sway/commands/opacity.c3
-rw-r--r--sway/commands/output.c12
-rw-r--r--sway/commands/output/background.c15
-rw-r--r--sway/commands/output/dpms.c22
-rw-r--r--sway/commands/output/mode.c58
-rw-r--r--sway/commands/output/power.c43
-rw-r--r--sway/commands/output/render_bit_depth.c29
-rw-r--r--sway/commands/output/transform.c1
-rw-r--r--sway/commands/output/unplug.c54
-rw-r--r--sway/commands/primary_selection.c23
-rw-r--r--sway/commands/reload.c8
-rw-r--r--sway/commands/rename.c7
-rw-r--r--sway/commands/resize.c158
-rw-r--r--sway/commands/scratchpad.c21
-rw-r--r--sway/commands/seat.c4
-rw-r--r--sway/commands/seat/attach.c2
-rw-r--r--sway/commands/seat/cursor.c14
-rw-r--r--sway/commands/seat/idle.c6
-rw-r--r--sway/commands/show_marks.c11
-rw-r--r--sway/commands/smart_gaps.c7
-rw-r--r--sway/commands/split.c23
-rw-r--r--sway/commands/sticky.c6
-rw-r--r--sway/commands/swap.c182
-rw-r--r--sway/commands/title_align.c9
-rw-r--r--sway/commands/title_format.c1
-rw-r--r--sway/commands/titlebar_border_thickness.c1
-rw-r--r--sway/commands/titlebar_padding.c1
-rw-r--r--sway/commands/unmark.c13
-rw-r--r--sway/commands/workspace.c35
-rw-r--r--sway/config.c135
-rw-r--r--sway/config/bar.c6
-rw-r--r--sway/config/input.c12
-rw-r--r--sway/config/output.c299
-rw-r--r--sway/config/seat.c1
-rw-r--r--sway/criteria.c102
-rw-r--r--sway/desktop/desktop.c39
-rw-r--r--sway/desktop/idle_inhibit_v1.c62
-rw-r--r--sway/desktop/launcher.c255
-rw-r--r--sway/desktop/layer_shell.c759
-rw-r--r--sway/desktop/output.c958
-rw-r--r--sway/desktop/render.c1125
-rw-r--r--sway/desktop/surface.c46
-rw-r--r--sway/desktop/transaction.c734
-rw-r--r--sway/desktop/xdg_shell.c384
-rw-r--r--sway/desktop/xwayland.c339
-rw-r--r--sway/input/cursor.c632
-rw-r--r--sway/input/input-manager.c112
-rw-r--r--sway/input/keyboard.c250
-rw-r--r--sway/input/libinput.c124
-rw-r--r--sway/input/seat.c508
-rw-r--r--sway/input/seatop_default.c541
-rw-r--r--sway/input/seatop_down.c174
-rw-r--r--sway/input/seatop_move_floating.c13
-rw-r--r--sway/input/seatop_move_tiling.c230
-rw-r--r--sway/input/seatop_resize_floating.c54
-rw-r--r--sway/input/seatop_resize_tiling.c28
-rw-r--r--sway/input/switch.c38
-rw-r--r--sway/input/tablet.c49
-rw-r--r--sway/input/text_input.c51
-rw-r--r--sway/ipc-json.c361
-rw-r--r--sway/ipc-server.c35
-rw-r--r--sway/lock.c354
-rw-r--r--sway/main.c225
-rw-r--r--sway/meson.build34
-rw-r--r--sway/realtime.c40
-rw-r--r--sway/scene_descriptor.c66
-rw-r--r--sway/server.c282
-rw-r--r--sway/sway-bar.5.scd2
-rw-r--r--sway/sway-input.5.scd47
-rw-r--r--sway/sway-ipc.7.scd47
-rw-r--r--sway/sway-output.5.scd43
-rw-r--r--sway/sway.5.scd153
-rw-r--r--sway/sway_text_node.c303
-rw-r--r--sway/swaynag.c20
-rw-r--r--sway/tree/arrange.c105
-rw-r--r--sway/tree/container.c1685
-rw-r--r--sway/tree/node.c55
-rw-r--r--sway/tree/output.c130
-rw-r--r--sway/tree/root.c287
-rw-r--r--sway/tree/view.c764
-rw-r--r--sway/tree/workspace.c174
-rw-r--r--sway/xdg_activation_v1.c50
-rw-r--r--sway/xdg_decoration.c41
-rw-r--r--swaybar/bar.c55
-rw-r--r--swaybar/config.c4
-rw-r--r--swaybar/i3bar.c38
-rw-r--r--swaybar/image.c (renamed from common/background-image.c)92
-rw-r--r--swaybar/input.c64
-rw-r--r--swaybar/ipc.c38
-rw-r--r--swaybar/main.c2
-rw-r--r--swaybar/meson.build5
-rw-r--r--swaybar/render.c415
-rw-r--r--swaybar/status_line.c13
-rw-r--r--swaybar/tray/host.c10
-rw-r--r--swaybar/tray/icon.c26
-rw-r--r--swaybar/tray/item.c53
-rw-r--r--swaybar/tray/tray.c4
-rw-r--r--swaybar/tray/watcher.c11
-rw-r--r--swaymsg/main.c156
-rw-r--r--swaymsg/swaymsg.1.scd22
-rw-r--r--swaynag/config.c103
-rw-r--r--swaynag/main.c47
-rw-r--r--swaynag/meson.build3
-rw-r--r--swaynag/render.c72
-rw-r--r--swaynag/swaynag.1.scd3
-rw-r--r--swaynag/swaynag.5.scd5
-rw-r--r--swaynag/swaynag.c153
-rw-r--r--swaynag/types.c15
248 files changed, 11680 insertions, 8897 deletions
diff --git a/.builds/alpine.yml b/.builds/alpine.yml
index dc5e7c11..59df7737 100644
--- a/.builds/alpine.yml
+++ b/.builds/alpine.yml
@@ -4,22 +4,27 @@ packages:
4 - eudev-dev 4 - eudev-dev
5 - gdk-pixbuf-dev 5 - gdk-pixbuf-dev
6 - json-c-dev 6 - json-c-dev
7 - libdisplay-info-dev
7 - libevdev-dev 8 - libevdev-dev
8 - libinput-dev 9 - libinput-dev
10 - libseat-dev
9 - libxcb-dev 11 - libxcb-dev
10 - libxkbcommon-dev 12 - libxkbcommon-dev
11 - mesa-dev 13 - mesa-dev
12 - meson 14 - meson
13 - pango-dev 15 - pango-dev
16 - pcre2-dev
14 - pixman-dev 17 - pixman-dev
15 - scdoc 18 - scdoc
16 - wayland-dev 19 - wayland-dev
17 - wayland-protocols 20 - wayland-protocols
18 - xcb-util-image-dev 21 - xcb-util-image-dev
19 - xorg-server-xwayland 22 - xcb-util-wm-dev
23 - xwayland-dev
24 - hwdata-dev
20sources: 25sources:
21 - https://github.com/swaywm/sway 26 - https://github.com/swaywm/sway
22 - https://github.com/swaywm/wlroots 27 - https://gitlab.freedesktop.org/wlroots/wlroots.git
23tasks: 28tasks:
24 - wlroots: | 29 - wlroots: |
25 cd wlroots 30 cd wlroots
@@ -28,7 +33,7 @@ tasks:
28 sudo ninja -C build install 33 sudo ninja -C build install
29 - setup: | 34 - setup: |
30 cd sway 35 cd sway
31 meson build -Dauto_features=enabled -Dtray=disabled 36 meson build --fatal-meson-warnings -Dauto_features=enabled -Dtray=disabled
32 - build: | 37 - build: |
33 cd sway 38 cd sway
34 ninja -C build 39 ninja -C build
@@ -36,3 +41,10 @@ tasks:
36 cd sway 41 cd sway
37 meson configure build -Dxwayland=disabled 42 meson configure build -Dxwayland=disabled
38 ninja -C build 43 ninja -C build
44 - build-static: |
45 cd sway
46 mkdir subprojects
47 ln -s ../../wlroots subprojects/wlroots
48 rm -rf build
49 meson build --fatal-meson-warnings --default-library=static --force-fallback-for=wlroots
50 ninja -C build
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index c0f70186..9972c01a 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -3,20 +3,25 @@ packages:
3 - cairo 3 - cairo
4 - gdk-pixbuf2 4 - gdk-pixbuf2
5 - json-c 5 - json-c
6 - libdisplay-info
6 - libegl 7 - libegl
7 - libinput 8 - libinput
8 - libxcb 9 - libxcb
9 - libxkbcommon 10 - libxkbcommon
10 - meson 11 - meson
11 - pango 12 - pango
13 - pcre2
12 - scdoc 14 - scdoc
13 - wayland 15 - wayland
14 - wayland-protocols 16 - wayland-protocols
15 - xcb-util-image 17 - xcb-util-image
18 - xcb-util-wm
16 - xorg-xwayland 19 - xorg-xwayland
20 - seatd
21 - hwdata
17sources: 22sources:
18 - https://github.com/swaywm/sway 23 - https://github.com/swaywm/sway
19 - https://github.com/swaywm/wlroots 24 - https://gitlab.freedesktop.org/wlroots/wlroots.git
20tasks: 25tasks:
21 - wlroots: | 26 - wlroots: |
22 cd wlroots 27 cd wlroots
@@ -25,7 +30,7 @@ tasks:
25 sudo ninja -C build install 30 sudo ninja -C build install
26 - setup: | 31 - setup: |
27 cd sway 32 cd sway
28 meson build -Dauto_features=enabled -Dsd-bus-provider=libsystemd 33 meson build --fatal-meson-warnings -Dauto_features=enabled -Dsd-bus-provider=libsystemd
29 - build: | 34 - build: |
30 cd sway 35 cd sway
31 ninja -C build 36 ninja -C build
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 273badbc..29c6312a 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -4,6 +4,7 @@ packages:
4- devel/json-c 4- devel/json-c
5- devel/libevdev 5- devel/libevdev
6- devel/meson 6- devel/meson
7- devel/pcre2
7- devel/pkgconf 8- devel/pkgconf
8- graphics/cairo 9- graphics/cairo
9- graphics/gdk-pixbuf2 10- graphics/gdk-pixbuf2
@@ -19,13 +20,17 @@ packages:
19- devel/libudev-devd 20- devel/libudev-devd
20- graphics/libdrm 21- graphics/libdrm
21- graphics/mesa-libs 22- graphics/mesa-libs
23- sysutils/libdisplay-info
24- sysutils/seatd
22- x11/libinput 25- x11/libinput
23- x11/libX11 26- x11/libX11
24- x11/pixman 27- x11/pixman
25- x11/xcb-util-wm 28- x11/xcb-util-wm
29- x11-servers/xwayland-devel
30- misc/hwdata
26sources: 31sources:
27- https://github.com/swaywm/sway 32- https://github.com/swaywm/sway
28- https://github.com/swaywm/wlroots 33- https://gitlab.freedesktop.org/wlroots/wlroots.git
29tasks: 34tasks:
30- setup: | 35- setup: |
31 cd sway 36 cd sway
@@ -33,7 +38,7 @@ tasks:
33 cd subprojects 38 cd subprojects
34 ln -s ../../wlroots wlroots 39 ln -s ../../wlroots wlroots
35 cd .. 40 cd ..
36 meson build -Dtray=enabled -Dsd-bus-provider=basu 41 meson build --fatal-meson-warnings -Dtray=enabled -Dsd-bus-provider=basu
37- build: | 42- build: |
38 cd sway 43 cd sway
39 ninja -C build 44 ninja -C build
diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index 5818da3c..00000000
--- a/.clang-format
+++ /dev/null
@@ -1,16 +0,0 @@
1BasedOnStyle: LLVM
2IndentWidth: 4
3TabWidth: 4
4UseTab: Always
5BreakBeforeBraces: Attach
6AllowShortIfStatementsOnASingleLine: false
7IndentCaseLabels: false
8SortIncludes: false
9ColumnLimit: 80
10AlignAfterOpenBracket: DontAlign
11BinPackParameters: false
12BinPackArguments: false
13ContinuationIndentWidth: 8
14AllowAllParametersOfDeclarationOnNextLine: false
15AllowShortLoopsOnASingleLine: true
16ReflowComments: false
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 156accde..eba606e6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -6,10 +6,9 @@ labels: 'bug'
6--- 6---
7 7
8### Please read the following before submitting: 8### Please read the following before submitting:
9- Please do NOT submit bug reports for questions. Ask questions on IRC at #sway on irc.freenode.net. 9- Please do NOT submit bug reports for questions. Ask questions on IRC at #sway on Libera Chat.
10- Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use Sway. 10- Proprietary graphics drivers, including nvidia, are not supported. Please use the open source equivalents, such as nouveau, if you would like to use Sway.
11- Problems with the Wayland version of Firefox are likely to be Firefox bugs. Start by submitting your issue to the Firefox Bugzilla and come back here only after they confirm otherwise. 11- Please do NOT submit issues for information from the github wiki. The github wiki is community maintained and therefore may contain outdated information, scripts that don't work or obsolete workarounds.
12- Please do NOT submit issues for information from the github wiki. The github wiki is community maintained and therefore may contain outdated information, scripts that don't work or osbolete workarounds.
13 If you fix a script or find outdated information, don't hesitate to adjust the wiki page. 12 If you fix a script or find outdated information, don't hesitate to adjust the wiki page.
14 13
15### Please fill out the following: 14### Please fill out the following:
@@ -19,6 +18,7 @@ labels: 'bug'
19- **Debug Log:** 18- **Debug Log:**
20 - Run `sway -d 2> ~/sway.log` from a TTY and upload it to a pastebin, such as gist.github.com. 19 - Run `sway -d 2> ~/sway.log` from a TTY and upload it to a pastebin, such as gist.github.com.
21 - This will record information about sway's activity. Please try to keep the reproduction as brief as possible and exit sway. 20 - This will record information about sway's activity. Please try to keep the reproduction as brief as possible and exit sway.
21 - Attach the **full** file, do not truncate it.
22 22
23- **Configuration File:** 23- **Configuration File:**
24 - Please try to produce with the default configuration. 24 - Please try to produce with the default configuration.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index f09cdf5b..0092609b 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
1blank_issues_enabled: false 1blank_issues_enabled: false
2contact_links: 2contact_links:
3 - name: Questions 3 - name: Questions
4 url: "http://webchat.freenode.net/?channels=sway&uio=d4" 4 url: "https://libera.chat"
5 about: "Please ask questions on IRC in #sway on irc.freenode.net" 5 about: "Please ask questions on IRC in #sway on Libera Chat"
diff --git a/.gitignore b/.gitignore
index 1ec0cb52..167960e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ bin/
6test/ 6test/
7build/ 7build/
8build-*/ 8build-*/
9.cache/
9.lvimrc 10.lvimrc
10config-debug 11config-debug
11wayland-*-protocol.* 12wayland-*-protocol.*
diff --git a/.mailmap b/.mailmap
new file mode 100644
index 00000000..35ee25b0
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1 @@
Ronan Pigott <ronan@rjp.ie> <rpigott@berkeley.edu>
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c17afdda..4f043f7a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,8 @@
1# Contributing to sway 1# Contributing to sway
2 2
3Contributing just involves sending a pull request. You will probably be more 3Contributing just involves sending a pull request. You will probably be more
4successful with your contribution if you visit 4successful with your contribution if you visit #sway-devel on Libera Chat
5[#sway-devel](https://webchat.freenode.net/?channels=sway-devel) on 5upfront and discuss your plans.
6irc.freenode.net upfront and discuss your plans.
7 6
8Note: rules are made to be broken. Adjust or ignore any/all of these as you see 7Note: rules are made to be broken. Adjust or ignore any/all of these as you see
9fit, but be prepared to justify it to your peers. 8fit, but be prepared to justify it to your peers.
diff --git a/README.ar.md b/README.ar.md
new file mode 100644
index 00000000..4f9bf828
--- /dev/null
+++ b/README.ar.md
@@ -0,0 +1,91 @@
1# sway
2
3sway
4
5هو مدير للمجموعات المركبة لـ[Wayland] متواÙÙ‚ مع [i3] -
6
7إقرأ [الأسئلة الشائعة](https://github.com/swaywm/sway/wiki)
8
9 انضم الى [قناة IRC](https://web.libera.chat/gamja/?channels=#sway)
10
11
12## تواقيع الإصدار
13 تٌوقع الإصدارات بـواسطة [E88F5E48] Ùˆ تÙنشر على [GitHub](https://github.com/swaywm/sway/releases)
14
15## التثبيت
16
17### بإستخدام الحزم
18
19يتوÙر Sway للعديد من التوزيعات، حاول تثبيت حزمة "sway" لتوزيعتك
20### التجميع من المصدر
21إطلع على [صÙحة الويكي هذه](https://github.com/swaywm/sway/wiki/Development-Setup) إذا أردت بناء الـHEAD من sway Ùˆ wlroots لأغراض الÙحص والتطوير
22
23تثبيت اللوازم:
24* meson \*
25* [wlroots]
26* wayland
27* wayland-protocols \*
28* pcre2
29* json-c
30* pango
31* cairo
32* gdk-pixbuf2 (optional: system tray)
33* [scdoc] (optional: man pages) \*
34* git (optional: version info) \*
35
36_\* Compile-time dep_
37
38Ù†ÙØ° هذه الأوامر:
39
40 meson build/
41 ninja -C build/
42 sudo ninja -C build/ install
43
44## الإعدادات
45
46إذا كنت بالÙعل تستخدم i3ØŒ Ùعليك نسخ إعدادات i3 لديك إلى `~/.config/sway/config` وسو٠تعمل تلقائياً.
47
48Ùˆ إلا عليك نسخ مل٠الإعدادات النموذج إلى `config/sway/config` الموجود عادةً ÙÙŠ `/etc/sway/config.`
49
50
51## التشغيل
52
53شغل `sway` بإستخدام أمر TTY.
54قد يعمل بعض مدراء العرض مع أنهم غير مدعومون من sway
55(gdm مثلاً يعمل بشكل جيد إلى حد ما)
56
57[en]: https://github.com/swaywm/sway#readme
58[ar]: README.ar.md
59[cs]: README.cs.md
60[de]: README.de.md
61[dk]: README.dk.md
62[es]: README.es.md
63[fr]: README.fr.md
64[ge]: README.ge.md
65[gr]: README.gr.md
66[hi]: README.hi.md
67[hu]: README.hu.md
68[ir]: README.ir.md
69[it]: README.it.md
70[ja]: README.ja.md
71[ko]: README.ko.md
72[nl]: README.nl.md
73[no]: README.no.md
74[pl]: README.pl.md
75[pt]: README.pt.md
76[ro]: README.ro.md
77[ru]: README.ru.md
78[sv]: README.sv.md
79[tr]: README.tr.md
80[uk]: README.uk.md
81[zh-CN]: README.zh-CN.md
82[zh-TW]: README.zh-TW.md
83[i3]: https://i3wm.org/
84[Wayland]: http://wayland.freedesktop.org/
85[FAQ]: https://github.com/swaywm/sway/wiki
86[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
87[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
88[GitHub releases]: https://github.com/swaywm/sway/releases
89[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
90[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
91[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.cs.md b/README.cs.md
new file mode 100644
index 00000000..41efba54
--- /dev/null
+++ b/README.cs.md
@@ -0,0 +1,89 @@
1# sway
2
3[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]
4
5sway je s [i3] kompatibilní [Wayland] kompozitor. PÅ™eÄtÄ›te si [FAQ]. PÅ™ipojte se na
6[IRC kanál][IRC channel] \(#sway na irc.libera.chat).
7
8## Podpisy vydání
9
10Vydání jsou podepsána [E88F5E48] a publikována [na GitHubu][GitHub releases].
11
12## Instalace
13
14### Z balíÄků
15
16Sway je dostupný ve spoustÄ› distribucí. Zkuste nainstalovat balíÄek "sway" ve vaší
17distribuci.
18
19### Kompilace ze zdrojových kódů
20
21Podívejte se na [tuto stránku wiki][Development setup], pokud chcete sestavit HEAD
22sway a wlroots pro testování nebo vývoj.
23
24Nainstalujte závislosti:
25
26* meson \*
27* [wlroots]
28* wayland
29* wayland-protocols \*
30* pcre2
31* json-c
32* pango
33* cairo
34* gdk-pixbuf2 (volitelné: oznamovací oblast)
35* [scdoc] (volitelné: manuálové stránky) \*
36* git (volitelné: informace o verzi) \*
37
38_\* Závislost pouze pro sestavení_
39
40Spusťte tyto příkazy:
41
42 meson build/
43 ninja -C build/
44 sudo ninja -C build/ install
45
46## Konfigurace
47
48Pokud již používáte i3, zkopírujte svou konfiguraci i3 do `~/.config/sway/config`
49a ta bude ihned fungovat. Jinak zkopírujte do `~/.config/sway/config` ukázkový
50konfiguraÄní soubor. Ten se obvykle nachází v `/etc/sway/config`.
51Pro více informací o konfiguraci spusťte `man 5 sway`.
52
53## Spuštění
54
55Spusťte `sway` z TTY. Některé správce zobrazení mohou fungovat, ale nejsou
56podporovány sway (je známo, že gdm funguje docela dobře).
57
58[en]: https://github.com/swaywm/sway#readme
59[cs]: README.cs.md
60[de]: README.de.md
61[dk]: README.dk.md
62[es]: README.es.md
63[fr]: README.fr.md
64[sv]: README.sv.md
65[gr]: README.gr.md
66[hi]: README.hi.md
67[hu]: README.hu.md
68[ir]: README.ir.md
69[it]: README.it.md
70[ja]: README.ja.md
71[ko]: README.ko.md
72[nl]: README.nl.md
73[pl]: README.pl.md
74[pt]: README.pt.md
75[ro]: README.ro.md
76[ru]: README.ru.md
77[tr]: README.tr.md
78[uk]: README.uk.md
79[zh-CN]: README.zh-CN.md
80[zh-TW]: README.zh-TW.md
81[i3]: https://i3wm.org/
82[Wayland]: http://wayland.freedesktop.org/
83[FAQ]: https://github.com/swaywm/sway/wiki
84[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
85[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
86[GitHub releases]: https://github.com/swaywm/sway/releases
87[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
88[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
89[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.de.md b/README.de.md
index 24e66a60..68b411d9 100644
--- a/README.de.md
+++ b/README.de.md
@@ -1,30 +1,30 @@
1# Sway 1# Sway
2Sway 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](http://webchat.freenode.net/?channels=sway&uio=d4) bei (#sway on irc.freenode.net; Englisch). 2Sway 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).
3 3
4## Signaturen 4## Signaturen
5Jedes Release wird mit dem PGP-Schlüssel [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) signiert und auf GitHub veröffentlicht. 5Jedes 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.
6 6
7## Installation 7## Installation
8### Mit der Paketverwaltung
9Sway kann in vielen Distributionen direkt durch die Paketverwaltung installiert werden. Das Paket sollte "sway" heißen. Falls es kein solches Paket gibt, kannst du im [Wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) (englisch) nach mehr Informationen bezüglich deiner Distribution suchen.
10 8
11Falls du sway für deine eigene Distribution als Paket bereitstellen möchtest, solltest du die Entwickler per IRC oder E-Mail (sir@cmpwn.com) kontaktieren. 9### Über die Paketverwaltung
10
11Sway kann in vielen Distributionen direkt durch die Paketverwaltung installiert werden. Versuche einfach das Packet "sway" zu installieren.
12 12
13### Quellcode selbst kompilieren 13### Quellcode selbst kompilieren
14 14
15sway benötigt die folgenden Pakete: 15sway benötigt die folgenden Pakete:
16 16
17* meson\* 17* meson\*
18* [wlroots](https://github.com/swaywm/wlroots) 18* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
19* wayland 19* wayland
20* wayland-protocols\* 20* wayland-protocols\*
21* pcre 21* pcre2
22* json-c 22* json-c
23* pango 23* pango
24* cairo 24* cairo
25* gdk-pixbuf2 (Optional, wird für das Benachrichtigungsfeld (System Tray) benötigt) 25* gdk-pixbuf2 (Optional, wird für das Benachrichtigungsfeld (System Tray) benötigt)
26* [scdoc](https://git.sr.ht/~sircmpwn/scdoc)\* (Optional, wird für die Dokumentation (Man Pages) benötigt) 26* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (Optional, wird für die Dokumentation (Man Pages) benötigt)\*
27* git\* 27* git (Optional: Versionsinfo)\*
28 28
29_\*Werden nur während des Kompilierens benötigt_ 29_\*Werden nur während des Kompilierens benötigt_
30 30
@@ -34,12 +34,6 @@ Führe die folgenden Befehle aus:
34 ninja -C build 34 ninja -C build
35 sudo ninja -C build install 35 sudo ninja -C build install
36 36
37Falls dein System nicht logind benutzt, musst du sway noch die passenden Berechtigungen geben:
38
39 sudo chmod a+s /usr/local/bin/sway
40
41Sway läuft nur in der Startphase mit Root-Rechten.
42
43## Konfiguration 37## Konfiguration
44 38
45Falls 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`. 39Falls 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`.
diff --git a/README.dk.md b/README.dk.md
index 535000c3..5ce94cde 100644
--- a/README.dk.md
+++ b/README.dk.md
@@ -1,41 +1,43 @@
1# Sway 1# Sway
2 2
3Sway er en [i3](https://i3wm.org/)-kompatibel [Wayland](http://wayland.freedesktop.org/) compositor. 3Sway er en [i3]-kompatibel [Wayland] compositor. Læs [Ofte stillede spørgsmål].
4Læs [Ofte stillede spørgsmål](https://github.com/swaywm/sway/wiki). 4Deltag på [IRC kanalen][IRC kanal] \(#sway på irc.libera.chat).
5Deltag på [IRC kanalen](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway på irc.freenode.net).
6 5
7## Udgivelses Signaturer 6## Udgivelses Signaturer
8 7
9Udgivelser er signeret med [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 8Udgivelser er signeret med [E88F5E48] og publiceret [på GitHub][GitHub
10og publiseret på [GitHub](https://github.com/swaywm/sway/releases). 9releases].
11 10
12## Installation 11## Installation
13 12
14### Fra Pakker 13### Fra pakker
15 14
16Sway er tilgængelig i mange distributioner. Prøv at installere pakken "svay". Hvis den ikke er tilgængelig, så tjek [denne wiki-side](https://github.com/swaywm/sway/wiki/Unsupported-packages) 15Sway er tilgængelig i mange distributioner. Prøv at installere "sway" pakken
17for information om installation til din(e) distribution(er). 16fra din.
18 17
19Hvis du er interesseret i at lave en Sway pakke til din distribution, burde du besøge IRC 18Hvis du er interesseret i at pakke Sway til din distribution, kan du tage forbi
20kanalen eller sende en e-mail til sir@cmpwn.com for rådgivning. 19IRC kanalen eller sende en email til sir@cmpwn.com for rådgivning.
21 20
22### Kompilering fra kildekode 21### Kompilering fra kildekode
23 22
24Installation afhænger af følgende programmer: 23Se [denne wiki-side][Opsætning til udvikling] hvis du vil bygge HEAD af sway og
24wlroots til test eller udvikling.
25
26Installationsafhængigheder:
25 27
26* meson \* 28* meson \*
27* [wlroots](https://github.com/swaywm/wlroots) 29* [wlroots]
28* wayland 30* wayland
29* wayland-protocols \* 31* wayland-protocols \*
30* pcre 32* pcre2
31* json-c 33* json-c
32* pango 34* pango
33* cairo 35* cairo
34* gdk-pixbuf2 (valgfrit tillæg: system tray) 36* gdk-pixbuf2 (valgfrit: system tray)
35* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (valgfrit tillæg: man pages) \* 37* [scdoc] (valgfrit: man pages) \*
36* git \* 38* git \*
37 39
38_\*Kompiler krav_ 40_\*Kompileringsafhængighed_
39 41
40Kør følgende kommandoer: 42Kør følgende kommandoer:
41 43
@@ -43,21 +45,24 @@ Kør følgende kommandoer:
43 ninja -C build 45 ninja -C build
44 sudo ninja -C build install 46 sudo ninja -C build install
45 47
46På systemer uden 'logind', behøver du at sætte ejerens bruger-id for Sways eksekverbare filer - såkaldt SUID (Set owner User ID):
47
48 sudo chmod a+s /usr/local/bin/sway
49
50Sway vil frasige sig 'root' tilladelser kort efter opstart
51
52## Konfiguration 48## Konfiguration
53 49
54Hvis du allerede bruger i3, bør du kopiere din i3-konfiguration til `~/.config/sway/config` og 50Hvis du allerede bruger i3 kan du bare kopiere din i3 konfiguration til
55det vil bare fungerer. Ellers skal du kopiere eksempel konfigurations filen til 51`~/.config/sway/config`. Ellers skal du kopiere eksempelkonfigurationsfilen til
56`~/.config/sway/config`. Den er normalt placeret i `/etc/sway/config`. 52`~/.config/sway/config`. Den er normalt placeret i `/etc/sway/config`. Kør
57Kør `man 5 sway` for at få oplysninger om konfigurationen. 53`man 5 sway` for at få oplysninger om konfigurationen.
58 54
59## Kører 55## Eksekvering
60 56
61Kør `sway` fra en TTY. Nogle display managers fungerer muligvis, men understøttes ikke af 57Kør `sway` fra en TTY. Nogle display managers kan fungere, men Sway yder ikke
62Sway (gdm er kendt for at fungere temmelig godt). 58support til dem (gdm er kendt for at fungere temmelig godt).
63 59
60[i3]: https://i3wm.org/
61[Wayland]: http://wayland.freedesktop.org/
62[Ofte stillede spørgsmål]: https://github.com/swaywm/sway/wiki
63[IRC kanal]: https://web.libera.chat/gamja/?channels=#sway
64[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
65[GitHub releases]: https://github.com/swaywm/sway/releases
66[Opsætning til udvikling]: https://github.com/swaywm/sway/wiki/Development-Setup
67[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
68[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.es.md b/README.es.md
index 53f11f68..1f1657df 100644
--- a/README.es.md
+++ b/README.es.md
@@ -1,12 +1,12 @@
1# sway 1# sway
2 2
3sway es un compositor de [Wayland](http://wayland.freedesktop.org/) compatible con [i3](https://i3wm.org/). 3sway es un compositor de [Wayland](http://wayland.freedesktop.org/) compatible con [i3](https://i3wm.org/).
4Lea el [FAQ](https://github.com/swaywm/sway/wiki). Únase al [canal de IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on 4Lea el [FAQ](https://github.com/swaywm/sway/wiki). Únase al [canal de IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway on
5irc.freenode.net). 5irc.libera.chat).
6 6
7## Firmas de las versiones 7## Firmas de las versiones
8 8
9Las distintas versiones están firmadas con [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 9Las distintas versiones están firmadas con [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
10y publicadas [en GitHub](https://github.com/swaywm/sway/releases). 10y publicadas [en GitHub](https://github.com/swaywm/sway/releases).
11 11
12## Instalación 12## Instalación
@@ -25,10 +25,10 @@ escriba un email a sir@cmpwn.com
25Instale las dependencias: 25Instale las dependencias:
26 26
27* meson \* 27* meson \*
28* [wlroots](https://github.com/swaywm/wlroots) 28* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
29* wayland 29* wayland
30* wayland-protocols \* 30* wayland-protocols \*
31* pcre 31* pcre2
32* json-c 32* json-c
33* pango 33* pango
34* cairo 34* cairo
@@ -44,12 +44,6 @@ Desde su consola, ejecute las órdenes:
44 ninja -C build 44 ninja -C build
45 sudo ninja -C build install 45 sudo ninja -C build install
46 46
47En sistemas sin `logind`, necesitará cambiar los permisos del archivo compilado de sway:
48
49 sudo chmod a+s /usr/local/bin/sway
50
51Sway abandonará los permisos de super-usuario al poco de arrancar.
52
53## Configuración 47## Configuración
54 48
55Si ya utiliza i3, copie su archivo de configuración de i3 a `~/.config/sway/config` y 49Si ya utiliza i3, copie su archivo de configuración de i3 a `~/.config/sway/config` y
diff --git a/README.fr.md b/README.fr.md
index a72696d6..7752fc70 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -1,48 +1,49 @@
1# sway 1# sway
2 2
3Sway est un compositeur [Wayland](http://wayland.freedesktop.org/) compatible 3Sway est un compositeur [Wayland] compatible avec [i3]. Lisez la
4avec [i3](https://i3wm.org/), **en cours de développement**. Lisez la 4[FAQ]. Rejoignez le [canal IRC] (#sway sur irc.libera.chat).
5[FAQ](https://github.com/swaywm/sway/wiki). Rejoignez le [canal
6IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway sur
7irc.freenode.net).
8 5
9## Aide en français 6## Aide en français
10 7
11[abdelq](//github.com/abdelq) fournit du support en français sur IRC et Github, dans le fuseau horaire UTC-4 (EST). 8[abdelq] fournit du support en français sur IRC et Github, dans le fuseau
9horaire UTC-4 (EST).
12 10
13## Signatures de nouvelles versions 11## Signatures de nouvelles versions
14 12
15Les nouvelles versions sont signées avec [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 13Les nouvelles versions sont signées avec [E88F5E48] et publiées
16et publiées [sur GitHub](https://github.com/swaywm/sway/releases). 14[sur GitHub][versions GitHub].
17 15
18## Installation 16## Installation
19 17
20### À partir de paquets 18### À partir de paquets
21 19
22Sway est disponible sur plusieurs distributions. Essayez d'installer le paquet "sway" pour 20Sway est disponible sur beaucoup de distributions. Essayez d'installer le
23la vôtre. Si ce n'est pas disponible, consultez [cette page wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages) 21paquet "sway" pour la vôtre.
24pour de l'information sur l'installation pour vos distributions.
25 22
26Si vous êtes intéressé à maintenir Sway pour votre distribution, passez par le canal 23Si vous êtes intéressé à maintenir Sway pour votre distribution, passez sur le
27IRC ou envoyez un e-mail à sir@cmpwn.com (en anglais seulement) pour des conseils. 24canal IRC ou envoyez un e-mail à sir@cmpwn.com (en anglais seulement) pour des
25conseils.
28 26
29### Compilation depuis la source 27### Compilation depuis les sources
28
29Consultez [cette page wiki][Configuration de développement] si vous souhaitez
30compiler la révision HEAD de sway et wlroots pour tester ou développer.
30 31
31Installez les dépendances : 32Installez les dépendances :
32 33
33* meson \* 34* meson \*
34* [wlroots](https://github.com/swaywm/wlroots) 35* [wlroots]
35* wayland 36* wayland
36* wayland-protocols \* 37* wayland-protocols \*
37* pcre 38* pcre2
38* json-c 39* json-c
39* pango 40* pango
40* cairo 41* cairo
41* gdk-pixbuf2 (optionnel: system tray) 42* gdk-pixbuf2 (optionnel : system tray)
42* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optionnel: requis pour les pages man) \* 43* [scdoc] (optionnel : requis pour les pages man) \*
43* git \* 44* git (optionnel : information de version) \*
44 45
45_\*Requis uniquement pour la compilation_ 46_\* Requis uniquement pour la compilation_
46 47
47Exécutez ces commandes : 48Exécutez ces commandes :
48 49
@@ -50,20 +51,27 @@ Exécutez ces commandes :
50 ninja -C build 51 ninja -C build
51 sudo ninja -C build install 52 sudo ninja -C build install
52 53
53Sur les systèmes sans logind, vous devez suid le binaire de sway :
54
55 sudo chmod a+s /usr/local/bin/sway
56
57Sway se débarassera des permissions *root* peu de temps après le démarrage.
58
59## Configuration 54## Configuration
60 55
61Si vous utilisez déjà i3, copiez votre configuration i3 à `~/.config/sway/config` et 56Si vous utilisez déjà i3, copiez votre configuration i3 vers
62cela va fonctionner. Sinon, copiez l'exemple de fichier de configuration à 57`~/.config/sway/config` et sway fonctionnera directement. Sinon, copiez
63`~/.config/sway/config`. Il se trouve généralement dans `/etc/sway/config`. 58l'exemple de fichier de configuration vers `~/.config/sway/config`. Il se
64Exécutez `man 5 sway` pour l'information sur la configuration. 59trouve généralement dans `/etc/sway/config`. Exécutez `man 5 sway` pour lire la
60documentation pour la configuration de sway.
65 61
66## Exécution 62## Exécution
67 63
68Exécutez `sway` à partir d'un TTY. Certains gestionnaires d'affichage peuvent fonctionner, 64Exécutez `sway` à partir d'un TTY. Certains gestionnaires d'affichage peuvent
69mais ne sont pas supportés par Sway (gdm est réputé pour assez bien fonctionner). 65fonctionner, mais ne sont pas supportés par Sway (gdm est réputé pour assez
66bien fonctionner).
67
68[Wayland]: http://wayland.freedesktop.org/
69[i3]: https://i3wm.org/
70[FAQ]: https://github.com/swaywm/sway/wiki
71[canal IRC]: https://web.libera.chat/gamja/?channels=#sway
72[abdelq]: https://github.com/abdelq
73[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
74[versions GitHub]: https://github.com/swaywm/sway/releases
75[Configuration de développement]: https://github.com/swaywm/sway/wiki/Development-Setup
76[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
77[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.ge.md b/README.ge.md
new file mode 100644
index 00000000..bb8b9a34
--- /dev/null
+++ b/README.ge.md
@@ -0,0 +1,61 @@
1# sway
2
3sway áƒáƒ áƒ˜áƒ¡ [i3]-თáƒáƒ•áƒ¡áƒ”ბáƒáƒ“ი [Wayland]-ის კáƒáƒ›áƒžáƒáƒ–იტáƒáƒ áƒ˜. მეტი ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ˜áƒ¡áƒ—ვის იხილეთ
4[FAQ]. დáƒáƒ£áƒ™áƒáƒ•áƒ¨áƒ˜áƒ áƒ“ით [IRC áƒáƒ áƒ®áƒ¡][IRC channel] \(#sway irc.libera.chat-ზე).
5
6## გáƒáƒ›áƒáƒ¨áƒ•áƒ”ბის ხელმáƒáƒ¬áƒ”რები
7
8გáƒáƒ›áƒáƒ¨áƒ•áƒ”ბები ხელმáƒáƒ¬áƒ”რილირ[E88F5E48]-ით დრგáƒáƒ›áƒáƒ¥áƒ•áƒ”ყნებულირ[GitHub-ზე][GitHub releases].
9
10## ინსტáƒáƒšáƒáƒªáƒ˜áƒ
11
12### რეპáƒáƒ–იტáƒáƒ áƒ˜áƒ˜áƒ“áƒáƒœ
13
14Sway áƒáƒ áƒ˜áƒ¡ ხელმისáƒáƒ¬áƒ•áƒ“áƒáƒ›áƒ˜ ბევრი დისტრიბუტáƒáƒªáƒ˜áƒ˜áƒ¡áƒ—ვის. ცáƒáƒ“ეთ "sway" პáƒáƒ™áƒ”ტის ინსტáƒáƒšáƒáƒªáƒ˜áƒ თქვენთვის.
15
16### კáƒáƒ“ის კáƒáƒ›áƒžáƒ˜áƒšáƒáƒªáƒ˜áƒ
17
18იხილეთ [ეს ვიკი გვერდი][Development setup] თუ გინდáƒáƒ— რáƒáƒ› áƒáƒáƒ¬áƒ§áƒáƒ— sway დრwlroots სáƒáƒ¢áƒ”სტáƒáƒ“ áƒáƒœ დეველáƒáƒžáƒ›áƒ”ნტისთვის.
19
20დáƒáƒáƒ˜áƒœáƒ¡áƒ¢áƒáƒšáƒ˜áƒ áƒ”თ დáƒáƒ›áƒáƒ™áƒ˜áƒ“ებულებები:
21
22* meson \*
23* [wlroots]
24* wayland
25* wayland-protocols \*
26* pcre2
27* json-c
28* pango
29* cairo
30* gdk-pixbuf2 (áƒáƒ¡áƒ”ვე áƒáƒ áƒ©áƒ”ვითიáƒ: system tray)
31* [scdoc] (áƒáƒ¡áƒ”ვე áƒáƒ áƒ©áƒ”ვითიáƒ: man pages) \*
32* git (áƒáƒ¡áƒ”ვე áƒáƒ áƒ©áƒ”ვითიáƒ: version info) \*
33
34_\* Compile-time dep_
35
36გáƒáƒ£áƒ¨áƒ•áƒ˜áƒ— ეს ბრძáƒáƒœáƒ”ბები:
37
38 meson build/
39 ninja -C build/
40 sudo ninja -C build/ install
41
42## კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ
43
44თუ უკვე იყენებთ i3-ს, მáƒáƒ¨áƒ˜áƒœ დáƒáƒáƒ™áƒáƒžáƒ˜áƒ áƒ” i3 კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ დრჩáƒáƒ¡áƒ•áƒ˜ `~/.config/sway/config`
45დრუპრáƒáƒ‘ლემáƒáƒ“ იმუშáƒáƒ•áƒ”ბს პირდáƒáƒžáƒ˜áƒ . წინáƒáƒáƒ¦áƒ›áƒ“ეგ შემთხვევáƒáƒ¨áƒ˜ კáƒáƒœáƒ¤áƒ˜áƒ’ურáƒáƒªáƒ˜áƒ˜áƒ¡ ნიმუში ჩáƒáƒáƒ™áƒáƒžáƒ˜áƒ áƒ”თ áƒáƒ¥: `~/.config/sway/config`. კáƒáƒ›áƒžáƒ˜áƒ’ურáƒáƒªáƒ˜áƒ˜áƒ¡ ნიმუში ხშირ შემთხვევáƒáƒ¨áƒ˜ áƒáƒ áƒ˜áƒ¡ `/etc/sway/config`.
46გáƒáƒ£áƒ¨áƒ•áƒ˜ `man 5 sway` კáƒáƒœáƒžáƒ˜áƒ’ურáƒáƒªáƒ˜áƒáƒ–ე ინფáƒáƒ áƒ›áƒáƒªáƒ˜áƒ˜áƒ¡ მისáƒáƒ¦áƒ”ბáƒáƒ“.
47
48## გáƒáƒ¨áƒ•áƒ”ბáƒ
49
50გáƒáƒ£áƒ¨áƒ•áƒ˜ `sway` TTY-ისთვის. ზáƒáƒ’იერთმრლáƒáƒ’ინ მენეჯერმრშეიძლებრიმუშáƒáƒ•áƒáƒ¡, მáƒáƒ’რáƒáƒ› áƒáƒ 
51áƒáƒ áƒ˜áƒ¡ მხáƒáƒ áƒ“áƒáƒ­áƒ”რილი sway-სგáƒáƒœ (რáƒáƒ’áƒáƒ áƒª წესი კáƒáƒ áƒ’áƒáƒ“ მუშáƒáƒáƒ‘ს gdm).
52
53[i3]: https://i3wm.org/
54[Wayland]: http://wayland.freedesktop.org/
55[FAQ]: https://github.com/swaywm/sway/wiki
56[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
57[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
58[GitHub releases]: https://github.com/swaywm/sway/releases
59[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
60[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
61[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.gr.md b/README.gr.md
new file mode 100644
index 00000000..d697f78e
--- /dev/null
+++ b/README.gr.md
@@ -0,0 +1,67 @@
1# Sway
2
3Το sway ένα [i3]-συμβατό [Wayland] compositor. Διαβάστε το [FAQ]. Μπείτε στο
4[IRC channel] \(#sway on irc.libera.chat).
5
6## ΥπογÏαφές δημοσιεÏσεων
7
8Οι εκδόσεις είναι υπογεÏαμμένες με [E88F5E48] και δημοσιευμένες [στο GitHub][GitHub releases].
9
10## Εγκατάσταση
11
12### Από πακέτα
13
14Το Sway είναι διαθέσιμο σε πολλά distributions. Δοκιμάστε εγκαταστώντας το "sway" package για
15το δικό σας.
16
17Εάν ενδιαφέÏεστε για packaging του sway για το distribution σας, να πάτε στο IRC
18channel ή στείλτε ένα email στο sir@cmpwn.com για συμβουλές.
19
20### Compiling από πηγή
21
22ΤσεκάÏετε [αυτό το wiki page][Development setup] εάμα θέλετε να κάνετε build το HEAD του
23sway και wlroots γιά τεστάÏισμα ή development.
24
25Εγκατάσταση των dependencies:
26
27* meson \*
28* [wlroots]
29* wayland
30* wayland-protocols \*
31* pcre2
32* json-c
33* pango
34* cairo
35* gdk-pixbuf2 (Ï€ÏοαιÏετικό: system tray)
36* [scdoc] (Ï€ÏοαιÏετικό: man pages) \*
37* git (Ï€ÏοαιÏετικό: πληÏοφοÏίες εκδώσεων) \*
38
39_\*Compile-time dep_
40
41ΤÏέξτε αυτά τα commands:
42
43 meson build/
44 ninja -C build/
45 sudo ninja -C build/ install
46
47## Configuration
48
49Εάν ήδη χÏησιμοποιήτε το i3, αντιγÏάψτε το i3 config σας στο `~/.config/sway/config` και
50θα δουλέψει out of the box. Αλλιώς, αντιγÏάψτε το sample configuration αÏχείο στο
51`~/.config/sway/config`. Το οποίο συνήθως βÏίσκεται στο `/etc/sway/config`.
52Κάντε run `man 5 sway` για πληÏοφοÏίες Ï„Î¿Ï configuration.
53
54## ΤÏέχοντας
55
56ΤÏέξτε `sway` από ένα TTY. ΜεÏίκα display managers μποÏεί να δουλέψουν αλλά δέν είναι συμβατά με
57το sway (το gdm γνωÏίζεται να δουλέβει σχετικά καλά).
58
59[i3]: https://i3wm.org/
60[Wayland]: http://wayland.freedesktop.org/
61[FAQ]: https://github.com/swaywm/sway/wiki
62[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
63[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
64[GitHub releases]: https://github.com/swaywm/sway/releases
65[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
66[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
67[scdoc]: https://git.sr.ht/~sircmpwn/scdoc \ No newline at end of file
diff --git a/README.hi.md b/README.hi.md
new file mode 100644
index 00000000..eae5e90a
--- /dev/null
+++ b/README.hi.md
@@ -0,0 +1,63 @@
1# sway
2
3sway à¤à¤• [i3](https://i3wm.org/)-अनà¥à¤•à¥‚ल
4[Wayland](https://wayland.freedesktop.org/) Compositor है।
5[FAQ](https://github.com/swaywm/sway/wiki) पढिये। [IRC
6Channel](https://web.libera.chat/gamja/?channels=#sway)
7(irc.libera.chat पर #sway) में भी जà¥à¤¡à¤¿à¤¯à¥‡à¥¤
8
9## रिलीज हसà¥à¤¤à¤¾à¤•à¥à¤·à¤°
10
11रिलीजें
12[E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
13से साइन होतें हैं और [Github पर](https://github.com/swaywm/sway/releases) पà¥à¤°à¤•à¤¾à¤¶à¤¿à¤¤ होते हैं।
14
15## इंसà¥à¤Ÿà¥Œà¤²à¥‡à¤¶à¤¨
16
17### पैकेजों के दà¥à¤µà¤¾à¤°à¤¾
18
19Sway कई distributions में उपà¥à¤²à¤¬à¥à¤§ है। आप अपने में "sway" नामक पैकेज इंसà¥à¤Ÿà¥Œà¤² करके देख
20सकते हैं।
21
22### Source से compile करके
23
24यदि आप परीकà¥à¤·à¤£ और विकास के लिठsway और wlroots के नवीनतम संसà¥à¤•à¤°à¤£ बनाना
25चाहते हैं, तो [यह विकी
26पृषà¥à¤ ](https://github.com/swaywm/sway/wiki/Development-Setup) देखें।
27
28निरà¥à¤­à¤°à¤¤à¤¾à¤à¤‚:
29
30* meson \*
31* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
32* wayland
33* wayland-protocols \*
34* pcre2
35* json-c
36* pango
37* cairo
38* gdk-pixbuf (वैकलà¥à¤ªà¤¿à¤•: system tray के लिये)
39* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (वैकलà¥à¤ªà¤¿à¤•: man पृषà¥à¤ à¥‹à¤‚ के लिये)
40 \*
41* git (वैकलà¥à¤ªà¤¿à¤•: संसà¥à¤•à¤°à¤£ जानने के लिये)
42
43_\* Compilation के समय आवशà¥à¤¯à¤•_
44
45ये commands चलाà¤à¤‚:
46
47 meson build/
48 ninja -C build/
49 sudo ninja -C build/ install
50
51## Configuration
52
53अगर आप पहले से ही i3 का उपयोग करते हैं तो अपने i3 config को
54`~/.config/sway/config` में copy कर लीजिये और वह बिना किसी परिवरà¥à¤¤à¤¨ के काम
55करेगा। अनà¥à¤¯à¤¥à¤¾, नमूने configuration file को `~/.config/sway/config` में copy
56कर लीजिये। यह सामानà¥à¤¯à¤¤à¤ƒ `/etc/sway/config` में पाया जाता है। `man 5
57sway` से आप configuration के बारे में जानकारी पà¥à¤°à¤¾à¤ªà¥à¤¤ कर सकते हैं।
58
59## चलाना
60
61आप à¤à¤• tty से `sway` को चला सकते हैं। कà¥à¤› display managers काम करते हैं परनà¥à¤¤à¥ ये
62sway के दà¥à¤µà¤¾à¤°à¤¾ समरà¥à¤¥à¤¿à¤¤ नहीं है (gdm के बारे में जाना गया है कि वह सही काम करता
63है)।
diff --git a/README.hu.md b/README.hu.md
new file mode 100644
index 00000000..82ca6785
--- /dev/null
+++ b/README.hu.md
@@ -0,0 +1,70 @@
1# sway
2
3A 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).
4
5## Csomag aláírások
6
7A kiadott csomagok az [E88F5E48] kulccsal vannak aláírva és [GitHub-on][GitHub releases] publikálva.
8
9## Telepítés
10
11### Csomagból
12
13A Sway sok disztribúció csomagkezelőjéből elérhető, próbáld meg a "sway"
14csomagot telepíteni az általad használt eszközzel.
15
16Ha szeretnél csomagot készíteni a saját disztribúciódhoz, ugorj be az IRC
17csatornára, vagy küldj levelet a sir@cmpwn.com címre tanácsokért.
18
19### Fordítás forráskódból
20
21Olvasd el [ezt a wiki oldalt][Development setup], ha szeretnéd tesztelési vagy
22fejlesztési célokból lefordítani az aktuális (HEAD) állapotát a `sway`-nek és a
23`wlroots`-nak.
24
25Telepítsd a függőségeket:
26
27* meson \*
28* [wlroots]
29* wayland
30* wayland-protocols \*
31* pcre2
32* json-c
33* pango
34* cairo
35* gdk-pixbuf2 (opcionális: system tray)
36* [scdoc] (opcionális: man pages) \*
37* git (opcionális: version info) \*
38
39_\*Fordításidejű függőség_
40
41Futtasd ezeket a parancsokat:
42
43 meson build
44 ninja -C build
45 sudo ninja -C build install
46
47## Konfiguráció
48
49Ha előzőleg i3-mat használtál, akkor átmásolhatod az i3 beállításaidat a
50`~/.config/sway/config` file-ba és ugyanúgy működni fognak. Egyéb esetben másold
51le kiindulási alapnak a mintát, ami általában az `etc/sway/config` elérési
52útvonalon található.
53Futtasd a `man 5 sway` parancsot további információért a konfigurációval
54kapcsolatban.
55
56## Futtatás
57
58Futtasd a `sway` parancsot egy TTY felületről. Néhány bejelentkezéskezelő
59(display manager) működhet, de alapvetően nem támogatottak a sway által. (A
60gdm-ről ismeretes, hogy egész jól működik.)
61
62[i3]: https://i3wm.org/
63[Wayland]: http://wayland.freedesktop.org/
64[FAQ]: https://github.com/swaywm/sway/wiki
65[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
66[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
67[GitHub releases]: https://github.com/swaywm/sway/releases
68[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
69[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
70[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.ir.md b/README.ir.md
new file mode 100644
index 00000000..a485a405
--- /dev/null
+++ b/README.ir.md
@@ -0,0 +1,54 @@
1# sway
2
3&rlm;sway یک کامپوزیتور الهام گرÙته از [i3](https://i3wm.org/) بر روی [Wayland](http://wayland.freedesktop.org/) است. [سوال‌های متداول](https://github.com/swaywm/sway/wiki) را بخوانید. در [کانال
4IRC](http://web.libera.chat/gamja/?channels=sway&uio=d4) عضو شوید (&lrm;#sway&rlm; در
5irc.libera.chat).
6
7برای حمایت از تیم توسعه sway به [صÙحه
8Patreon با نام کاربری SirCmpwn](https://patreon.com/sircmpwn) مراجعه کنید.
9
10## امضای نسخه‌ها
11
12امضای نسخه‌ها با [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) در [GitHub](https://github.com/swaywm/sway/releases) منتشر می‌شود.
13
14## شیوه نصب
15
16### از بسته‌های رسمی
17
18&rlm;sway در بسته‌های رسمی توزیع‌های مختل٠وجود دارد. بسته «sway» را نصب کنید. در صورتی Ú©Ù‡ بسته رسمی وجود نداشت، برای آگاهی بیشتر درباره نصب روی توزیعتان به این [صÙحه راهنما](https://github.com/swaywm/sway/wiki/Unsupported-packages) مراجعه کنید.
19
20اگر به ایجاد بسته sway برای توزیعتان علاقه‌مند هستید، از کانال IRC استÙاده کنید یا به sir@cmpwn.com ایمیل بزنید.
21
22### کامپایل کردن کد
23
24چنانچه می‌خواهید آخرین نسخه کد sway Ùˆ wlroots را برای آزمایش یا توسعه بسازید به این [صÙحه راهنما](https://github.com/swaywm/sway/wiki/Development-Setup) مراجعه کنید.
25
26بسته‌های مورد نیاز:
27
28* meson \*
29* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
30* wayland
31* wayland-protocols \*
32* pcre2
33* json-c
34* pango
35* cairo
36* gdk-pixbuf2 (انتخابی: برای system tray)
37* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (انتخابی: برای صÙحه‌های راهنما) \*
38* git (انتخابی: برای اطلاع در خصوص نسخه‌ها) \*
39
40_\*نیازمندی‌های زمان کامپایل برنامه_
41
42این Ùرمان‌ها را اجرا کنید:
43
44 meson build
45 ninja -C build
46 sudo ninja -C build install
47
48### شخصی سازی و تنظیمات
49
50اگر در حال حاضر از i3 استÙاده می‌کنید، تنظیمات i3 خودتان را در Ùایل ‪`~/.config/sway/config`‬ Ú©Ù¾ÛŒ کنید Ùˆ بدون نیاز به تغییر کار خواهد کرد. در غیر این‌صورت، Ùایل نمونه تنظیمات را استÙاده کنید. این Ùایل عموما در ‪`/etc/sway/config`‬ قرار دارد. برای آگاهی بیشتر `man 5 sway` را اجرا کنید.
51
52## اجرا
53
54در محیط TTY کاÙیست `sway` را اجرا کنید. ممکن است ابزارهای مدیریت نمایشگری نیز برای این کار وجود داشته باشند اما از طر٠sway پشتیبانی نمی‌شوند (gdm عملکرد خوبی در این زمینه دارد).
diff --git a/README.it.md b/README.it.md
new file mode 100644
index 00000000..82bb5783
--- /dev/null
+++ b/README.it.md
@@ -0,0 +1,66 @@
1# sway
2
3sway è un compositore di [Wayland] compatibile con [i3]. Leggi le [FAQ].
4Unisciti al [canale di IRC] \(#sway su irc.libera.chat).
5
6## Firma delle versioni
7
8Le versioni sono firmate con la chiave [E88F5E48] e sono pubblicate
9[su GitHub][GitHub releases].
10
11## Installazione
12
13### Da un pacchetto
14
15Sway è disponibile in molte distribuzioni, prova a installare il pacchetto
16"sway" per la tua.
17
18### Compilazione dei sorgenti
19
20Consulta [questa pagina del wiki][Development setup] se vuoi compilare l'HEAD
21di sway e wlroots per testarli o contribuire allo sviluppo.
22
23Installa le dipendenze:
24
25* meson \*
26* [wlroots]
27* wayland
28* wayland-protocols \*
29* pcre
30* json-c
31* pango
32* cairo
33* gdk-pixbuf2 (opzionale: area di notifica)
34* [scdoc] (opzionale: pagine del manuale) \*
35* git (opzionale: informazioni sulla versione) \*
36
37_\* Dipendenza necessaria per la compilazione_
38
39Esegui questi comandi:
40
41 meson build/
42 ninja -C build/
43 sudo ninja -C build/ install
44
45## Configurazione
46
47Se hai già usato i3, copia il tuo file di configurazione in
48`~/.config/sway/config` e sway funzionerà immediatamente. Altrimenti, copia il
49file d'esempio in `~/.config/sway/config`, generalmente è situato in
50`/etc/sway/config`. Consulta `man 5 sway` per ulteriori informazioni sulla
51configurazione.
52
53## Esecuzione
54
55Lancia `sway` da un TTY. Alcuni gestori d'accesso potrebbero funzionare ma non
56sono supportati da sway (gdm funziona abbastanza bene).
57
58[i3]: https://i3wm.org/
59[Wayland]: http://wayland.freedesktop.org/
60[FAQ]: https://github.com/swaywm/sway/wiki
61[canale di IRC]: https://web.libera.chat/gamja/?channels=#sway
62[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
63[GitHub releases]: https://github.com/swaywm/sway/releases
64[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
65[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
66[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.ja.md b/README.ja.md
index fa28f3da..4e9a9971 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -2,7 +2,7 @@
2 2
3Swayã¯[i3](https://i3wm.org/)互æ›ãª[Wayland](http://wayland.freedesktop.org/)コンãƒã‚¸ã‚¿ã§ã™ã€‚ 3Swayã¯[i3](https://i3wm.org/)互æ›ãª[Wayland](http://wayland.freedesktop.org/)コンãƒã‚¸ã‚¿ã§ã™ã€‚
4[FAQ](https://github.com/swaywm/sway/wiki)ã‚‚åˆã‚ã›ã¦ã”覧ãã ã•ã„。 4[FAQ](https://github.com/swaywm/sway/wiki)ã‚‚åˆã‚ã›ã¦ã”覧ãã ã•ã„。
5[IRC ãƒãƒ£ãƒ³ãƒãƒ«](http://webchat.freenode.net/?channels=sway&uio=d4) (irc.freenode.netã®#sway)ã‚‚ã‚ã‚Šã¾ã™ã€‚ 5[IRC ãƒãƒ£ãƒ³ãƒãƒ«](https://web.libera.chat/gamja/?channels=#sway) (irc.libera.chatã®#sway)ã‚‚ã‚ã‚Šã¾ã™ã€‚
6 6
7[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 7[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
8 8
@@ -12,7 +12,7 @@ SirCmpwnã¯ã€æ—¥æœ¬èªžã§ã®ã‚µãƒãƒ¼ãƒˆã‚’IRCã¨GitHubã§è¡Œã„ã¾ã™ã€‚タイ
12 12
13## リリースã®ç½²å 13## リリースã®ç½²å
14 14
15Swayã®ãƒªãƒªãƒ¼ã‚¹ã¯[B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A)ã§ç½²åã•ã‚Œã€[GitHub](https://github.com/swaywm/sway/releases)ã§å…¬é–‹ã•ã‚Œã¦ã„ã¾ã™ã€‚ 15Swayã®ãƒªãƒªãƒ¼ã‚¹ã¯[E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)ã§ç½²åã•ã‚Œã€[GitHub](https://github.com/swaywm/sway/releases)ã§å…¬é–‹ã•ã‚Œã¦ã„ã¾ã™ã€‚
16 16
17## インストール 17## インストール
18 18
@@ -27,18 +27,18 @@ Swayã¯æ²¢å±±ã®ãƒ‡ã‚£ã‚¹ãƒˆãƒªãƒ“ューションã§æä¾›ã•ã‚Œã¦ã„ã¾ã™ã€‚"
27次ã®ä¾å­˜ãƒ‘ッケージをインストールã—ã¦ãã ã•ã„: 27次ã®ä¾å­˜ãƒ‘ッケージをインストールã—ã¦ãã ã•ã„:
28 28
29* meson \* 29* meson \*
30* [wlroots](https://github.com/swaywm/wlroots) 30* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
31* wayland 31* wayland
32* wayland-protocols \* 32* wayland-protocols \*
33* pcre 33* pcre2
34* json-c 34* json-c
35* pango 35* pango
36* cairo 36* cairo
37* gdk-pixbuf2 (システムイコンã§å¿…è¦ã§ã™) 37* gdk-pixbuf2 (ä»»æ„: システムイコンã§å¿…è¦ã§ã™)
38* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (manã§å¿…è¦ã§ã™) \* 38* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (ä»»æ„: manã§å¿…è¦ã§ã™) \*
39* git \* 39* git (ä»»æ„: ãƒãƒ¼ã‚¸ãƒ§ãƒ³æƒ…å ±ã§å¿…è¦ã§ã™) \*
40 40
41_\*コンパイルã®æ™‚_ 41_\*コンパイル時ã®ä¾å­˜_
42 42
43次ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’実行ã—ã¦ãã ã•ã„: 43次ã®ã‚³ãƒžãƒ³ãƒ‰ã‚’実行ã—ã¦ãã ã•ã„:
44 44
@@ -46,12 +46,6 @@ _\*コンパイルã®æ™‚_
46 ninja -C build 46 ninja -C build
47 sudo ninja -C build install 47 sudo ninja -C build install
48 48
49logindを使用ã—ã¦ã„ãªã„システムã§ã¯ã€ãƒã‚¤ãƒŠãƒªã«suidを設定ã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™:
50
51 sudo chmod a+s /usr/local/bin/sway
52
53swayã¯èµ·å‹•å¾Œã€ã™ãã«root許å¯ã‚’è½ã¨ã—ã¾ã™ã€‚
54
55## 設定 49## 設定
56 50
57æ—¢ã«i3を使用ã—ã¦ã„ã‚‹å ´åˆã¯ã€i3ã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã‚’`~/.config/sway/config`ã«ã‚³ãƒ”ーã™ã‚Œã°å‹•ãã¾ã™ã€‚ãã†ã§ãªã„å ´åˆã¯ã€ã‚µãƒ³ãƒ—ルã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã‚’`~/.config/sway/config`ã«ã‚³ãƒ”ーã—ã¦ãã ã•ã„。サンプルã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã¯ã€é€šå¸¸`/etc/sway/config`ã«ã‚ã‚Šã¾ã™ã€‚`man 5 sway`を実行ã™ã‚‹ã“ã¨ã§ã€è¨­å®šã«é–¢ã™ã‚‹æƒ…報を見るã“ã¨ãŒã§ãã¾ã™ã€‚ 51æ—¢ã«i3を使用ã—ã¦ã„ã‚‹å ´åˆã¯ã€i3ã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã‚’`~/.config/sway/config`ã«ã‚³ãƒ”ーã™ã‚Œã°å‹•ãã¾ã™ã€‚ãã†ã§ãªã„å ´åˆã¯ã€ã‚µãƒ³ãƒ—ルã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã‚’`~/.config/sway/config`ã«ã‚³ãƒ”ーã—ã¦ãã ã•ã„。サンプルã®è¨­å®šãƒ•ã‚¡ã‚¤ãƒ«ã¯ã€é€šå¸¸`/etc/sway/config`ã«ã‚ã‚Šã¾ã™ã€‚`man 5 sway`を実行ã™ã‚‹ã“ã¨ã§ã€è¨­å®šã«é–¢ã™ã‚‹æƒ…報を見るã“ã¨ãŒã§ãã¾ã™ã€‚
diff --git a/README.ko.md b/README.ko.md
index 9c3dd323..e086c174 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -1,11 +1,11 @@
1# sway 1# sway
2 2
3sway는 [i3](https://i3wm.org/)-호환 [Wayland](http://wayland.freedesktop.org/) ì»´í¬ì§€í„°ìž…니다. 3sway는 [i3](https://i3wm.org/)-호환 [Wayland](http://wayland.freedesktop.org/) ì»´í¬ì§€í„°ìž…니다.
4[FAQ](https://github.com/swaywm/sway/wiki)를 ì½ì–´ë³´ì„¸ìš”. [IRC 채ë„](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net)ë„ ìžˆìŠµë‹ˆë‹¤. 4[FAQ](https://github.com/swaywm/sway/wiki)를 ì½ì–´ë³´ì„¸ìš”. [IRC 채ë„](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat)ë„ ìžˆìŠµë‹ˆë‹¤.
5 5
6## 릴리즈 서명 6## 릴리즈 서명
7 7
8릴리즈는 [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A)ì—ì„œ 서명ë˜ê³ , 8릴리즈는 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)ì—ì„œ 서명ë˜ê³ ,
9[GitHubì—ì„œ](https://github.com/swaywm/sway/releases) 공개ë˜ê³  있습니다. 9[GitHubì—ì„œ](https://github.com/swaywm/sway/releases) 공개ë˜ê³  있습니다.
10 10
11## 설치 11## 설치
@@ -24,10 +24,10 @@ IRC 채ë„ì„ ë°©ë¬¸í•˜ê±°ë‚˜ sir@cmpwn.com으로 ì´ë©”ì¼ì„ ë³´ë‚´ ìƒë‹´ ë°›
24ë‹¤ìŒ ì˜ì¡´ íŒ¨í‚¤ì§€ë“¤ì„ ì„¤ì¹˜í•´ 주세요: 24ë‹¤ìŒ ì˜ì¡´ íŒ¨í‚¤ì§€ë“¤ì„ ì„¤ì¹˜í•´ 주세요:
25 25
26* meson \* 26* meson \*
27* [wlroots](https://github.com/swaywm/wlroots) 27* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
28* wayland 28* wayland
29* wayland-protocols \* 29* wayland-protocols \*
30* pcre 30* pcre2
31* json-c 31* json-c
32* pango 32* pango
33* cairo 33* cairo
@@ -43,12 +43,6 @@ _\*ì»´íŒŒì¼ ë–„ í•„ìš”_
43 ninja -C build 43 ninja -C build
44 sudo ninja -C build install 44 sudo ninja -C build install
45 45
46logind를 사용하고 있지 않는 시스템ì—서는, ë°”ì´ë„ˆë¦¬ì— suid를 설정할 필요가 있습니다:
47
48 sudo chmod a+s /usr/local/bin/sway
49
50Sway는 시작 í›„ì— root ê¶Œí•œì„ dropí•  것 입니다.
51
52## 설정 46## 설정
53 47
54i3를 ì´ë¯¸ 사용 중ì´ë¼ë©´, i3 configì„ `~/.config/sway/config`ë¡œ 복사하세요. 48i3를 ì´ë¯¸ 사용 중ì´ë¼ë©´, i3 configì„ `~/.config/sway/config`ë¡œ 복사하세요.
diff --git a/README.md b/README.md
index 4698afbe..15c7c099 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
1# sway 1# sway
2 2
3**[English][en]** - [日本語][ja] - [Français][fr] - [УкраїнÑька][uk] - [Español][es] - [Polski][pl] - [中文-简体][zh-CN] - [Deutsch][de] - [Nederlands][nl] - [РуÑÑкий][ru] - [中文-ç¹é«”][zh-TW] - [Português][pt] - [Danish][dk] - [한국어][ko] - [Română][ro] 3**[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]
4 4
5sway is an [i3]-compatible [Wayland] compositor. Read the [FAQ]. Join the 5sway is an [i3]-compatible [Wayland] compositor. Read the [FAQ]. Join the
6[IRC channel] \(#sway on irc.freenode.net). 6[IRC channel] \(#sway on irc.libera.chat).
7 7
8## Release Signatures 8## Release Signatures
9 9
@@ -16,9 +16,6 @@ Releases are signed with [E88F5E48] and published [on GitHub][GitHub releases].
16Sway is available in many distributions. Try installing the "sway" package for 16Sway is available in many distributions. Try installing the "sway" package for
17yours. 17yours.
18 18
19If you're interested in packaging sway for your distribution, stop by the IRC
20channel or shoot an email to sir@cmpwn.com for advice.
21
22### Compiling from Source 19### Compiling from Source
23 20
24Check out [this wiki page][Development setup] if you want to build the HEAD of 21Check out [this wiki page][Development setup] if you want to build the HEAD of
@@ -30,27 +27,22 @@ Install dependencies:
30* [wlroots] 27* [wlroots]
31* wayland 28* wayland
32* wayland-protocols \* 29* wayland-protocols \*
33* pcre 30* pcre2
34* json-c 31* json-c
35* pango 32* pango
36* cairo 33* cairo
37* gdk-pixbuf2 (optional: system tray) 34* gdk-pixbuf2 (optional: additional image formats for system tray)
35* [swaybg] (optional: wallpaper)
38* [scdoc] (optional: man pages) \* 36* [scdoc] (optional: man pages) \*
39* git (optional: version info) \* 37* git (optional: version info) \*
40 38
41_\*Compile-time dep_ 39_\* Compile-time dep_
42 40
43Run these commands: 41Run these commands:
44 42
45 meson build 43 meson build/
46 ninja -C build 44 ninja -C build/
47 sudo ninja -C build install 45 sudo ninja -C build/ install
48
49On systems without logind, you need to suid the sway binary:
50
51 sudo chmod a+s /usr/local/bin/sway
52
53Sway will drop root permissions shortly after startup.
54 46
55## Configuration 47## Configuration
56 48
@@ -65,26 +57,38 @@ Run `sway` from a TTY. Some display managers may work but are not supported by
65sway (gdm is known to work fairly well). 57sway (gdm is known to work fairly well).
66 58
67[en]: https://github.com/swaywm/sway#readme 59[en]: https://github.com/swaywm/sway#readme
68[ja]: https://github.com/swaywm/sway/blob/master/README.ja.md 60[ar]: README.ar.md
69[fr]: https://github.com/swaywm/sway/blob/master/README.fr.md 61[cs]: README.cs.md
70[uk]: https://github.com/swaywm/sway/blob/master/README.uk.md 62[de]: README.de.md
71[es]: https://github.com/swaywm/sway/blob/master/README.es.md 63[dk]: README.dk.md
72[pl]: https://github.com/swaywm/sway/blob/master/README.pl.md 64[es]: README.es.md
73[zh-CN]: https://github.com/swaywm/sway/blob/master/README.zh-CN.md 65[fr]: README.fr.md
74[de]: https://github.com/swaywm/sway/blob/master/README.de.md 66[ge]: README.ge.md
75[nl]: https://github.com/swaywm/sway/blob/master/README.nl.md 67[gr]: README.gr.md
76[ru]: https://github.com/swaywm/sway/blob/master/README.ru.md 68[hi]: README.hi.md
77[zh-TW]: https://github.com/swaywm/sway/blob/master/README.zh-TW.md 69[hu]: README.hu.md
78[pt]: https://github.com/swaywm/sway/blob/master/README.pt.md 70[ir]: README.ir.md
79[dk]: https://github.com/swaywm/sway/blob/master/README.dk.md 71[it]: README.it.md
80[ko]: https://github.com/swaywm/sway/blob/master/README.ko.md 72[ja]: README.ja.md
81[ro]: https://github.com/swaywm/sway/blob/master/README.ro.md 73[ko]: README.ko.md
74[nl]: README.nl.md
75[no]: README.no.md
76[pl]: README.pl.md
77[pt]: README.pt.md
78[ro]: README.ro.md
79[ru]: README.ru.md
80[sv]: README.sv.md
81[tr]: README.tr.md
82[uk]: README.uk.md
83[zh-CN]: README.zh-CN.md
84[zh-TW]: README.zh-TW.md
82[i3]: https://i3wm.org/ 85[i3]: https://i3wm.org/
83[Wayland]: http://wayland.freedesktop.org/ 86[Wayland]: http://wayland.freedesktop.org/
84[FAQ]: https://github.com/swaywm/sway/wiki 87[FAQ]: https://github.com/swaywm/sway/wiki
85[IRC channel]: http://webchat.freenode.net/?channels=sway&uio=d4 88[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
86[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48 89[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
87[GitHub releases]: https://github.com/swaywm/sway/releases 90[GitHub releases]: https://github.com/swaywm/sway/releases
88[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup 91[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
89[wlroots]: https://github.com/swaywm/wlroots 92[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
93[swaybg]: https://github.com/swaywm/swaybg/
90[scdoc]: https://git.sr.ht/~sircmpwn/scdoc 94[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.nl.md b/README.nl.md
index 86ebe398..bf1ea975 100644
--- a/README.nl.md
+++ b/README.nl.md
@@ -2,12 +2,12 @@
2 2
3Sway is een [i3](https://i3wm.org/)-compatibele [Wayland](http://wayland.freedesktop.org/) compositor. 3Sway is een [i3](https://i3wm.org/)-compatibele [Wayland](http://wayland.freedesktop.org/) compositor.
4Lees de [FAQ](https://github.com/swaywm/sway/wiki). Word lid van het [IRC 4Lees de [FAQ](https://github.com/swaywm/sway/wiki). Word lid van het [IRC
5kanaal](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway op 5kanaal](https://web.libera.chat/gamja/?channels=#sway) (#sway op
6irc.freenode.net). 6irc.libera.chat).
7 7
8## Releasehandtekeningen 8## Releasehandtekeningen
9 9
10Releases worden ondertekend met [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 10Releases worden ondertekend met [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
11en gepubliceerd [op GitHub](https://github.com/swaywm/sway/releases). 11en gepubliceerd [op GitHub](https://github.com/swaywm/sway/releases).
12 12
13## Installatie 13## Installatie
@@ -25,10 +25,10 @@ kanaal of stuur een e-mail naar sir@cmpwn.com voor advies.
25Afhankelijkheden installeren: 25Afhankelijkheden installeren:
26 26
27* meson \* 27* meson \*
28* [wlroots](https://github.com/swaywm/wlroots) 28* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
29* wayland 29* wayland
30* wayland-protocols \* 30* wayland-protocols \*
31* pcre 31* pcre2
32* json-c 32* json-c
33* pango 33* pango
34* cairo 34* cairo
@@ -44,12 +44,6 @@ Voer deze opdrachten uit:
44 ninja -C build 44 ninja -C build
45 sudo ninja -C build install 45 sudo ninja -C build install
46 46
47Op systemen zonder logind, moet je bij het binaire bestand het suid bit instellen:
48
49 sudo chmod a+s /usr/local/bin/sway
50
51Sway zal root-rechten kort na het opstarten loslaten.
52
53## Configuratie 47## Configuratie
54 48
55Als je al i3 gebruikt, kopieer dan je i3-configuratie naar `~/.config/sway/config` en 49Als je al i3 gebruikt, kopieer dan je i3-configuratie naar `~/.config/sway/config` en
diff --git a/README.no.md b/README.no.md
new file mode 100644
index 00000000..35d6e320
--- /dev/null
+++ b/README.no.md
@@ -0,0 +1,68 @@
1# Sway
2
3Sway er en [i3]-kompatibel [Wayland] compositor. Les [Ofte stilte spørsmål].
4Delta på [IRC kanalen][IRC kanal] \(#sway på irc.libera.chat).
5
6## Utgivelses Signaturer
7
8Utgivelser er signert med [E88F5E48] og publisert [på GitHub][GitHub
9releases].
10
11## Installasjon
12
13### Fra system pakker
14
15Sway er tilgjengelig i mange distribusjoner. Prøv å installere "sway" pakken
16fra din distro sine repoer.
17
18Er du interessert i å pakke Sway for din distribusjon kan du ta turen innom
19IRC-kanalen eller send en e-post til sir@cmpwn.com for råd.
20
21### Kompilering fra kildekode
22
23Se [denne wiki-siden][Oppsetting for utvikling] hvis du vil bygge fra HEAD grenen av sway og
24wlroots for testing eller utvikling.
25
26Installasjonsavhengigheter:
27
28* meson \*
29* [wlroots]
30* wayland
31* wayland-protocols \*
32* pcre2
33* json-c
34* pango
35* cairo
36* gdk-pixbuf2 (valgfritt: system tray)
37* [scdoc] (valgfritt: man pages) \*
38* git \*
39
40_\*Kompileringsavhengigheter_
41
42Kjør følgende kommandoer:
43
44 meson build
45 ninja -C build
46 sudo ninja -C build install
47
48## Konfigurasjon
49
50Hvis du allerede bruker i3 kan du bare kopiere din i3 konfigurasjon til
51`~/.config/sway/config`. Ellers skal du kopiere eksempel konfigurasjonsfilen til
52`~/.config/sway/config`. Eksempel filen er normalt plasert i `/etc/sway/config`. Kjør
53`man 5 sway` for å få oplysninger om konfigurasjonen.
54
55## Utførelse
56
57Kjør `sway` fra en TTY. Noen display managers kan fungere, men Sway har ikke
58støtte for dem (gdm er kjent for å fungere ganske bra).
59
60[i3]: https://i3wm.org/
61[Wayland]: http://wayland.freedesktop.org/
62[Ofte stilte spørsmål]: https://github.com/swaywm/sway/wiki
63[IRC kanal]: https://web.libera.chat/gamja/?channels=#sway
64[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
65[GitHub releases]: https://github.com/swaywm/sway/releases
66[Oppsetting for utvikling]: https://github.com/swaywm/sway/wiki/Development-Setup
67[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
68[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.pl.md b/README.pl.md
index b63b8567..65b3c3a1 100644
--- a/README.pl.md
+++ b/README.pl.md
@@ -1,12 +1,12 @@
1# sway 1# sway
2 2
3sway jest kompozytorem [Wayland](http://wayland.freedesktop.org/) kompatybilnym z [i3](https://i3wm.org/). 3sway jest kompozytorem [Wayland](http://wayland.freedesktop.org/) kompatybilnym z [i3](https://i3wm.org/).
4Przeczytaj [FAQ](https://github.com/swaywm/sway/wiki). Dołącz do [kanału IRC](http://webchat.freenode.net/?channels=sway&uio=d4) 4Przeczytaj [FAQ](https://github.com/swaywm/sway/wiki). Dołącz do [kanału IRC](https://web.libera.chat/gamja/?channels=#sway)
5(#sway na irc.freenode.net). 5(#sway na irc.libera.chat).
6 6
7## Podpisy cyfrowe wydań 7## Podpisy cyfrowe wydań
8 8
9Wydania sÄ… podpisywane przy pomocy klucza [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 9Wydania sÄ… podpisywane przy pomocy klucza [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
10i publikowane [na GitHubie](https://github.com/swaywm/sway/releases). 10i publikowane [na GitHubie](https://github.com/swaywm/sway/releases).
11 11
12## Instalacja 12## Instalacja
@@ -25,10 +25,10 @@ adres sir@cmpwn.com w celu uzyskania wskazówek.
25Zainstaluj zależności: 25Zainstaluj zależności:
26 26
27* meson \* 27* meson \*
28* [wlroots](https://github.com/swaywm/wlroots) 28* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
29* wayland 29* wayland
30* wayland-protocols \* 30* wayland-protocols \*
31* pcre 31* pcre2
32* json-c 32* json-c
33* pango 33* pango
34* cairo 34* cairo
@@ -44,12 +44,6 @@ Wykonaj następujące polecenia:
44 ninja -C build 44 ninja -C build
45 sudo ninja -C build install 45 sudo ninja -C build install
46 46
47Na systemach bez logind należy wykonać polecenie suid na pliku wykonywalnym sway:
48
49 sudo chmod a+s /usr/local/bin/sway
50
51Sway pozbędzie się uprawnień roota tuż po wystartowaniu.
52
53## Konfiguracja 47## Konfiguracja
54 48
55Jeśli już korzystasz z i3, skopiuj swoją konfigurację i3 do katalogu `~/.config/sway/config` i 49Jeśli już korzystasz z i3, skopiuj swoją konfigurację i3 do katalogu `~/.config/sway/config` i
diff --git a/README.pt.md b/README.pt.md
index ad7cab65..c1611a31 100644
--- a/README.pt.md
+++ b/README.pt.md
@@ -2,12 +2,12 @@
2 2
3O sway é um compositor do [Wayland](http://wayland.freedesktop.org/) compatível com o [i3](https://i3wm.org/). 3O sway é um compositor do [Wayland](http://wayland.freedesktop.org/) compatível com o [i3](https://i3wm.org/).
4Leia o [FAQ](https://github.com/swaywm/sway/wiki). Junte-se ao [canal do 4Leia o [FAQ](https://github.com/swaywm/sway/wiki). Junte-se ao [canal do
5IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway em 5IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway em
6irc.freenode.net). 6irc.libera.chat).
7 7
8## Assinatura das versões 8## Assinatura das versões
9 9
10As versões são assinadas com [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 10As versões são assinadas com [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
11e publicadas [no GitHub](https://github.com/swaywm/sway/releases). 11e publicadas [no GitHub](https://github.com/swaywm/sway/releases).
12 12
13## Instalação 13## Instalação
@@ -27,10 +27,10 @@ Verifique [essa página da wiki](https://github.com/swaywm/sway/wiki/Development
27Instale as dependências: 27Instale as dependências:
28 28
29* meson \* 29* meson \*
30* [wlroots](https://github.com/swaywm/wlroots) 30* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
31* wayland 31* wayland
32* wayland-protocols \* 32* wayland-protocols \*
33* pcre 33* pcre2
34* json-c 34* json-c
35* pango 35* pango
36* cairo 36* cairo
@@ -46,12 +46,6 @@ Execute esses comandos:
46 ninja -C build 46 ninja -C build
47 sudo ninja -C build install 47 sudo ninja -C build install
48 48
49Em sistemas sem logind, você precisa preparar o binário do sway:
50
51 sudo chmod a+s /usr/local/bin/sway
52
53O sway perderá as privilégios de de root logo após o início do sistema.
54
55## Configuração 49## Configuração
56 50
57Se você já utiliza o i3, então copie os seus arquivos de configuração para `~/.config/sway/config` e 51Se você já utiliza o i3, então copie os seus arquivos de configuração para `~/.config/sway/config` e
diff --git a/README.ro.md b/README.ro.md
index dd895b56..a3559a8b 100644
--- a/README.ro.md
+++ b/README.ro.md
@@ -1,11 +1,11 @@
1# sway 1# sway
2 2
3sway este un compositor pentru [Wayland](http://wayland.freedesktop.org/) compatibil cu [i3](https://i3wm.org/). 3sway este un compositor pentru [Wayland](http://wayland.freedesktop.org/) compatibil cu [i3](https://i3wm.org/).
4Citiți [FAQ](https://github.com/swaywm/sway/wiki)-ul. Connectați-vă la canalul nostru [IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway pe irc.freenode.net). 4Citiți [FAQ](https://github.com/swaywm/sway/wiki)-ul. Connectați-vă la canalul nostru [IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway pe irc.libera.chat).
5 5
6## Semnarea digitală 6## Semnarea digitală
7 7
8Noile versiuni sunt semnate cu [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 8Noile versiuni sunt semnate cu [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
9și postate [pe GitHub](https://github.com/swaywm/sway/releases). 9și postate [pe GitHub](https://github.com/swaywm/sway/releases).
10 10
11## Instalare 11## Instalare
@@ -22,10 +22,10 @@ Dacă sunteți interesați in a crea pachete pentru distribuția voastră, infor
22Dependențe pentru instalare: 22Dependențe pentru instalare:
23 23
24* meson \* 24* meson \*
25* [wlroots](https://github.com/swaywm/wlroots) 25* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
26* wayland 26* wayland
27* wayland-protocols \* 27* wayland-protocols \*
28* pcre 28* pcre2
29* json-c 29* json-c
30* pango 30* pango
31* cairo 31* cairo
@@ -43,14 +43,6 @@ Rulați aceste comenzi:
43 sudo ninja -C build install 43 sudo ninja -C build install
44``` 44```
45 45
46Pe sisteme fără logind, trebuie să folosiți următoarea comandă pentru a marca binarul de Sway ca suid:
47
48```
49 sudo chmod a+s /usr/local/bin/sway
50```
51
52Imediat după pornire, Sway va renunța la permisiunile de root.
53
54## Configurare 46## Configurare
55 47
56Dacă folosiți deja i3, copiați fișierul de configurare din i3 în `~/.config/sway/config`, și va funcționa fără a necesita nici o modificare. In caz contrar, copiați exemplul de configurare (disponibil de obicei în `/etc/sway/config`) în `~/.config/sway/config`. 48Dacă folosiți deja i3, copiați fișierul de configurare din i3 în `~/.config/sway/config`, și va funcționa fără a necesita nici o modificare. In caz contrar, copiați exemplul de configurare (disponibil de obicei în `/etc/sway/config`) în `~/.config/sway/config`.
diff --git a/README.ru.md b/README.ru.md
index a870ec89..edc0eda7 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -1,42 +1,41 @@
1# sway 1# sway
2 2
3sway - Ñто [i3](https://i3wm.org/)-ÑовмеÑтимый композитор [Wayland](http://wayland.freedesktop.org/). 3sway - Ñто [i3]-ÑовмеÑтимый композитор [Wayland].
4Больше информации в [FAQ](https://github.com/swaywm/sway/wiki). ПриÑоединÑйтеÑÑŒ к 4Больше информации в [FAQ]. ПриÑоединÑйтеÑÑŒ к
5[IRC-каналу](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на 5[IRC-каналу][IRC channel] (#sway на
6irc.freenode.net). 6irc.libera.chat).
7 7
8## ПодпиÑи релизов 8## ПодпиÑи релизов
9 9
10Релизы подпиÑываютÑÑ ÐºÐ»ÑŽÑ‡Ð¾Ð¼ [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 10Релизы подпиÑываютÑÑ ÐºÐ»ÑŽÑ‡Ð¾Ð¼ [E88F5E48] и публикуютÑÑ [на GitHub][GitHub releases].
11и публикуютÑÑ [на GitHub](https://github.com/swaywm/sway/releases).
12 11
13## УÑтановка 12## УÑтановка
14 13
15### Из репозиториев 14### Из репозиториев
16 15
17sway доÑтупен во многих диÑтрибутивах. Попробуйте уÑтановить пакет "sway". 16Sway доÑтупен во многих диÑтрибутивах. Попробуйте уÑтановить пакет "sway".
18ЕÑли он вдруг недоÑтупен, проверьте [Ñту Ñтраницу на wiki](https://github.com/swaywm/sway/wiki/Unsupported-packages)
19Ð´Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ о подробноÑÑ‚ÑÑ… уÑтановки Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾
20диÑтрибутива.
21 17
22ЕÑли вы заинтереÑованы поддерживать sway в вашем диÑтрибутиве, заглÑните в наш IRC-канал 18ЕÑли Ð²Ð°Ñ Ð¸Ð½Ñ‚ÐµÑ€ÐµÑует Ñоздание пакета sway Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ диÑтрибутива, зайдите на [IRC-канал][IRC channel]
23или обратитеÑÑŒ на sir@cmpwn.com за Ñоветом. 19или отправьте пиÑьмо на sir@cmpwn.com за Ñоветом.
24 20
25### Сборка из иÑходников 21### Сборка из иÑходников
26 22
23ПоÑетите [Ñту Ñтраницу на вики][Development setup], еÑли вы хотите поÑтроить поÑледнюю верÑию
24sway и wlroots Ð´Ð»Ñ Ñ‚ÐµÑÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸Ð»Ð¸ разработки.
25
27УÑтановите завиÑимоÑти: 26УÑтановите завиÑимоÑти:
28 27
29* meson \* 28* meson \*
30* [wlroots](https://github.com/swaywm/wlroots) 29* [wlroots]
31* wayland 30* wayland
32* wayland-protocols \* 31* wayland-protocols \*
33* pcre 32* pcre2
34* json-c 33* json-c
35* pango 34* pango
36* cairo 35* cairo
37* gdk-pixbuf2 (необÑзательно: Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ треÑ) 36* gdk-pixbuf2 (опÑионально: Ð´Ð»Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ треÑ)
38* [scdoc](https://git.sr.ht/~sircmpwn/scdoc)½ÐµÐ¾Ð±Ñзательно: Ð´Ð»Ñ Ñборки man-Ñтраниц) \* 37* [scdoc] (опÑионально: Ð´Ð»Ñ man-Ñтраниц) \*
39* git \* 38* git (опционально: Ð´Ð»Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸ о верÑии) \*
40 39
41_\*ЗавиÑимоÑти Ð´Ð»Ñ Ñборки_ 40_\*ЗавиÑимоÑти Ð´Ð»Ñ Ñборки_
42 41
@@ -46,12 +45,6 @@ _\*ЗавиÑимоÑти Ð´Ð»Ñ Ñборки_
46 ninja -C build 45 ninja -C build
47 sudo ninja -C build install 46 sudo ninja -C build install
48 47
49Ðа ÑиÑтемах без logind вам понадобитÑÑ Ð´Ð¾Ð±Ð°Ð²Ð¸Ñ‚ÑŒ suid к файлу программы sway:
50
51 sudo chmod a+s /usr/local/bin/sway
52
53sway ÑброÑит root-права при запуÑке.
54
55## ÐаÑтройка 48## ÐаÑтройка
56 49
57ЕÑли вы уже иÑпользуете i3, Ñкопируйте ваш конфигурационный файл i3 в `~/.config/sway/config`, и 50ЕÑли вы уже иÑпользуете i3, Ñкопируйте ваш конфигурационный файл i3 в `~/.config/sway/config`, и
@@ -63,3 +56,13 @@ sway ÑброÑит root-права при запуÑке.
63 56
64Выполните команду `sway` прÑмо из TTY. Ðекоторые диÑплейные менеджеры могут работать, но они не поддерживаютÑÑ Ñо Ñтороны 57Выполните команду `sway` прÑмо из TTY. Ðекоторые диÑплейные менеджеры могут работать, но они не поддерживаютÑÑ Ñо Ñтороны
65sway (gdm работает довольно неплохо). 58sway (gdm работает довольно неплохо).
59
60[i3]: https://i3wm.org/
61[Wayland]: http://wayland.freedesktop.org/
62[FAQ]: https://github.com/swaywm/sway/wiki
63[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
64[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
65[GitHub releases]: https://github.com/swaywm/sway/releases
66[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
67[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
68[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.sv.md b/README.sv.md
new file mode 100644
index 00000000..c50ca068
--- /dev/null
+++ b/README.sv.md
@@ -0,0 +1,83 @@
1# sway
2
3[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]
4
5sway är en [i3]-kompatibel [Wayland] compositor. Läs våran [FAQ]-sida. Gå med i vår
6[IRC-kanal] \(#sway på irc.libera.chat).
7
8## Utgåvosignaturer
9
10Utgåvor är signerade med [E88F5E48] och publicerade på [GitHub][GitHub releases].
11
12## Installering
13
14### Med pakethanterare
15
16Sway är tillgänglig i många distributioner. Prova att installera "sway" med din distributions pakethanterare.
17
18### Genom att kompilera från källkod
19
20Kolla in [denna wiki-sida][Development setup] om du vill bygga sway och wlroots HEAD för testning eller utveckling.
21
22Installera paket som sway behöver:
23
24* meson \*
25* [wlroots]
26* wayland
27* wayland-protocols \*
28* pcre2
29* json-c
30* pango
31* cairo
32* gdk-pixbuf2 (valbar: systembricka)
33* [scdoc] (valbar: manualer) \*
34* git (valbar: versioninfo) \*
35
36_\* Krav för kompilering_
37
38Kör dessa kommandon:
39
40 meson build/
41 ninja -C build/
42 sudo ninja -C build/ install
43
44## Konfiguration
45
46Ifall du redan använder i3 så kan du kopiera din konfigurationsfil till `~/.config/sway/config` och det kommer då att fungera som det ska.
47Kopiera annars exemplarkonfigurationsfilen till `~/.config/sway/config`. Den ligger oftast i `/etc/sway/config`.
48Kör `man 5 sway` för mer information kring konfigurationen.
49
50## Att köra sway
51
52Kör `sway` från en TTY. Vissa inloggningahanterare kan fungera men inte vara stödda av sway (gdm ska fungera hyfsat bra).
53
54[en]: https://github.com/swaywm/sway#readme
55[de]: README.de.md
56[dk]: README.dk.md
57[es]: README.es.md
58[fr]: README.fr.md
59[sv]: README.sv.md
60[gr]: README.gr.md
61[hu]: README.hu.md
62[ir]: README.ir.md
63[it]: README.it.md
64[ja]: README.ja.md
65[ko]: README.ko.md
66[nl]: README.nl.md
67[pl]: README.pl.md
68[pt]: README.pt.md
69[ro]: README.ro.md
70[ru]: README.ru.md
71[tr]: README.tr.md
72[uk]: README.uk.md
73[zh-CN]: README.zh-CN.md
74[zh-TW]: README.zh-TW.md
75[i3]: https://i3wm.org/
76[Wayland]: http://wayland.freedesktop.org/
77[FAQ]: https://github.com/swaywm/sway/wiki
78[IRC-kanal]: https://web.libera.chat/gamja/?channels=#sway
79[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
80[GitHub releases]: https://github.com/swaywm/sway/releases
81[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
82[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
83[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.tr.md b/README.tr.md
new file mode 100644
index 00000000..40de1474
--- /dev/null
+++ b/README.tr.md
@@ -0,0 +1,62 @@
1# sway
2
3
4Sway, [i3]-uyumlu bir [Wayland] dizgicisidir. [SSS][FAQ]'yi okuyun.
5[IRC kanalı][IRC channel]na katılın \(irc.libera.chat'te #sway (İngilizce)).
6
7## Sürüm imzaları
8
9Sürümler [E88F5E48] ile imzalandı ve [GitHub][GitHub releases]'da yayınlandı.
10
11## Kurulum
12
13### Paketler ile
14
15Sway birçok dağıtımda mevcuttur. Sizinki için "sway" paketini yüklemeyi deneyin.
16
17Dağıtımınız için sway'i paketlemekle ilgileniyorsanız, IRC kanalına uğrayın veya tavsiye için sir@cmpwn.com adresine bir e-posta gönderin.
18
19### Kaynak koddan derleme
20
21Test veya geliştirme için sway ve wlroots'un HEAD'ini oluşturmak istiyorsanız [bu wiki sayfası][Development setup]na göz atın.
22
23Aşağıdaki bağımlılıkları yükleyin:
24
25* meson \*
26* [wlroots]
27* wayland
28* wayland-protocols \*
29* pcre2
30* json-c
31* pango
32* cairo
33* gdk-pixbuf2 (isteğe bağlı: system tray)
34* [scdoc] (isteğe bağlı: man pages) \*
35* git (isteğe bağlı: version info) \*
36
37_\*Derleme-anı bağımlılıkları_
38
39Şu komutları çalıştırın:
40
41 meson build
42 ninja -C build
43 sudo ninja -C build install
44
45## Yapılandırma
46
47Zaten i3 kullanıyorsanız, i3 yapılandırmanızı `~/.config/sway/config` konumuna kopyalayın ve kutudan çıktığı gibi çalışacaktır. Aksi takdirde, örnek yapılandırma dosyasını `~/.config/sway/config` konumuna kopyalayın. Genellikle `/etc/sway/config` konumunda bulunur.
48Yapılandırma hakkında bilgi almak için `man 5 sway` komutunu çalıştırın.
49
50## Çalıştırma
51
52TTY'den `sway` çalıştırın. Bazı görüntü yöneticileriyle(display manager) çalışabilir ama Sway tarafından desteklenmez. (gdm'nin oldukça iyi çalıştığı bilinmektedir.)
53
54[i3]: https://i3wm.org/
55[Wayland]: http://wayland.freedesktop.org/
56[FAQ]: https://github.com/swaywm/sway/wiki
57[IRC channel]: https://web.libera.chat/gamja/?channels=#sway
58[E88F5E48]: https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48
59[GitHub releases]: https://github.com/swaywm/sway/releases
60[Development setup]: https://github.com/swaywm/sway/wiki/Development-Setup
61[wlroots]: https://gitlab.freedesktop.org/wlroots/wlroots
62[scdoc]: https://git.sr.ht/~sircmpwn/scdoc
diff --git a/README.uk.md b/README.uk.md
index 95047cb8..33359cff 100644
--- a/README.uk.md
+++ b/README.uk.md
@@ -2,8 +2,8 @@
2 2
3Sway це ÑуміÑний з [i3](https://i3wm.org/) композитор [Wayland](http://wayland.freedesktop.org/). 3Sway це ÑуміÑний з [i3](https://i3wm.org/) композитор [Wayland](http://wayland.freedesktop.org/).
4ОзнайомтеÑÑŒ з [ЧаПами](https://github.com/swaywm/sway/wiki). ПриєднуйтеÑÑŒ до [Ñпільноти в 4ОзнайомтеÑÑŒ з [ЧаПами](https://github.com/swaywm/sway/wiki). ПриєднуйтеÑÑŒ до [Ñпільноти в
5IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на 5IRC](https://web.libera.chat/gamja/?channels=#sway) (#sway на
6irc.freenode.net). 6irc.libera.chat).
7 7
8## Підтримка українÑькою мовою 8## Підтримка українÑькою мовою
9 9
@@ -15,7 +15,7 @@ Hummer12007 у IRC-Ñпільноті. Будьте терплÑчі, вам оÐ
15 15
16## ПідпиÑи випуÑків 16## ПідпиÑи випуÑків
17 17
18ВипуÑки підпиÑані ключем [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 18ВипуÑки підпиÑані ключем [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48)
19та публікуютьÑÑ Ð½Ð° Ñторінці [GitHub](https://github.com/swaywm/sway/releases). 19та публікуютьÑÑ Ð½Ð° Ñторінці [GitHub](https://github.com/swaywm/sway/releases).
20 20
21## Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ 21## Ð’ÑтановленнÑ
@@ -36,10 +36,10 @@ Sway доÑтупний у багатьох диÑтрибутивах Linux (а
36Ð’Ñтановіть залежноÑÑ‚Ñ–: 36Ð’Ñтановіть залежноÑÑ‚Ñ–:
37 37
38* meson \* 38* meson \*
39* [wlroots](https://github.com/swaywm/wlroots) 39* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
40* wayland 40* wayland
41* wayland-protocols \* 41* wayland-protocols \*
42* pcre 42* pcre2
43* json-c 43* json-c
44* pango 44* pango
45* cairo 45* cairo
@@ -55,12 +55,6 @@ _\*Лише Ð´Ð»Ñ ÐºÐ¾Ð¼Ð¿Ñ–Ð»Ñції_
55 ninja -C build 55 ninja -C build
56 sudo ninja -C build install 56 sudo ninja -C build install
57 57
58Ðа ÑиÑтемах без logind, необхідно вÑтановити біт SUID на виконуваний файл sway:
59
60 sudo chmod a+s /usr/local/bin/sway
61
62Sway втратить права доÑтупу root незабаром піÑÐ»Ñ Ð·Ð°Ð¿ÑƒÑку.
63
64## ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ 58## ÐалаштуваннÑ
65 59
66Якщо ви вже викориÑтовуєте i3, Ñкопіюйте Ñвій файл налаштувань 60Якщо ви вже викориÑтовуєте i3, Ñкопіюйте Ñвій файл налаштувань
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 9a3337ce..a6f4518a 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,63 +1,50 @@
1# sway 1# sway
2 2
3sway 是和 [i3](https://i3wm.org/) 兼容的 [Wayland](http://wayland.freedesktop.org/) compositor. 3sway 是和 [i3](https://i3wm.org/) 兼容的 [Wayland](http://wayland.freedesktop.org/) compositor。
4阅读 [FAQ](https://github.com/swaywm/sway/wiki). 加入 [IRC 4 [查看FAQ](https://github.com/swaywm/sway/wiki)/ [加入IRC频é“](https://web.libera.chat/gamja/?channels=#sway) (#sway on irc.libera.chat)
5频é“](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on
6irc.freenode.net).
7 5
8## å‘布签å 6## å‘行签å
9 7
10å‘布是以 [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) ç­¾å 8æ¯ä¸ªå‘行版都以 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 的密钥签å并å‘布在 [GitHub](https://github.com/swaywm/sway/releases)上。
11并å‘布在 [GitHub](https://github.com/swaywm/sway/releases).
12 9
13## 安装 10## 安装
14 11
15### 从软件包中 12### 从包管ç†å™¨å®‰è£…
16 13
17Sway 在很多å‘行版中å¯ç”¨. å°è¯•åœ¨ä½ çš„å‘行版中安装 "sway" 包. 14Sway 在很多å‘行版中å¯ç”¨ã€‚请å°è¯•åœ¨ä½ çš„å‘行版中安装 `sway` 。
18如果这ä¸å¯ç”¨, 请到 [æ­¤ wiki 页](https://github.com/swaywm/sway/wiki/Unsupported-packages)
19检查针对你的å‘行版关于安装的信æ¯.
20 15
21如果你有兴趣给你的å‘行版打包 sway, åœä¸‹æ¥åˆ° IRC 频é“或者å邮件至 sir@cmpwn.com 获å–建议. 16### 从æºç ç¼–è¯
22 17
23### 从æºä»£ç ç¼–译 18如果想è¦æž„建最新版swayå’Œwlroots用以测试和开å‘,请查看 [æ­¤wiki页é¢](https://github.com/swaywm/sway/wiki/Development-Setup)
24 19
25安装ä¾èµ–: 20安装如下ä¾èµ–:
26 21
27* meson \* 22* meson \*
28* [wlroots](https://github.com/swaywm/wlroots) 23* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
29* wayland 24* wayland
30* wayland-protocols \* 25* wayland-protocols \*
31* pcre 26* pcre2
32* json-c 27* json-c
33* pango 28* pango
34* cairo 29* cairo
35* gdk-pixbuf2 (å¯é€‰çš„: system tray) 30* gdk-pixbuf2 (å¯é€‰çš„: system tray)
36* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (å¯é€‰çš„: man pages) \* 31* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (å¯é€‰: man pages) \*
37* git \* 32* git \*
38 33
39_\*编译时ä¾èµ–_ 34_\*编译时ä¾èµ–_
40 35
41è¿è¡Œè¿™äº›å‘½ä»¤: 36è¿è¡Œå¦‚下命令:
42 37
43 meson build 38 meson build/
44 ninja -C build 39 ninja -C build/
45 sudo ninja -C build install 40 sudo ninja -C build/ install
46
47在没有 logind 的系统上, 你需è¦ç»™ sway 二进制设置 suid:
48
49 sudo chmod a+s /usr/local/bin/sway
50
51Sway 将会在å¯åŠ¨åŽå°½å¿«ä¸¢æŽ‰ root æƒé™.
52 41
53## é…ç½® 42## é…ç½®
54 43
55如果你已ç»åœ¨ä½¿ç”¨ i3, 接下æ¥å¤åˆ¶ä½ çš„ i3 é…置到 `~/.config/sway/config` 44如果你已ç»åœ¨ä½¿ç”¨i3,直接å¤åˆ¶i3é…置文件到 `~/.config/sway/config`,这是开箱å³ç”¨çš„。或者,你å¯ä»¥å¤åˆ¶é…置样例到`~/.config/sway/config`。它通常ä½äºŽ `/etc/sway/config`。
56它å¯ä»¥ç›´æŽ¥å·¥ä½œ. 或者, å¤åˆ¶æ ·æœ¬é…置文件到 45è¿è¡Œ `man 5 sway` 获å–关于é…置的更多信æ¯ã€‚
57`~/.config/sway/config`. 它通常ä½äºŽ `/etc/sway/config`.
58è¿è¡Œ `man 5 sway` 获å–关于é…置的信æ¯.
59 46
60## è¿è¡Œ 47## è¿è¡Œ
61 48
62从 TTY 中è¿è¡Œ `sway` . æŸäº›æ˜¾ç¤ºç®¡ç†å™¨å¯èƒ½ä¼šå·¥ä½œä½†å¹¶ä¸è¢« sway æ”¯æŒ 49从 TTY 中è¿è¡Œ `sway`。 æŸäº›æ˜¾ç¤ºç®¡ç†å™¨ï¼ˆDisplay Manager)也许å¯ä»¥å·¥ä½œä½†ä¸è¢« sway 支æŒã€‚
63(已知的 gdm 工作得éžå¸¸å¥½). 50(已知 gdm 工作得éžå¸¸å¥½)。
diff --git a/README.zh-TW.md b/README.zh-TW.md
index 13a9d2f4..2de2f63f 100644
--- a/README.zh-TW.md
+++ b/README.zh-TW.md
@@ -2,12 +2,12 @@
2 2
3sway 是一個與 [i3](https://i3wm.org/) 相容的 [Wayland](http://wayland.freedesktop.org/) compositor。 3sway 是一個與 [i3](https://i3wm.org/) 相容的 [Wayland](http://wayland.freedesktop.org/) compositor。
4閱讀 [FAQ](https://github.com/swaywm/sway/wiki)。 加入 [IRC 4閱讀 [FAQ](https://github.com/swaywm/sway/wiki)。 加入 [IRC
5é »é“](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on 5é »é“](https://web.libera.chat/gamja/?channels=#sway) (#sway on
6irc.freenode.net) 6irc.libera.chat)
7 7
8## 發行簽章 8## 發行簽章
9 9
10所有發行的版本都會以 [B22DA89A](http://pgp.mit.edu/pks/lookup?op=vindex&search=0x52CB6609B22DA89A) 簽署 10所有發行的版本都會以 [E88F5E48](https://keys.openpgp.org/search?q=34FF9526CFEF0E97A340E2E40FDE7BE0E88F5E48) 簽署
11並發佈於 [GitHub](https://github.com/swaywm/sway/releases) 11並發佈於 [GitHub](https://github.com/swaywm/sway/releases)
12 12
13## å®‰è£ 13## 安è£
@@ -25,10 +25,10 @@ Sway 在許多發行版都有æä¾›ã€‚è«‹è‡ªå·±å˜—è©¦æ–¼ä½ çš„ç™¼è¡Œç‰ˆå®‰è£ ã€
25相ä¾å¥—件: 25相ä¾å¥—件:
26 26
27* meson \* 27* meson \*
28* [wlroots](https://github.com/swaywm/wlroots) 28* [wlroots](https://gitlab.freedesktop.org/wlroots/wlroots)
29* wayland 29* wayland
30* wayland-protocols \* 30* wayland-protocols \*
31* pcre 31* pcre2
32* json-c 32* json-c
33* pango 33* pango
34* cairo 34* cairo
@@ -44,12 +44,6 @@ _\*編譯時相ä¾_
44 ninja -C build 44 ninja -C build
45 sudo ninja -C build install 45 sudo ninja -C build install
46 46
47在沒有 logind 的系統上,你需è¦ç‚º sway 的執行檔加上 suid。
48
49 sudo chmod a+s /usr/local/bin/sway
50
51Sway 在啟動ä¸ä¹…後就會放棄 root 權é™ã€‚
52
53## 設定檔 47## 設定檔
54 48
55如果你已經在使用 i3,你å¯ä»¥ç›´æŽ¥å°‡ä½ çš„ i3 設定檔複製到 `~/.config/sway/config` 然後就能直接使用。 49如果你已經在使用 i3,你å¯ä»¥ç›´æŽ¥å°‡ä½ çš„ i3 設定檔複製到 `~/.config/sway/config` 然後就能直接使用。
diff --git a/client/pool-buffer.c b/client/pool-buffer.c
index fd500c49..3546b897 100644
--- a/client/pool-buffer.c
+++ b/client/pool-buffer.c
@@ -1,50 +1,43 @@
1#define _POSIX_C_SOURCE 200809 1#define _POSIX_C_SOURCE 200809
2#include <assert.h> 2#include <assert.h>
3#include <cairo/cairo.h> 3#include <cairo.h>
4#include <errno.h>
4#include <fcntl.h> 5#include <fcntl.h>
5#include <pango/pangocairo.h> 6#include <pango/pangocairo.h>
6#include <stdio.h> 7#include <stdio.h>
7#include <stdlib.h> 8#include <stdlib.h>
8#include <string.h> 9#include <string.h>
9#include <sys/mman.h> 10#include <sys/mman.h>
11#include <time.h>
10#include <unistd.h> 12#include <unistd.h>
11#include <wayland-client.h> 13#include <wayland-client.h>
12#include "config.h" 14#include "config.h"
13#include "pool-buffer.h" 15#include "pool-buffer.h"
14#include "util.h" 16#include "util.h"
15 17
16static int create_pool_file(size_t size, char **name) { 18static int anonymous_shm_open(void) {
17 static const char template[] = "sway-client-XXXXXX"; 19 int retries = 100;
18 const char *path = getenv("XDG_RUNTIME_DIR"); 20
19 if (path == NULL) { 21 do {
20 fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); 22 // try a probably-unique name
21 return -1; 23 struct timespec ts;
22 } 24 clock_gettime(CLOCK_MONOTONIC, &ts);
23 25 pid_t pid = getpid();
24 size_t name_size = strlen(template) + 1 + strlen(path) + 1; 26 char name[50];
25 *name = malloc(name_size); 27 snprintf(name, sizeof(name), "/sway-%x-%x",
26 if (*name == NULL) { 28 (unsigned int)pid, (unsigned int)ts.tv_nsec);
27 fprintf(stderr, "allocation failed\n"); 29
28 return -1; 30 // shm_open guarantees that O_CLOEXEC is set
29 } 31 int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
30 snprintf(*name, name_size, "%s/%s", path, template); 32 if (fd >= 0) {
31 33 shm_unlink(name);
32 int fd = mkstemp(*name); 34 return fd;
33 if (fd < 0) { 35 }
34 return -1;
35 }
36
37 if (!sway_set_cloexec(fd, true)) {
38 close(fd);
39 return -1;
40 }
41 36
42 if (ftruncate(fd, size) < 0) { 37 --retries;
43 close(fd); 38 } while (retries > 0 && errno == EEXIST);
44 return -1;
45 }
46 39
47 return fd; 40 return -1;
48} 41}
49 42
50static void buffer_release(void *data, struct wl_buffer *wl_buffer) { 43static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
@@ -62,17 +55,20 @@ static struct pool_buffer *create_buffer(struct wl_shm *shm,
62 uint32_t stride = width * 4; 55 uint32_t stride = width * 4;
63 size_t size = stride * height; 56 size_t size = stride * height;
64 57
65 char *name; 58 int fd = anonymous_shm_open();
66 int fd = create_pool_file(size, &name); 59 if (fd == -1) {
67 assert(fd != -1); 60 return NULL;
61 }
62 if (ftruncate(fd, size) < 0) {
63 close(fd);
64 return NULL;
65 }
68 void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 66 void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
69 struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); 67 struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
70 buf->buffer = wl_shm_pool_create_buffer(pool, 0, 68 buf->buffer = wl_shm_pool_create_buffer(pool, 0,
71 width, height, stride, format); 69 width, height, stride, format);
72 wl_shm_pool_destroy(pool); 70 wl_shm_pool_destroy(pool);
73 close(fd); 71 close(fd);
74 unlink(name);
75 free(name);
76 72
77 buf->size = size; 73 buf->size = size;
78 buf->width = width; 74 buf->width = width;
diff --git a/common/cairo.c b/common/cairo.c
index 403dcf49..7c59d48c 100644
--- a/common/cairo.c
+++ b/common/cairo.c
@@ -1,6 +1,6 @@
1#include <stdint.h> 1#include <stdint.h>
2#include <cairo/cairo.h> 2#include <cairo.h>
3#include "cairo.h" 3#include "cairo_util.h"
4 4
5void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { 5void cairo_set_source_u32(cairo_t *cairo, uint32_t color) {
6 cairo_set_source_rgba(cairo, 6 cairo_set_source_rgba(cairo,
diff --git a/common/gesture.c b/common/gesture.c
new file mode 100644
index 00000000..58170443
--- /dev/null
+++ b/common/gesture.c
@@ -0,0 +1,333 @@
1#define _POSIX_C_SOURCE 200809L
2#include "gesture.h"
3
4#include <math.h>
5#include <stdarg.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include "list.h"
10#include "log.h"
11#include "stringop.h"
12
13const uint8_t GESTURE_FINGERS_ANY = 0;
14
15char *gesture_parse(const char *input, struct gesture *output) {
16 // Clear output in case of failure
17 output->type = GESTURE_TYPE_NONE;
18 output->fingers = GESTURE_FINGERS_ANY;
19 output->directions = GESTURE_DIRECTION_NONE;
20
21 // Split input type, fingers and directions
22 list_t *split = split_string(input, ":");
23 if (split->length < 1 || split->length > 3) {
24 return format_str(
25 "expected <gesture>[:<fingers>][:direction], got %s",
26 input);
27 }
28
29 // Parse gesture type
30 if (strcmp(split->items[0], "hold") == 0) {
31 output->type = GESTURE_TYPE_HOLD;
32 } else if (strcmp(split->items[0], "pinch") == 0) {
33 output->type = GESTURE_TYPE_PINCH;
34 } else if (strcmp(split->items[0], "swipe") == 0) {
35 output->type = GESTURE_TYPE_SWIPE;
36 } else {
37 return format_str("expected hold|pinch|swipe, got %s",
38 (const char *)split->items[0]);
39 }
40
41 // Parse optional arguments
42 if (split->length > 1) {
43 char *next = split->items[1];
44
45 // Try to parse as finger count (1-9)
46 if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
47 output->fingers = atoi(next);
48
49 // Move to next if available
50 next = split->length == 3 ? split->items[2] : NULL;
51 } else if (split->length == 3) {
52 // Fail here if argument can only be finger count
53 return format_str("expected 1-9, got %s", next);
54 }
55
56 // If there is an argument left, try to parse as direction
57 if (next) {
58 list_t *directions = split_string(next, "+");
59
60 for (int i = 0; i < directions->length; ++i) {
61 const char *item = directions->items[i];
62 if (strcmp(item, "any") == 0) {
63 continue;
64 } else if (strcmp(item, "up") == 0) {
65 output->directions |= GESTURE_DIRECTION_UP;
66 } else if (strcmp(item, "down") == 0) {
67 output->directions |= GESTURE_DIRECTION_DOWN;
68 } else if (strcmp(item, "left") == 0) {
69 output->directions |= GESTURE_DIRECTION_LEFT;
70 } else if (strcmp(item, "right") == 0) {
71 output->directions |= GESTURE_DIRECTION_RIGHT;
72 } else if (strcmp(item, "inward") == 0) {
73 output->directions |= GESTURE_DIRECTION_INWARD;
74 } else if (strcmp(item, "outward") == 0) {
75 output->directions |= GESTURE_DIRECTION_OUTWARD;
76 } else if (strcmp(item, "clockwise") == 0) {
77 output->directions |= GESTURE_DIRECTION_CLOCKWISE;
78 } else if (strcmp(item, "counterclockwise") == 0) {
79 output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
80 } else {
81 return format_str("expected direction, got %s", item);
82 }
83 }
84 list_free_items_and_destroy(directions);
85 }
86 } // if optional args
87
88 list_free_items_and_destroy(split);
89
90 return NULL;
91}
92
93const char *gesture_type_string(enum gesture_type type) {
94 switch (type) {
95 case GESTURE_TYPE_NONE:
96 return "none";
97 case GESTURE_TYPE_HOLD:
98 return "hold";
99 case GESTURE_TYPE_PINCH:
100 return "pinch";
101 case GESTURE_TYPE_SWIPE:
102 return "swipe";
103 }
104
105 return NULL;
106}
107
108const char *gesture_direction_string(enum gesture_direction direction) {
109 switch (direction) {
110 case GESTURE_DIRECTION_NONE:
111 return "none";
112 case GESTURE_DIRECTION_UP:
113 return "up";
114 case GESTURE_DIRECTION_DOWN:
115 return "down";
116 case GESTURE_DIRECTION_LEFT:
117 return "left";
118 case GESTURE_DIRECTION_RIGHT:
119 return "right";
120 case GESTURE_DIRECTION_INWARD:
121 return "inward";
122 case GESTURE_DIRECTION_OUTWARD:
123 return "outward";
124 case GESTURE_DIRECTION_CLOCKWISE:
125 return "clockwise";
126 case GESTURE_DIRECTION_COUNTERCLOCKWISE:
127 return "counterclockwise";
128 }
129
130 return NULL;
131}
132
133// Helper to turn combination of directions flags into string representation.
134static char *gesture_directions_to_string(uint32_t directions) {
135 char *result = NULL;
136
137 for (uint8_t bit = 0; bit < 32; bit++) {
138 uint32_t masked = directions & (1 << bit);
139 if (masked) {
140 const char *name = gesture_direction_string(masked);
141
142 if (!name) {
143 name = "unknown";
144 }
145
146 if (!result) {
147 result = strdup(name);
148 } else {
149 char *new = format_str("%s+%s", result, name);
150 free(result);
151 result = new;
152 }
153 }
154 }
155
156 if(!result) {
157 return strdup("any");
158 }
159
160 return result;
161}
162
163char *gesture_to_string(struct gesture *gesture) {
164 char *directions = gesture_directions_to_string(gesture->directions);
165 char *result = format_str("%s:%u:%s",
166 gesture_type_string(gesture->type),
167 gesture->fingers, directions);
168 free(directions);
169 return result;
170}
171
172bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
173 // Check that gesture type matches
174 if (target->type != type) {
175 return false;
176 }
177
178 // Check that finger count matches
179 if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
180 return false;
181 }
182
183 return true;
184}
185
186bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
187 // Check type and fingers
188 if (!gesture_check(target, to_match->type, to_match->fingers)) {
189 return false;
190 }
191
192 // Enforce exact matches ...
193 if (exact && target->directions != to_match->directions) {
194 return false;
195 }
196
197 // ... or ensure all target directions are matched
198 return (target->directions & to_match->directions) == target->directions;
199}
200
201bool gesture_equal(struct gesture *a, struct gesture *b) {
202 return a->type == b->type
203 && a->fingers == b->fingers
204 && a->directions == b->directions;
205}
206
207// Return count of set bits in directions bit field.
208static uint8_t gesture_directions_count(uint32_t directions) {
209 uint8_t count = 0;
210 for (; directions; directions >>= 1) {
211 count += directions & 1;
212 }
213 return count;
214}
215
216// Compare direction bit count of two direction.
217static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
218 return gesture_directions_count(a) - gesture_directions_count(b);
219}
220
221// Compare two direction based on their direction bit count
222int8_t gesture_compare(struct gesture *a, struct gesture *b) {
223 return gesture_directions_compare(a->directions, b->directions);
224}
225
226void gesture_tracker_begin(struct gesture_tracker *tracker,
227 enum gesture_type type, uint8_t fingers) {
228 tracker->type = type;
229 tracker->fingers = fingers;
230
231 tracker->dx = 0.0;
232 tracker->dy = 0.0;
233 tracker->scale = 1.0;
234 tracker->rotation = 0.0;
235
236 sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
237 gesture_type_string(type), fingers);
238}
239
240bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
241 return tracker->type == type;
242}
243
244void gesture_tracker_update(struct gesture_tracker *tracker,
245 double dx, double dy, double scale, double rotation) {
246 if (tracker->type == GESTURE_TYPE_HOLD) {
247 sway_assert(false, "hold does not update.");
248 return;
249 }
250
251 tracker->dx += dx;
252 tracker->dy += dy;
253
254 if (tracker->type == GESTURE_TYPE_PINCH) {
255 tracker->scale = scale;
256 tracker->rotation += rotation;
257 }
258
259 sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
260 gesture_type_string(tracker->type),
261 tracker->fingers,
262 tracker->dx, tracker->dy,
263 tracker->scale, tracker->rotation);
264}
265
266void gesture_tracker_cancel(struct gesture_tracker *tracker) {
267 sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
268 gesture_type_string(tracker->type), tracker->fingers);
269
270 tracker->type = GESTURE_TYPE_NONE;
271}
272
273struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
274 struct gesture *result = calloc(1, sizeof(struct gesture));
275
276 // Ignore gesture under some threshold
277 // TODO: Make configurable
278 const double min_rotation = 5;
279 const double min_scale_delta = 0.1;
280
281 // Determine direction
282 switch(tracker->type) {
283 // Gestures with scale and rotation
284 case GESTURE_TYPE_PINCH:
285 if (tracker->rotation > min_rotation) {
286 result->directions |= GESTURE_DIRECTION_CLOCKWISE;
287 }
288 if (tracker->rotation < -min_rotation) {
289 result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
290 }
291
292 if (tracker->scale > (1.0 + min_scale_delta)) {
293 result->directions |= GESTURE_DIRECTION_OUTWARD;
294 }
295 if (tracker->scale < (1.0 - min_scale_delta)) {
296 result->directions |= GESTURE_DIRECTION_INWARD;
297 }
298 __attribute__ ((fallthrough));
299 // Gestures with dx and dy
300 case GESTURE_TYPE_SWIPE:
301 if (fabs(tracker->dx) > fabs(tracker->dy)) {
302 if (tracker->dx > 0) {
303 result->directions |= GESTURE_DIRECTION_RIGHT;
304 } else {
305 result->directions |= GESTURE_DIRECTION_LEFT;
306 }
307 } else {
308 if (tracker->dy > 0) {
309 result->directions |= GESTURE_DIRECTION_DOWN;
310 } else {
311 result->directions |= GESTURE_DIRECTION_UP;
312 }
313 }
314 // Gesture without any direction
315 case GESTURE_TYPE_HOLD:
316 break;
317 // Not tracking any gesture
318 case GESTURE_TYPE_NONE:
319 sway_assert(false, "Not tracking any gesture.");
320 return result;
321 }
322
323 result->type = tracker->type;
324 result->fingers = tracker->fingers;
325
326 char *description = gesture_to_string(result);
327 sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
328 free(description);
329
330 tracker->type = GESTURE_TYPE_NONE;
331
332 return result;
333}
diff --git a/common/meson.build b/common/meson.build
index c653dd72..c0ce1f68 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -1,8 +1,8 @@
1lib_sway_common = static_library( 1lib_sway_common = static_library(
2 'sway-common', 2 'sway-common',
3 files( 3 files(
4 'background-image.c',
5 'cairo.c', 4 'cairo.c',
5 'gesture.c',
6 'ipc-client.c', 6 'ipc-client.c',
7 'log.c', 7 'log.c',
8 'loop.c', 8 'loop.c',
@@ -13,7 +13,6 @@ lib_sway_common = static_library(
13 ), 13 ),
14 dependencies: [ 14 dependencies: [
15 cairo, 15 cairo,
16 gdk_pixbuf,
17 pango, 16 pango,
18 pangocairo, 17 pangocairo,
19 wayland_client.partial_dependency(compile_args: true) 18 wayland_client.partial_dependency(compile_args: true)
diff --git a/common/pango.c b/common/pango.c
index fc3d0688..288569b3 100644
--- a/common/pango.c
+++ b/common/pango.c
@@ -1,4 +1,4 @@
1#include <cairo/cairo.h> 1#include <cairo.h>
2#include <pango/pangocairo.h> 2#include <pango/pangocairo.h>
3#include <stdarg.h> 3#include <stdarg.h>
4#include <stdbool.h> 4#include <stdbool.h>
@@ -6,7 +6,7 @@
6#include <stdio.h> 6#include <stdio.h>
7#include <stdlib.h> 7#include <stdlib.h>
8#include <string.h> 8#include <string.h>
9#include "cairo.h" 9#include "cairo_util.h"
10#include "log.h" 10#include "log.h"
11#include "stringop.h" 11#include "stringop.h"
12 12
@@ -50,7 +50,7 @@ size_t escape_markup_text(const char *src, char *dest) {
50 return length; 50 return length;
51} 51}
52 52
53PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, 53PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc,
54 const char *text, double scale, bool markup) { 54 const char *text, double scale, bool markup) {
55 PangoLayout *layout = pango_cairo_create_layout(cairo); 55 PangoLayout *layout = pango_cairo_create_layout(cairo);
56 PangoAttrList *attrs; 56 PangoAttrList *attrs;
@@ -73,60 +73,59 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
73 } 73 }
74 74
75 pango_attr_list_insert(attrs, pango_attr_scale_new(scale)); 75 pango_attr_list_insert(attrs, pango_attr_scale_new(scale));
76 PangoFontDescription *desc = pango_font_description_from_string(font);
77 pango_layout_set_font_description(layout, desc); 76 pango_layout_set_font_description(layout, desc);
78 pango_layout_set_single_paragraph_mode(layout, 1); 77 pango_layout_set_single_paragraph_mode(layout, 1);
79 pango_layout_set_attributes(layout, attrs); 78 pango_layout_set_attributes(layout, attrs);
80 pango_attr_list_unref(attrs); 79 pango_attr_list_unref(attrs);
81 pango_font_description_free(desc);
82 return layout; 80 return layout;
83} 81}
84 82
85void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, 83void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height,
86 int *baseline, double scale, bool markup, const char *fmt, ...) { 84 int *baseline, double scale, bool markup, const char *fmt, ...) {
87 va_list args; 85 va_list args;
88 va_start(args, fmt); 86 va_start(args, fmt);
89 // Add one since vsnprintf excludes null terminator. 87 char *buf = vformat_str(fmt, args);
90 int length = vsnprintf(NULL, 0, fmt, args) + 1;
91 va_end(args); 88 va_end(args);
92
93 char *buf = malloc(length);
94 if (buf == NULL) { 89 if (buf == NULL) {
95 sway_log(SWAY_ERROR, "Failed to allocate memory");
96 return; 90 return;
97 } 91 }
98 va_start(args, fmt);
99 vsnprintf(buf, length, fmt, args);
100 va_end(args);
101 92
102 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 93 PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup);
103 pango_cairo_update_layout(cairo, layout); 94 pango_cairo_update_layout(cairo, layout);
104 pango_layout_get_pixel_size(layout, width, height); 95 pango_layout_get_pixel_size(layout, width, height);
105 if (baseline) { 96 if (baseline) {
106 *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE; 97 *baseline = pango_layout_get_baseline(layout) / PANGO_SCALE;
107 } 98 }
108 g_object_unref(layout); 99 g_object_unref(layout);
100
109 free(buf); 101 free(buf);
110} 102}
111 103
112void pango_printf(cairo_t *cairo, const char *font, 104void get_text_metrics(const PangoFontDescription *description, int *height, int *baseline) {
105 cairo_t *cairo = cairo_create(NULL);
106 PangoContext *pango = pango_cairo_create_context(cairo);
107 // When passing NULL as a language, pango uses the current locale.
108 PangoFontMetrics *metrics = pango_context_get_metrics(pango, description, NULL);
109
110 *baseline = pango_font_metrics_get_ascent(metrics) / PANGO_SCALE;
111 *height = *baseline + pango_font_metrics_get_descent(metrics) / PANGO_SCALE;
112
113 pango_font_metrics_unref(metrics);
114 g_object_unref(pango);
115 cairo_destroy(cairo);
116}
117
118void render_text(cairo_t *cairo, const PangoFontDescription *desc,
113 double scale, bool markup, const char *fmt, ...) { 119 double scale, bool markup, const char *fmt, ...) {
114 va_list args; 120 va_list args;
115 va_start(args, fmt); 121 va_start(args, fmt);
116 // Add one since vsnprintf excludes null terminator. 122 char *buf = vformat_str(fmt, args);
117 int length = vsnprintf(NULL, 0, fmt, args) + 1;
118 va_end(args); 123 va_end(args);
119
120 char *buf = malloc(length);
121 if (buf == NULL) { 124 if (buf == NULL) {
122 sway_log(SWAY_ERROR, "Failed to allocate memory");
123 return; 125 return;
124 } 126 }
125 va_start(args, fmt);
126 vsnprintf(buf, length, fmt, args);
127 va_end(args);
128 127
129 PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); 128 PangoLayout *layout = get_pango_layout(cairo, desc, buf, scale, markup);
130 cairo_font_options_t *fo = cairo_font_options_create(); 129 cairo_font_options_t *fo = cairo_font_options_create();
131 cairo_get_font_options(cairo, fo); 130 cairo_get_font_options(cairo, fo);
132 pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); 131 pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo);
@@ -134,5 +133,6 @@ void pango_printf(cairo_t *cairo, const char *font,
134 pango_cairo_update_layout(cairo, layout); 133 pango_cairo_update_layout(cairo, layout);
135 pango_cairo_show_layout(cairo, layout); 134 pango_cairo_show_layout(cairo, layout);
136 g_object_unref(layout); 135 g_object_unref(layout);
136
137 free(buf); 137 free(buf);
138} 138}
diff --git a/common/stringop.c b/common/stringop.c
index 7fb3fe12..c503143a 100644
--- a/common/stringop.c
+++ b/common/stringop.c
@@ -1,5 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 2#include <ctype.h>
3#include <stdarg.h>
3#include <stdbool.h> 4#include <stdbool.h>
4#include <stdio.h> 5#include <stdio.h>
5#include <stdlib.h> 6#include <stdlib.h>
@@ -328,3 +329,35 @@ bool expand_path(char **path) {
328 wordfree(&p); 329 wordfree(&p);
329 return true; 330 return true;
330} 331}
332
333char *vformat_str(const char *fmt, va_list args) {
334 char *str = NULL;
335 va_list args_copy;
336 va_copy(args_copy, args);
337
338 int len = vsnprintf(NULL, 0, fmt, args);
339 if (len < 0) {
340 sway_log_errno(SWAY_ERROR, "vsnprintf(\"%s\") failed", fmt);
341 goto out;
342 }
343
344 str = malloc(len + 1);
345 if (str == NULL) {
346 sway_log_errno(SWAY_ERROR, "malloc() failed");
347 goto out;
348 }
349
350 vsnprintf(str, len + 1, fmt, args_copy);
351
352out:
353 va_end(args_copy);
354 return str;
355}
356
357char *format_str(const char *fmt, ...) {
358 va_list args;
359 va_start(args, fmt);
360 char *str = vformat_str(fmt, args);
361 va_end(args);
362 return str;
363}
diff --git a/common/util.c b/common/util.c
index 5ea94f48..5d4c0673 100644
--- a/common/util.c
+++ b/common/util.c
@@ -10,12 +10,6 @@
10#include "log.h" 10#include "log.h"
11#include "util.h" 11#include "util.h"
12 12
13uint32_t get_current_time_msec(void) {
14 struct timespec now;
15 clock_gettime(CLOCK_MONOTONIC, &now);
16 return now.tv_sec * 1000 + now.tv_nsec / 1000000;
17}
18
19int wrap(int i, int max) { 13int wrap(int i, int max) {
20 return ((i % max) + max) % max; 14 return ((i % max) + max) % max;
21} 15}
@@ -86,6 +80,12 @@ enum movement_unit parse_movement_unit(const char *unit) {
86 80
87int parse_movement_amount(int argc, char **argv, 81int parse_movement_amount(int argc, char **argv,
88 struct movement_amount *amount) { 82 struct movement_amount *amount) {
83 if (!sway_assert(argc > 0, "Expected args in parse_movement_amount")) {
84 amount->amount = 0;
85 amount->unit = MOVEMENT_UNIT_INVALID;
86 return 0;
87 }
88
89 char *err; 89 char *err;
90 amount->amount = (int)strtol(argv[0], &err, 10); 90 amount->amount = (int)strtol(argv[0], &err, 10);
91 if (*err) { 91 if (*err) {
diff --git a/completions/bash/sway b/completions/bash/sway
index edd752cd..01b20073 100644
--- a/completions/bash/sway
+++ b/completions/bash/sway
@@ -2,7 +2,7 @@
2 2
3_sway() 3_sway()
4{ 4{
5 local cur prev 5 local cur prev short long
6 _get_comp_words_by_ref cur prev 6 _get_comp_words_by_ref cur prev
7 7
8 short=( 8 short=(
diff --git a/completions/bash/swaybar b/completions/bash/swaybar
index 1e085c65..3709d4f9 100644
--- a/completions/bash/swaybar
+++ b/completions/bash/swaybar
@@ -2,7 +2,7 @@
2 2
3_swaybar() 3_swaybar()
4{ 4{
5 local cur prev 5 local cur prev short long
6 _get_comp_words_by_ref cur prev 6 _get_comp_words_by_ref cur prev
7 7
8 short=( 8 short=(
diff --git a/completions/bash/swaymsg b/completions/bash/swaymsg
index f865e4e1..30457751 100644
--- a/completions/bash/swaymsg
+++ b/completions/bash/swaymsg
@@ -2,7 +2,7 @@
2 2
3_swaymsg() 3_swaymsg()
4{ 4{
5 local cur prev 5 local cur prev types short long
6 _get_comp_words_by_ref cur prev 6 _get_comp_words_by_ref cur prev
7 7
8 types=( 8 types=(
diff --git a/completions/meson.build b/completions/meson.build
new file mode 100644
index 00000000..6bca9391
--- /dev/null
+++ b/completions/meson.build
@@ -0,0 +1,57 @@
1if get_option('zsh-completions')
2 zsh_files = files(
3 'zsh/_sway',
4 'zsh/_swaymsg',
5 )
6 zsh_install_dir = join_paths(datadir, 'zsh', 'site-functions')
7
8 install_data(zsh_files, install_dir: zsh_install_dir)
9endif
10
11if get_option('bash-completions')
12 bash_comp = dependency('bash-completion', required: false)
13
14 bash_files = files(
15 'bash/sway',
16 'bash/swaymsg',
17 )
18
19 if get_option('swaybar')
20 bash_files += files('bash/swaybar')
21 endif
22
23 if bash_comp.found()
24 bash_install_dir = bash_comp.get_variable(
25 pkgconfig: 'completionsdir',
26 pkgconfig_define: ['datadir', datadir]
27 )
28 else
29 bash_install_dir = join_paths(datadir, 'bash-completion', 'completions')
30 endif
31
32 install_data(bash_files, install_dir: bash_install_dir)
33endif
34
35if get_option('fish-completions')
36 fish_comp = dependency('fish', required: false)
37
38 fish_files = files(
39 'fish/sway.fish',
40 'fish/swaymsg.fish',
41 )
42
43 if get_option('swaynag')
44 fish_files += files('fish/swaynag.fish')
45 endif
46
47 if fish_comp.found()
48 fish_install_dir = fish_comp.get_variable(
49 pkgconfig: 'completionsdir',
50 pkgconfig_define: ['datadir', datadir]
51 )
52 else
53 fish_install_dir = join_paths(datadir, 'fish', 'vendor_completions.d')
54 endif
55
56 install_data(fish_files, install_dir: fish_install_dir)
57endif
diff --git a/config.in b/config.in
index 08703bef..a5173165 100644
--- a/config.in
+++ b/config.in
@@ -14,11 +14,11 @@ set $down j
14set $up k 14set $up k
15set $right l 15set $right l
16# Your preferred terminal emulator 16# Your preferred terminal emulator
17set $term alacritty 17set $term foot
18# Your preferred application launcher 18# Your preferred application launcher
19# Note: pass the final command to swaymsg so that the resulting window can be opened 19# Note: pass the final command to swaymsg so that the resulting window can be opened
20# on the original workspace that the command was run on. 20# on the original workspace that the command was run on.
21set $menu dmenu_path | dmenu | xargs swaymsg exec -- 21set $menu dmenu_path | wmenu | xargs swaymsg exec --
22 22
23### Output configuration 23### Output configuration
24# 24#
@@ -37,7 +37,7 @@ output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill
37# 37#
38# exec swayidle -w \ 38# exec swayidle -w \
39# timeout 300 'swaylock -f -c 000000' \ 39# timeout 300 'swaylock -f -c 000000' \
40# timeout 600 'swaymsg "output * dpms off"' resume 'swaymsg "output * dpms on"' \ 40# timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
41# before-sleep 'swaylock -f -c 000000' 41# before-sleep 'swaylock -f -c 000000'
42# 42#
43# This will lock your screen after 300 seconds of inactivity, then turn off 43# This will lock your screen after 300 seconds of inactivity, then turn off
@@ -82,7 +82,7 @@ output * bg @datadir@/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill
82 bindsym $mod+Shift+c reload 82 bindsym $mod+Shift+c reload
83 83
84 # Exit sway (logs you out of your Wayland session) 84 # Exit sway (logs you out of your Wayland session)
85 bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit' 85 bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit'
86# 86#
87# Moving around: 87# Moving around:
88# 88#
@@ -205,7 +205,7 @@ bar {
205 205
206 # When the status_command prints a new line to stdout, swaybar updates. 206 # When the status_command prints a new line to stdout, swaybar updates.
207 # The default just shows the current date and time. 207 # The default just shows the current date and time.
208 status_command while date +'%Y-%m-%d %l:%M:%S %p'; do sleep 1; done 208 status_command while date +'%Y-%m-%d %X'; do sleep 1; done
209 209
210 colors { 210 colors {
211 statusline #ffffff 211 statusline #ffffff
diff --git a/contrib/_incr_version b/contrib/_incr_version
deleted file mode 100755
index a4fa2654..00000000
--- a/contrib/_incr_version
+++ /dev/null
@@ -1,21 +0,0 @@
1#!/bin/sh -eu
2old_version="$1"
3new_version="$2"
4
5if [ "$new_version" != "${new_version#v}" ]
6then
7 echo "Error: The new version shouldn't be prefixed with a \"v\"." >&2
8 exit 1
9fi
10
11sed -i meson.build -e "s/^ version: .*#release_version/ version: '$new_version', #release_version/g"
12
13printf "Minimum wlroots version? "
14read wlr_version_min
15printf "Maximum wlroots version? "
16read wlr_version_max
17
18sed -i meson.build -e "s/wlroots_version =.*/wlroots_version = ['>=$wlr_version_min', '<$wlr_version_max']/"
19
20git add meson.build
21git commit -m "Update version to $new_version"
diff --git a/contrib/autoname-workspaces.py b/contrib/autoname-workspaces.py
deleted file mode 100755
index 297d91b2..00000000
--- a/contrib/autoname-workspaces.py
+++ /dev/null
@@ -1,130 +0,0 @@
1#!/usr/bin/python
2
3# This script requires i3ipc-python package (install it from a system package manager
4# or pip).
5# It adds icons to the workspace name for each open window.
6# Set your keybindings like this: set $workspace1 workspace number 1
7# Add your icons to WINDOW_ICONS.
8# Based on https://github.com/maximbaz/dotfiles/blob/master/bin/i3-autoname-workspaces
9
10import argparse
11import i3ipc
12import logging
13import re
14import signal
15import sys
16
17WINDOW_ICONS = {
18 "firefox": "",
19}
20
21DEFAULT_ICON = "ó°€"
22
23
24def icon_for_window(window):
25 app_id = window.app_id
26 if app_id is not None and len(app_id) > 0:
27 app_id = app_id.lower()
28 if app_id in WINDOW_ICONS:
29 return WINDOW_ICONS[app_id]
30 logging.info("No icon available for window with app_id: %s" % str(app_id))
31 else:
32 # xwayland support
33 class_name = window.window_class
34 if len(class_name) > 0:
35 class_name = class_name.lower()
36 if class_name in WINDOW_ICONS:
37 return WINDOW_ICONS[class_name]
38 logging.info(
39 "No icon available for window with class_name: %s" % str(class_name)
40 )
41 return DEFAULT_ICON
42
43
44def rename_workspaces(ipc):
45 for workspace in ipc.get_tree().workspaces():
46 name_parts = parse_workspace_name(workspace.name)
47 icon_tuple = ()
48 for w in workspace:
49 if w.app_id is not None or w.window_class is not None:
50 icon = icon_for_window(w)
51 if not ARGUMENTS.duplicates and icon in icon_tuple:
52 continue
53 icon_tuple += (icon,)
54 name_parts["icons"] = " ".join(icon_tuple) + " "
55 new_name = construct_workspace_name(name_parts)
56 ipc.command('rename workspace "%s" to "%s"' % (workspace.name, new_name))
57
58
59def undo_window_renaming(ipc):
60 for workspace in ipc.get_tree().workspaces():
61 name_parts = parse_workspace_name(workspace.name)
62 name_parts["icons"] = None
63 new_name = construct_workspace_name(name_parts)
64 ipc.command('rename workspace "%s" to "%s"' % (workspace.name, new_name))
65 ipc.main_quit()
66 sys.exit(0)
67
68
69def parse_workspace_name(name):
70 return re.match(
71 "(?P<num>[0-9]+):?(?P<shortname>\w+)? ?(?P<icons>.+)?", name
72 ).groupdict()
73
74
75def construct_workspace_name(parts):
76 new_name = str(parts["num"])
77 if parts["shortname"] or parts["icons"]:
78 new_name += ":"
79
80 if parts["shortname"]:
81 new_name += parts["shortname"]
82
83 if parts["icons"]:
84 new_name += " " + parts["icons"]
85
86 return new_name
87
88
89if __name__ == "__main__":
90 parser = argparse.ArgumentParser(
91 description="This script automatically changes the workspace name in sway depending on your open applications."
92 )
93 parser.add_argument(
94 "--duplicates",
95 "-d",
96 action="store_true",
97 help="Set it when you want an icon for each instance of the same application per workspace.",
98 )
99 parser.add_argument(
100 "--logfile",
101 "-l",
102 type=str,
103 default="/tmp/sway-autoname-workspaces.log",
104 help="Path for the logfile.",
105 )
106 args = parser.parse_args()
107 global ARGUMENTS
108 ARGUMENTS = args
109
110 logging.basicConfig(
111 level=logging.INFO,
112 filename=ARGUMENTS.logfile,
113 filemode="w",
114 format="%(message)s",
115 )
116
117 ipc = i3ipc.Connection()
118
119 for sig in [signal.SIGINT, signal.SIGTERM]:
120 signal.signal(sig, lambda signal, frame: undo_window_renaming(ipc))
121
122 def window_event_handler(ipc, e):
123 if e.change in ["new", "close", "move"]:
124 rename_workspaces(ipc)
125
126 ipc.on("window", window_event_handler)
127
128 rename_workspaces(ipc)
129
130 ipc.main()
diff --git a/contrib/grimshot b/contrib/grimshot
deleted file mode 100755
index 461a5eef..00000000
--- a/contrib/grimshot
+++ /dev/null
@@ -1,154 +0,0 @@
1#!/bin/sh
2
3## Grimshot: a helper for screenshots within sway
4## Requirements:
5## - `grim`: screenshot utility for wayland
6## - `slurp`: to select an area
7## - `swaymsg`: to read properties of current window
8## - `wl-copy`: clipboard utility
9## - `jq`: json utility to parse swaymsg output
10## - `notify-send`: to show notifications
11## Those are needed to be installed, if unsure, run `grimshot check`
12##
13## See `man 1 grimshot` or `grimshot usage` for further details.
14
15getTargetDirectory() {
16 test -f ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs && \
17 . ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs
18
19 echo ${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}
20}
21
22if [ "$1" = "--notify" ]; then
23 NOTIFY=yes
24 shift 1
25else
26 NOTIFY=no
27fi
28
29ACTION=${1:-usage}
30SUBJECT=${2:-screen}
31FILE=${3:-$(getTargetDirectory)/$(date -Ins).png}
32
33if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "check" ]; then
34 echo "Usage:"
35 echo " grimshot [--notify] (copy|save) [active|screen|output|area|window] [FILE]"
36 echo " grimshot check"
37 echo " grimshot usage"
38 echo ""
39 echo "Commands:"
40 echo " copy: Copy the screenshot data into the clipboard."
41 echo " save: Save the screenshot to a regular file."
42 echo " check: Verify if required tools are installed and exit."
43 echo " usage: Show this message and exit."
44 echo ""
45 echo "Targets:"
46 echo " active: Currently active window."
47 echo " screen: All visible outputs."
48 echo " output: Currently active output."
49 echo " area: Manually select a region."
50 echo " window: Manually select a window."
51 exit
52fi
53
54notify() {
55 notify-send -t 3000 -a grimshot "$@"
56}
57notifyOk() {
58 [ "$NOTIFY" = "no" ] && return
59
60 TITLE=${2:-"Screenshot"}
61 MESSAGE=${1:-"OK"}
62 notify "$TITLE" "$MESSAGE"
63}
64notifyError() {
65 if [ $NOTIFY = "yes" ]; then
66 TITLE=${2:-"Screenshot"}
67 MESSAGE=${1:-"Error taking screenshot with grim"}
68 notify -u critical "$TITLE" "$MESSAGE"
69 else
70 echo $1
71 fi
72}
73
74die() {
75 MSG=${1:-Bye}
76 notifyError "Error: $MSG"
77 exit 2
78}
79
80check() {
81 COMMAND=$1
82 if command -v "$COMMAND" > /dev/null 2>&1; then
83 RESULT="OK"
84 else
85 RESULT="NOT FOUND"
86 fi
87 echo " $COMMAND: $RESULT"
88}
89
90takeScreenshot() {
91 FILE=$1
92 GEOM=$2
93 OUTPUT=$3
94 if [ ! -z "$OUTPUT" ]; then
95 grim -o "$OUTPUT" "$FILE" || die "Unable to invoke grim"
96 elif [ -z "$GEOM" ]; then
97 grim "$FILE" || die "Unable to invoke grim"
98 else
99 grim -g "$GEOM" "$FILE" || die "Unable to invoke grim"
100 fi
101}
102
103if [ "$ACTION" = "check" ] ; then
104 echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..."
105 check grim
106 check slurp
107 check swaymsg
108 check wl-copy
109 check jq
110 check notify-send
111 exit
112elif [ "$SUBJECT" = "area" ] ; then
113 GEOM=$(slurp -d)
114 # Check if user exited slurp without selecting the area
115 if [ -z "$GEOM" ]; then
116 exit
117 fi
118 WHAT="Area"
119elif [ "$SUBJECT" = "active" ] ; then
120 FOCUSED=$(swaymsg -t get_tree | jq -r 'recurse(.nodes[]?, .floating_nodes[]?) | select(.focused)')
121 GEOM=$(echo "$FOCUSED" | jq -r '.rect | "\(.x),\(.y) \(.width)x\(.height)"')
122 APP_ID=$(echo "$FOCUSED" | jq -r '.app_id')
123 WHAT="$APP_ID window"
124elif [ "$SUBJECT" = "screen" ] ; then
125 GEOM=""
126 WHAT="Screen"
127elif [ "$SUBJECT" = "output" ] ; then
128 GEOM=""
129 OUTPUT=$(swaymsg -t get_outputs | jq -r '.[] | select(.focused)' | jq -r '.name')
130 WHAT="$OUTPUT"
131elif [ "$SUBJECT" = "window" ] ; then
132 GEOM=$(swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"' | slurp)
133 # Check if user exited slurp without selecting the area
134 if [ -z "$GEOM" ]; then
135 exit
136 fi
137 WHAT="Window"
138else
139 die "Unknown subject to take a screen shot from" "$SUBJECT"
140fi
141
142if [ "$ACTION" = "copy" ] ; then
143 takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error"
144 notifyOk "$WHAT copied to buffer"
145else
146 if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then
147 TITLE="Screenshot of $SUBJECT"
148 MESSAGE=$(basename "$FILE")
149 notifyOk "$MESSAGE" "$TITLE"
150 echo $FILE
151 else
152 notifyError "Error taking screenshot with grim"
153 fi
154fi
diff --git a/contrib/grimshot.1 b/contrib/grimshot.1
deleted file mode 100644
index f6c8a377..00000000
--- a/contrib/grimshot.1
+++ /dev/null
@@ -1,103 +0,0 @@
1.\" Generated by scdoc 1.11.1
2.\" Complete documentation for this program is not available as a GNU info page
3.ie \n(.g .ds Aq \(aq
4.el .ds Aq '
5.nh
6.ad l
7.\" Begin generated content:
8.TH "grimshot" "1" "2020-12-20"
9.P
10.SH NAME
11.P
12grimshot - a helper for screenshots within sway
13.P
14.SH SYNOPSIS
15.P
16\fBgrimshot\fR [--notify] (copy|save) [TARGET] [FILE]
17.br
18\fBgrimshot\fR check
19.br
20\fBgrimshot\fR usage
21.P
22.SH OPTIONS
23.P
24\fB--notify\fR
25.RS 4
26Show notifications to the user that a screenshot has been taken.\&
27.P
28.RE
29\fBsave\fR
30.RS 4
31Save the screenshot into a regular file.\& Grimshot will write images
32files to \fBXDG_SCREENSHOTS_DIR\fR if this is set (or defined
33in \fBuser-dirs.\&dir\fR), or otherwise fall back to \fBXDG_PICTURES_DIR\fR.\&
34.P
35.RE
36\fBcopy\fR
37.RS 4
38Copy the screenshot data (as image/png) into the clipboard.\&
39.P
40.RE
41.SH DESCRIPTION
42.P
43Grimshot is an easy-to-use screenshot utility for sway.\& It provides a
44convenient interface over grim, slurp and jq, and supports storing the
45screenshot either directly to the clipboard using wl-copy or to a file.\&
46.P
47.SH EXAMPLES
48.P
49An example usage pattern is to add these bindings to your sway config:
50.P
51.nf
52.RS 4
53# Screenshots:
54# Super+P: Current window
55# Super+Shift+p: Select area
56# Super+Alt+p Current output
57# Super+Ctrl+p Select a window
58
59bindsym Mod4+p exec grimshot save active
60bindsym Mod4+Shift+p exec grimshot save area
61bindsym Mod4+Mod1+p exec grimshot save output
62bindsym Mod4+Ctrl+p exec grimshot save window
63.fi
64.RE
65.P
66.SH TARGETS
67.P
68grimshot can capture the following named targets:
69.P
70\fIactive\fR
71.RS 4
72Captures the currently active window.\&
73.P
74.RE
75\fIscreen\fR
76.RS 4
77Captures the entire screen.\& This includes all visible outputs.\&
78.P
79.RE
80\fIarea\fR
81.RS 4
82Allows manually selecting a rectangular region, and captures that.\&
83.P
84.RE
85\fIwindow\fR
86.RS 4
87Allows manually selecting a single window (by clicking on it), and
88captures it.\&
89.P
90.RE
91\fIoutput\fR
92.RS 4
93Captures the currently active output.\&
94.P
95.RE
96.SH OUTPUT
97.P
98Grimshot will print the filename of the captured screenshot to stdout if called
99with the \fIsave\fR subcommand.\&
100.P
101.SH SEE ALSO
102.P
103\fBgrim\fR(1)
diff --git a/contrib/grimshot.1.scd b/contrib/grimshot.1.scd
deleted file mode 100644
index 4ab58532..00000000
--- a/contrib/grimshot.1.scd
+++ /dev/null
@@ -1,76 +0,0 @@
1grimshot(1)
2
3# NAME
4
5grimshot - a helper for screenshots within sway
6
7# SYNOPSIS
8
9*grimshot* [--notify] (copy|save) [TARGET] [FILE]++
10*grimshot* check++
11*grimshot* usage
12
13# OPTIONS
14
15*--notify*
16 Show notifications to the user that a screenshot has been taken.
17
18*save*
19 Save the screenshot into a regular file. Grimshot will write images
20 files to *XDG_SCREENSHOTS_DIR* if this is set (or defined
21 in *user-dirs.dir*), or otherwise fall back to *XDG_PICTURES_DIR*.
22
23*copy*
24 Copy the screenshot data (as image/png) into the clipboard.
25
26# DESCRIPTION
27
28Grimshot is an easy-to-use screenshot utility for sway. It provides a
29convenient interface over grim, slurp and jq, and supports storing the
30screenshot either directly to the clipboard using wl-copy or to a file.
31
32# EXAMPLES
33
34An example usage pattern is to add these bindings to your sway config:
35
36```
37# Screenshots:
38# Super+P: Current window
39# Super+Shift+p: Select area
40# Super+Alt+p Current output
41# Super+Ctrl+p Select a window
42
43bindsym Mod4+p exec grimshot save active
44bindsym Mod4+Shift+p exec grimshot save area
45bindsym Mod4+Mod1+p exec grimshot save output
46bindsym Mod4+Ctrl+p exec grimshot save window
47```
48
49# TARGETS
50
51grimshot can capture the following named targets:
52
53_active_
54 Captures the currently active window.
55
56_screen_
57 Captures the entire screen. This includes all visible outputs.
58
59_area_
60 Allows manually selecting a rectangular region, and captures that.
61
62_window_
63 Allows manually selecting a single window (by clicking on it), and
64 captures it.
65
66_output_
67 Captures the currently active output.
68
69# OUTPUT
70
71Grimshot will print the filename of the captured screenshot to stdout if called
72with the _save_ subcommand.
73
74# SEE ALSO
75
76*grim*(1)
diff --git a/contrib/inactive-windows-transparency.py b/contrib/inactive-windows-transparency.py
deleted file mode 100755
index 77b1f221..00000000
--- a/contrib/inactive-windows-transparency.py
+++ /dev/null
@@ -1,64 +0,0 @@
1#!/usr/bin/python
2
3# This script requires i3ipc-python package (install it from a system package manager
4# or pip).
5# It makes inactive windows transparent. Use `transparency_val` variable to control
6# transparency strength in range of 0…1 or use the command line argument -o.
7
8import argparse
9import i3ipc
10import signal
11import sys
12from functools import partial
13
14def on_window_focus(inactive_opacity, ipc, event):
15 global prev_focused
16 global prev_workspace
17
18 focused = event.container
19 workspace = ipc.get_tree().find_focused().workspace().num
20
21 if focused.id != prev_focused.id: # https://github.com/swaywm/sway/issues/2859
22 focused.command("opacity 1")
23 if workspace == prev_workspace:
24 prev_focused.command("opacity " + inactive_opacity)
25 prev_focused = focused
26 prev_workspace = workspace
27
28
29def remove_opacity(ipc):
30 for workspace in ipc.get_tree().workspaces():
31 for w in workspace:
32 w.command("opacity 1")
33 ipc.main_quit()
34 sys.exit(0)
35
36
37if __name__ == "__main__":
38 transparency_val = "0.80"
39
40 parser = argparse.ArgumentParser(
41 description="This script allows you to set the transparency of unfocused windows in sway."
42 )
43 parser.add_argument(
44 "--opacity",
45 "-o",
46 type=str,
47 default=transparency_val,
48 help="set opacity value in range 0...1",
49 )
50 args = parser.parse_args()
51
52 ipc = i3ipc.Connection()
53 prev_focused = None
54 prev_workspace = ipc.get_tree().find_focused().workspace().num
55
56 for window in ipc.get_tree():
57 if window.focused:
58 prev_focused = window
59 else:
60 window.command("opacity " + args.opacity)
61 for sig in [signal.SIGINT, signal.SIGTERM]:
62 signal.signal(sig, lambda signal, frame: remove_opacity(ipc))
63 ipc.on("window::focus", partial(on_window_focus, args.opacity))
64 ipc.main() \ No newline at end of file
diff --git a/include/background-image.h b/include/background-image.h
deleted file mode 100644
index 15935ffd..00000000
--- a/include/background-image.h
+++ /dev/null
@@ -1,20 +0,0 @@
1#ifndef _SWAY_BACKGROUND_IMAGE_H
2#define _SWAY_BACKGROUND_IMAGE_H
3#include "cairo.h"
4
5enum background_mode {
6 BACKGROUND_MODE_STRETCH,
7 BACKGROUND_MODE_FILL,
8 BACKGROUND_MODE_FIT,
9 BACKGROUND_MODE_CENTER,
10 BACKGROUND_MODE_TILE,
11 BACKGROUND_MODE_SOLID_COLOR,
12 BACKGROUND_MODE_INVALID,
13};
14
15enum background_mode parse_background_mode(const char *mode);
16cairo_surface_t *load_background_image(const char *path);
17void render_background_image(cairo_t *cairo, cairo_surface_t *image,
18 enum background_mode mode, int buffer_width, int buffer_height);
19
20#endif
diff --git a/include/cairo.h b/include/cairo_util.h
index c1275db2..dc049c6d 100644
--- a/include/cairo.h
+++ b/include/cairo_util.h
@@ -1,8 +1,8 @@
1#ifndef _SWAY_CAIRO_H 1#ifndef _SWAY_CAIRO_UTIL_H
2#define _SWAY_CAIRO_H 2#define _SWAY_CAIRO_UTIL_H
3#include "config.h" 3#include "config.h"
4#include <stdint.h> 4#include <stdint.h>
5#include <cairo/cairo.h> 5#include <cairo.h>
6#include <wayland-client-protocol.h> 6#include <wayland-client-protocol.h>
7 7
8void cairo_set_source_u32(cairo_t *cairo, uint32_t color); 8void cairo_set_source_u32(cairo_t *cairo, uint32_t color);
diff --git a/include/gesture.h b/include/gesture.h
new file mode 100644
index 00000000..9c6b0f91
--- /dev/null
+++ b/include/gesture.h
@@ -0,0 +1,104 @@
1#ifndef _SWAY_GESTURE_H
2#define _SWAY_GESTURE_H
3
4#include <stdbool.h>
5#include <stdint.h>
6
7/**
8 * A gesture type used in binding.
9 */
10enum gesture_type {
11 GESTURE_TYPE_NONE = 0,
12 GESTURE_TYPE_HOLD,
13 GESTURE_TYPE_PINCH,
14 GESTURE_TYPE_SWIPE,
15};
16
17// Turns single type enum value to constant string representation.
18const char *gesture_type_string(enum gesture_type direction);
19
20// Value to use to accept any finger count
21extern const uint8_t GESTURE_FINGERS_ANY;
22
23/**
24 * A gesture direction used in binding.
25 */
26enum gesture_direction {
27 GESTURE_DIRECTION_NONE = 0,
28 // Directions based on delta x and y
29 GESTURE_DIRECTION_UP = 1 << 0,
30 GESTURE_DIRECTION_DOWN = 1 << 1,
31 GESTURE_DIRECTION_LEFT = 1 << 2,
32 GESTURE_DIRECTION_RIGHT = 1 << 3,
33 // Directions based on scale
34 GESTURE_DIRECTION_INWARD = 1 << 4,
35 GESTURE_DIRECTION_OUTWARD = 1 << 5,
36 // Directions based on rotation
37 GESTURE_DIRECTION_CLOCKWISE = 1 << 6,
38 GESTURE_DIRECTION_COUNTERCLOCKWISE = 1 << 7,
39};
40
41// Turns single direction enum value to constant string representation.
42const char *gesture_direction_string(enum gesture_direction direction);
43
44/**
45 * Struct representing a pointer gesture
46 */
47struct gesture {
48 enum gesture_type type;
49 uint8_t fingers;
50 uint32_t directions;
51};
52
53/**
54 * Parses gesture from <gesture>[:<fingers>][:<directions>] string.
55 *
56 * Return NULL on success, otherwise error message string
57 */
58char *gesture_parse(const char *input, struct gesture *output);
59
60// Turns gesture into string representation
61char *gesture_to_string(struct gesture *gesture);
62
63// Check if gesture is of certain type and finger count.
64bool gesture_check(struct gesture *target,
65 enum gesture_type type, uint8_t fingers);
66
67// Check if a gesture target/binding is match by other gesture/input
68bool gesture_match(struct gesture *target,
69 struct gesture *to_match, bool exact);
70
71// Returns true if gesture are exactly the same
72bool gesture_equal(struct gesture *a, struct gesture *b);
73
74// Compare distance between two matched target gestures.
75int8_t gesture_compare(struct gesture *a, struct gesture *b);
76
77// Small helper struct to track gestures over time
78struct gesture_tracker {
79 enum gesture_type type;
80 uint8_t fingers;
81 double dx, dy;
82 double scale;
83 double rotation;
84};
85
86// Begin gesture tracking
87void gesture_tracker_begin(struct gesture_tracker *tracker,
88 enum gesture_type type, uint8_t fingers);
89
90// Check if the provides type is currently being tracked
91bool gesture_tracker_check(struct gesture_tracker *tracker,
92 enum gesture_type type);
93
94// Update gesture track with new data point
95void gesture_tracker_update(struct gesture_tracker *tracker, double dx,
96 double dy, double scale, double rotation);
97
98// Reset tracker
99void gesture_tracker_cancel(struct gesture_tracker *tracker);
100
101// Reset tracker and return gesture tracked
102struct gesture *gesture_tracker_end(struct gesture_tracker *tracker);
103
104#endif
diff --git a/include/ipc-client.h b/include/ipc-client.h
index d3895023..9c5712d7 100644
--- a/include/ipc-client.h
+++ b/include/ipc-client.h
@@ -1,6 +1,9 @@
1#ifndef _SWAY_IPC_CLIENT_H 1#ifndef _SWAY_IPC_CLIENT_H
2#define _SWAY_IPC_CLIENT_H 2#define _SWAY_IPC_CLIENT_H
3 3
4// arbitrary number, it's probably sufficient, higher number = more memory usage
5#define JSON_MAX_DEPTH 512
6
4#include <stdbool.h> 7#include <stdbool.h>
5#include <stdint.h> 8#include <stdint.h>
6#include <sys/time.h> 9#include <sys/time.h>
diff --git a/include/pango.h b/include/pango.h
index 6ab83c16..228e39cf 100644
--- a/include/pango.h
+++ b/include/pango.h
@@ -3,8 +3,9 @@
3#include <stdarg.h> 3#include <stdarg.h>
4#include <stdbool.h> 4#include <stdbool.h>
5#include <stdint.h> 5#include <stdint.h>
6#include <cairo/cairo.h> 6#include <cairo.h>
7#include <pango/pangocairo.h> 7#include <pango/pangocairo.h>
8#include "stringop.h"
8 9
9/** 10/**
10 * Utility function which escape characters a & < > ' ". 11 * Utility function which escape characters a & < > ' ".
@@ -13,11 +14,12 @@
13 * escaped string to dest if provided. 14 * escaped string to dest if provided.
14 */ 15 */
15size_t escape_markup_text(const char *src, char *dest); 16size_t escape_markup_text(const char *src, char *dest);
16PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, 17PangoLayout *get_pango_layout(cairo_t *cairo, const PangoFontDescription *desc,
17 const char *text, double scale, bool markup); 18 const char *text, double scale, bool markup);
18void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, 19void get_text_size(cairo_t *cairo, const PangoFontDescription *desc, int *width, int *height,
19 int *baseline, double scale, bool markup, const char *fmt, ...); 20 int *baseline, double scale, bool markup, const char *fmt, ...) _SWAY_ATTRIB_PRINTF(8, 9);
20void pango_printf(cairo_t *cairo, const char *font, 21void get_text_metrics(const PangoFontDescription *desc, int *height, int *baseline);
21 double scale, bool markup, const char *fmt, ...); 22void render_text(cairo_t *cairo, PangoFontDescription *desc,
23 double scale, bool markup, const char *fmt, ...) _SWAY_ATTRIB_PRINTF(5, 6);
22 24
23#endif 25#endif
diff --git a/include/pool-buffer.h b/include/pool-buffer.h
index 54f5be06..b7a95afe 100644
--- a/include/pool-buffer.h
+++ b/include/pool-buffer.h
@@ -1,6 +1,6 @@
1#ifndef _SWAY_BUFFERS_H 1#ifndef _SWAY_BUFFERS_H
2#define _SWAY_BUFFERS_H 2#define _SWAY_BUFFERS_H
3#include <cairo/cairo.h> 3#include <cairo.h>
4#include <pango/pangocairo.h> 4#include <pango/pangocairo.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <stdint.h> 6#include <stdint.h>
diff --git a/include/stringop.h b/include/stringop.h
index 8d7089e9..19a50f23 100644
--- a/include/stringop.h
+++ b/include/stringop.h
@@ -2,8 +2,15 @@
2#define _SWAY_STRINGOP_H 2#define _SWAY_STRINGOP_H
3 3
4#include <stdbool.h> 4#include <stdbool.h>
5#include <stddef.h>
5#include "list.h" 6#include "list.h"
6 7
8#ifdef __GNUC__
9#define _SWAY_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end)))
10#else
11#define _SWAY_ATTRIB_PRINTF(start, end)
12#endif
13
7void strip_whitespace(char *str); 14void strip_whitespace(char *str);
8void strip_quotes(char *str); 15void strip_quotes(char *str);
9 16
@@ -30,4 +37,7 @@ char *argsep(char **stringp, const char *delim, char *matched_delim);
30// Expand a path using shell replacements such as $HOME and ~ 37// Expand a path using shell replacements such as $HOME and ~
31bool expand_path(char **path); 38bool expand_path(char **path);
32 39
40char *vformat_str(const char *fmt, va_list args) _SWAY_ATTRIB_PRINTF(1, 0);
41char *format_str(const char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2);
42
33#endif 43#endif
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 964b3661..27058587 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -3,13 +3,14 @@
3 3
4#include <wlr/util/edges.h> 4#include <wlr/util/edges.h>
5#include "config.h" 5#include "config.h"
6#include "stringop.h"
6 7
7struct sway_container; 8struct sway_container;
8 9
9typedef struct cmd_results *sway_cmd(int argc, char **argv); 10typedef struct cmd_results *sway_cmd(int argc, char **argv);
10 11
11struct cmd_handler { 12struct cmd_handler {
12 char *command; 13 const char *command;
13 sway_cmd *handle; 14 sway_cmd *handle;
14}; 15};
15 16
@@ -46,8 +47,8 @@ enum expected_args {
46struct cmd_results *checkarg(int argc, const char *name, 47struct cmd_results *checkarg(int argc, const char *name,
47 enum expected_args type, int val); 48 enum expected_args type, int val);
48 49
49struct cmd_handler *find_handler(char *line, struct cmd_handler *cmd_handlers, 50const struct cmd_handler *find_handler(const char *line,
50 size_t handlers_size); 51 const struct cmd_handler *cmd_handlers, size_t handlers_size);
51 52
52/** 53/**
53 * Parse and executes a command. 54 * Parse and executes a command.
@@ -68,7 +69,7 @@ struct cmd_results *config_command(char *command, char **new_block);
68 * Parse and handle a sub command 69 * Parse and handle a sub command
69 */ 70 */
70struct cmd_results *config_subcommand(char **argv, int argc, 71struct cmd_results *config_subcommand(char **argv, int argc,
71 struct cmd_handler *handlers, size_t handlers_size); 72 const struct cmd_handler *handlers, size_t handlers_size);
72/* 73/*
73 * Parses a command policy rule. 74 * Parses a command policy rule.
74 */ 75 */
@@ -76,7 +77,7 @@ struct cmd_results *config_commands_command(char *exec);
76/** 77/**
77 * Allocates a cmd_results object. 78 * Allocates a cmd_results object.
78 */ 79 */
79struct cmd_results *cmd_results_new(enum cmd_status status, const char *error, ...); 80struct cmd_results *cmd_results_new(enum cmd_status status, const char *error, ...) _SWAY_ATTRIB_PRINTF(2, 3);
80/** 81/**
81 * Frees a cmd_results object. 82 * Frees a cmd_results object.
82 */ 83 */
@@ -106,12 +107,14 @@ sway_cmd cmd_exec_process;
106sway_cmd cmd_assign; 107sway_cmd cmd_assign;
107sway_cmd cmd_bar; 108sway_cmd cmd_bar;
108sway_cmd cmd_bindcode; 109sway_cmd cmd_bindcode;
110sway_cmd cmd_bindgesture;
109sway_cmd cmd_bindswitch; 111sway_cmd cmd_bindswitch;
110sway_cmd cmd_bindsym; 112sway_cmd cmd_bindsym;
111sway_cmd cmd_border; 113sway_cmd cmd_border;
112sway_cmd cmd_client_noop; 114sway_cmd cmd_client_noop;
113sway_cmd cmd_client_focused; 115sway_cmd cmd_client_focused;
114sway_cmd cmd_client_focused_inactive; 116sway_cmd cmd_client_focused_inactive;
117sway_cmd cmd_client_focused_tab_title;
115sway_cmd cmd_client_unfocused; 118sway_cmd cmd_client_unfocused;
116sway_cmd cmd_client_urgent; 119sway_cmd cmd_client_urgent;
117sway_cmd cmd_client_placeholder; 120sway_cmd cmd_client_placeholder;
@@ -157,12 +160,11 @@ sway_cmd cmd_new_float;
157sway_cmd cmd_new_window; 160sway_cmd cmd_new_window;
158sway_cmd cmd_nop; 161sway_cmd cmd_nop;
159sway_cmd cmd_opacity; 162sway_cmd cmd_opacity;
160sway_cmd cmd_new_float;
161sway_cmd cmd_new_window;
162sway_cmd cmd_no_focus; 163sway_cmd cmd_no_focus;
163sway_cmd cmd_output; 164sway_cmd cmd_output;
164sway_cmd cmd_permit; 165sway_cmd cmd_permit;
165sway_cmd cmd_popup_during_fullscreen; 166sway_cmd cmd_popup_during_fullscreen;
167sway_cmd cmd_primary_selection;
166sway_cmd cmd_reject; 168sway_cmd cmd_reject;
167sway_cmd cmd_reload; 169sway_cmd cmd_reload;
168sway_cmd cmd_rename; 170sway_cmd cmd_rename;
@@ -190,6 +192,7 @@ sway_cmd cmd_titlebar_border_thickness;
190sway_cmd cmd_titlebar_padding; 192sway_cmd cmd_titlebar_padding;
191sway_cmd cmd_unbindcode; 193sway_cmd cmd_unbindcode;
192sway_cmd cmd_unbindswitch; 194sway_cmd cmd_unbindswitch;
195sway_cmd cmd_unbindgesture;
193sway_cmd cmd_unbindsym; 196sway_cmd cmd_unbindsym;
194sway_cmd cmd_unmark; 197sway_cmd cmd_unmark;
195sway_cmd cmd_urgent; 198sway_cmd cmd_urgent;
@@ -249,6 +252,7 @@ sway_cmd input_cmd_click_method;
249sway_cmd input_cmd_drag; 252sway_cmd input_cmd_drag;
250sway_cmd input_cmd_drag_lock; 253sway_cmd input_cmd_drag_lock;
251sway_cmd input_cmd_dwt; 254sway_cmd input_cmd_dwt;
255sway_cmd input_cmd_dwtp;
252sway_cmd input_cmd_events; 256sway_cmd input_cmd_events;
253sway_cmd input_cmd_left_handed; 257sway_cmd input_cmd_left_handed;
254sway_cmd input_cmd_map_from_region; 258sway_cmd input_cmd_map_from_region;
@@ -257,10 +261,12 @@ sway_cmd input_cmd_map_to_region;
257sway_cmd input_cmd_middle_emulation; 261sway_cmd input_cmd_middle_emulation;
258sway_cmd input_cmd_natural_scroll; 262sway_cmd input_cmd_natural_scroll;
259sway_cmd input_cmd_pointer_accel; 263sway_cmd input_cmd_pointer_accel;
264sway_cmd input_cmd_rotation_angle;
260sway_cmd input_cmd_scroll_factor; 265sway_cmd input_cmd_scroll_factor;
261sway_cmd input_cmd_repeat_delay; 266sway_cmd input_cmd_repeat_delay;
262sway_cmd input_cmd_repeat_rate; 267sway_cmd input_cmd_repeat_rate;
263sway_cmd input_cmd_scroll_button; 268sway_cmd input_cmd_scroll_button;
269sway_cmd input_cmd_scroll_button_lock;
264sway_cmd input_cmd_scroll_method; 270sway_cmd input_cmd_scroll_method;
265sway_cmd input_cmd_tap; 271sway_cmd input_cmd_tap;
266sway_cmd input_cmd_tap_button_map; 272sway_cmd input_cmd_tap_button_map;
@@ -282,12 +288,16 @@ sway_cmd output_cmd_dpms;
282sway_cmd output_cmd_enable; 288sway_cmd output_cmd_enable;
283sway_cmd output_cmd_max_render_time; 289sway_cmd output_cmd_max_render_time;
284sway_cmd output_cmd_mode; 290sway_cmd output_cmd_mode;
291sway_cmd output_cmd_modeline;
285sway_cmd output_cmd_position; 292sway_cmd output_cmd_position;
293sway_cmd output_cmd_power;
294sway_cmd output_cmd_render_bit_depth;
286sway_cmd output_cmd_scale; 295sway_cmd output_cmd_scale;
287sway_cmd output_cmd_scale_filter; 296sway_cmd output_cmd_scale_filter;
288sway_cmd output_cmd_subpixel; 297sway_cmd output_cmd_subpixel;
289sway_cmd output_cmd_toggle; 298sway_cmd output_cmd_toggle;
290sway_cmd output_cmd_transform; 299sway_cmd output_cmd_transform;
300sway_cmd output_cmd_unplug;
291 301
292sway_cmd seat_cmd_attach; 302sway_cmd seat_cmd_attach;
293sway_cmd seat_cmd_cursor; 303sway_cmd seat_cmd_cursor;
diff --git a/include/sway/config.h b/include/sway/config.h
index 59f22ae2..f9da1967 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -5,16 +5,20 @@
5#include <string.h> 5#include <string.h>
6#include <time.h> 6#include <time.h>
7#include <wlr/interfaces/wlr_switch.h> 7#include <wlr/interfaces/wlr_switch.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_tablet_tool.h> 8#include <wlr/types/wlr_tablet_tool.h>
9#include <wlr/util/box.h>
10#include <xkbcommon/xkbcommon.h> 10#include <xkbcommon/xkbcommon.h>
11#include <xf86drmMode.h>
11#include "../include/config.h" 12#include "../include/config.h"
13#include "gesture.h"
12#include "list.h" 14#include "list.h"
15#include "stringop.h"
13#include "swaynag.h" 16#include "swaynag.h"
14#include "tree/container.h" 17#include "tree/container.h"
15#include "sway/input/tablet.h" 18#include "sway/input/tablet.h"
16#include "sway/tree/root.h" 19#include "sway/tree/root.h"
17#include "wlr-layer-shell-unstable-v1-protocol.h" 20#include "wlr-layer-shell-unstable-v1-protocol.h"
21#include <pango/pangocairo.h>
18 22
19// TODO: Refactor this shit 23// TODO: Refactor this shit
20 24
@@ -31,7 +35,8 @@ enum binding_input_type {
31 BINDING_KEYSYM, 35 BINDING_KEYSYM,
32 BINDING_MOUSECODE, 36 BINDING_MOUSECODE,
33 BINDING_MOUSESYM, 37 BINDING_MOUSESYM,
34 BINDING_SWITCH 38 BINDING_SWITCH, // dummy, only used to call seat_execute_command
39 BINDING_GESTURE // dummy, only used to call seat_execute_command
35}; 40};
36 41
37enum binding_flags { 42enum binding_flags {
@@ -44,10 +49,11 @@ enum binding_flags {
44 BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload 49 BINDING_RELOAD = 1 << 6, // switch only; (re)trigger binding on reload
45 BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor 50 BINDING_INHIBITED = 1 << 7, // keyboard only: ignore shortcut inhibitor
46 BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key 51 BINDING_NOREPEAT = 1 << 8, // keyboard only; do not trigger when repeating a held key
52 BINDING_EXACT = 1 << 9, // gesture only; only trigger on exact match
47}; 53};
48 54
49/** 55/**
50 * A key binding and an associated command. 56 * A key (or mouse) binding and an associated command.
51 */ 57 */
52struct sway_binding { 58struct sway_binding {
53 enum binding_input_type type; 59 enum binding_input_type type;
@@ -61,12 +67,10 @@ struct sway_binding {
61 char *command; 67 char *command;
62}; 68};
63 69
64/** 70enum sway_switch_trigger {
65 * A mouse binding and an associated command. 71 SWAY_SWITCH_TRIGGER_OFF,
66 */ 72 SWAY_SWITCH_TRIGGER_ON,
67struct sway_mouse_binding { 73 SWAY_SWITCH_TRIGGER_TOGGLE,
68 uint32_t button;
69 char *command;
70}; 74};
71 75
72/** 76/**
@@ -74,12 +78,22 @@ struct sway_mouse_binding {
74 */ 78 */
75struct sway_switch_binding { 79struct sway_switch_binding {
76 enum wlr_switch_type type; 80 enum wlr_switch_type type;
77 enum wlr_switch_state state; 81 enum sway_switch_trigger trigger;
78 uint32_t flags; 82 uint32_t flags;
79 char *command; 83 char *command;
80}; 84};
81 85
82/** 86/**
87 * A gesture binding and an associated command.
88 */
89struct sway_gesture_binding {
90 char *input;
91 uint32_t flags;
92 struct gesture gesture;
93 char *command;
94};
95
96/**
83 * Focus on window activation. 97 * Focus on window activation.
84 */ 98 */
85enum sway_fowa { 99enum sway_fowa {
@@ -98,6 +112,7 @@ struct sway_mode {
98 list_t *keycode_bindings; 112 list_t *keycode_bindings;
99 list_t *mouse_bindings; 113 list_t *mouse_bindings;
100 list_t *switch_bindings; 114 list_t *switch_bindings;
115 list_t *gesture_bindings;
101 bool pango; 116 bool pango;
102}; 117};
103 118
@@ -136,14 +151,17 @@ struct input_config {
136 int drag; 151 int drag;
137 int drag_lock; 152 int drag_lock;
138 int dwt; 153 int dwt;
154 int dwtp;
139 int left_handed; 155 int left_handed;
140 int middle_emulation; 156 int middle_emulation;
141 int natural_scroll; 157 int natural_scroll;
142 float pointer_accel; 158 float pointer_accel;
159 float rotation_angle;
143 float scroll_factor; 160 float scroll_factor;
144 int repeat_delay; 161 int repeat_delay;
145 int repeat_rate; 162 int repeat_rate;
146 int scroll_button; 163 int scroll_button;
164 int scroll_button_lock;
147 int scroll_method; 165 int scroll_method;
148 int send_events; 166 int send_events;
149 int tap; 167 int tap;
@@ -233,12 +251,6 @@ struct seat_config {
233 } xcursor_theme; 251 } xcursor_theme;
234}; 252};
235 253
236enum config_dpms {
237 DPMS_IGNORE,
238 DPMS_ON,
239 DPMS_OFF,
240};
241
242enum scale_filter_mode { 254enum scale_filter_mode {
243 SCALE_FILTER_DEFAULT, // the default is currently smart 255 SCALE_FILTER_DEFAULT, // the default is currently smart
244 SCALE_FILTER_LINEAR, 256 SCALE_FILTER_LINEAR,
@@ -246,6 +258,12 @@ enum scale_filter_mode {
246 SCALE_FILTER_SMART, 258 SCALE_FILTER_SMART,
247}; 259};
248 260
261enum render_bit_depth {
262 RENDER_BIT_DEPTH_DEFAULT, // the default is currently 8
263 RENDER_BIT_DEPTH_8,
264 RENDER_BIT_DEPTH_10,
265};
266
249/** 267/**
250 * Size and position configuration for a particular output. 268 * Size and position configuration for a particular output.
251 * 269 *
@@ -254,9 +272,11 @@ enum scale_filter_mode {
254struct output_config { 272struct output_config {
255 char *name; 273 char *name;
256 int enabled; 274 int enabled;
275 int power;
257 int width, height; 276 int width, height;
258 float refresh_rate; 277 float refresh_rate;
259 int custom_mode; 278 int custom_mode;
279 drmModeModeInfo drm_mode;
260 int x, y; 280 int x, y;
261 float scale; 281 float scale;
262 enum scale_filter_mode scale_filter; 282 enum scale_filter_mode scale_filter;
@@ -264,11 +284,11 @@ struct output_config {
264 enum wl_output_subpixel subpixel; 284 enum wl_output_subpixel subpixel;
265 int max_render_time; // In milliseconds 285 int max_render_time; // In milliseconds
266 int adaptive_sync; 286 int adaptive_sync;
287 enum render_bit_depth render_bit_depth;
267 288
268 char *background; 289 char *background;
269 char *background_option; 290 char *background_option;
270 char *background_fallback; 291 char *background_fallback;
271 enum config_dpms dpms_state;
272}; 292};
273 293
274/** 294/**
@@ -281,6 +301,12 @@ struct side_gaps {
281 int left; 301 int left;
282}; 302};
283 303
304enum smart_gaps_mode {
305 SMART_GAPS_OFF,
306 SMART_GAPS_ON,
307 SMART_GAPS_INVERSE_OUTER,
308};
309
284/** 310/**
285 * Stores configuration for a workspace, regardless of whether the workspace 311 * Stores configuration for a workspace, regardless of whether the workspace
286 * exists. 312 * exists.
@@ -292,6 +318,12 @@ struct workspace_config {
292 struct side_gaps gaps_outer; 318 struct side_gaps gaps_outer;
293}; 319};
294 320
321enum pango_markup_config {
322 PANGO_MARKUP_DISABLED = false,
323 PANGO_MARKUP_ENABLED = true,
324 PANGO_MARKUP_DEFAULT // The default is font dependent ("pango:" prefix)
325};
326
295struct bar_config { 327struct bar_config {
296 char *swaybar_command; 328 char *swaybar_command;
297 struct wl_client *client; 329 struct wl_client *client;
@@ -323,7 +355,7 @@ struct bar_config {
323 char *position; 355 char *position;
324 list_t *bindings; 356 list_t *bindings;
325 char *status_command; 357 char *status_command;
326 bool pango_markup; 358 enum pango_markup_config pango_markup;
327 char *font; 359 char *font;
328 int height; // -1 not defined 360 int height; // -1 not defined
329 bool workspace_buttons; 361 bool workspace_buttons;
@@ -410,14 +442,6 @@ enum sway_popup_during_fullscreen {
410 POPUP_LEAVE, 442 POPUP_LEAVE,
411}; 443};
412 444
413enum command_context {
414 CONTEXT_CONFIG = 1 << 0,
415 CONTEXT_BINDING = 1 << 1,
416 CONTEXT_IPC = 1 << 2,
417 CONTEXT_CRITERIA = 1 << 3,
418 CONTEXT_ALL = 0xFFFFFFFF,
419};
420
421enum focus_follows_mouse_mode { 445enum focus_follows_mouse_mode {
422 FOLLOWS_NO, 446 FOLLOWS_NO,
423 FOLLOWS_YES, 447 FOLLOWS_YES,
@@ -479,9 +503,10 @@ struct sway_config {
479 char *floating_scroll_right_cmd; 503 char *floating_scroll_right_cmd;
480 enum sway_container_layout default_orientation; 504 enum sway_container_layout default_orientation;
481 enum sway_container_layout default_layout; 505 enum sway_container_layout default_layout;
482 char *font; 506 char *font; // Used for IPC.
483 size_t font_height; 507 PangoFontDescription *font_description; // Used internally for rendering and validating.
484 size_t font_baseline; 508 int font_height;
509 int font_baseline;
485 bool pango_markup; 510 bool pango_markup;
486 int titlebar_border_thickness; 511 int titlebar_border_thickness;
487 int titlebar_h_padding; 512 int titlebar_h_padding;
@@ -508,11 +533,12 @@ struct sway_config {
508 bool auto_back_and_forth; 533 bool auto_back_and_forth;
509 bool show_marks; 534 bool show_marks;
510 enum alignment title_align; 535 enum alignment title_align;
536 bool primary_selection;
511 537
512 bool tiling_drag; 538 bool tiling_drag;
513 int tiling_drag_threshold; 539 int tiling_drag_threshold;
514 540
515 bool smart_gaps; 541 enum smart_gaps_mode smart_gaps;
516 int gaps_inner; 542 int gaps_inner;
517 struct side_gaps gaps_outer; 543 struct side_gaps gaps_outer;
518 544
@@ -535,12 +561,15 @@ struct sway_config {
535 struct { 561 struct {
536 struct border_colors focused; 562 struct border_colors focused;
537 struct border_colors focused_inactive; 563 struct border_colors focused_inactive;
564 struct border_colors focused_tab_title;
538 struct border_colors unfocused; 565 struct border_colors unfocused;
539 struct border_colors urgent; 566 struct border_colors urgent;
540 struct border_colors placeholder; 567 struct border_colors placeholder;
541 float background[4]; 568 float background[4];
542 } border_colors; 569 } border_colors;
543 570
571 bool has_focused_tab_title;
572
544 // floating view 573 // floating view
545 int32_t floating_maximum_width; 574 int32_t floating_maximum_width;
546 int32_t floating_maximum_height; 575 int32_t floating_maximum_height;
@@ -559,7 +588,7 @@ struct sway_config {
559 struct sway_node *node; 588 struct sway_node *node;
560 struct sway_container *container; 589 struct sway_container *container;
561 struct sway_workspace *workspace; 590 struct sway_workspace *workspace;
562 bool using_criteria; 591 bool node_overridden; // True if the node is selected by means other than focus
563 struct { 592 struct {
564 int argc; 593 int argc;
565 char **argv; 594 char **argv;
@@ -598,7 +627,7 @@ void run_deferred_bindings(void);
598/** 627/**
599 * Adds a warning entry to the swaynag instance used for errors. 628 * Adds a warning entry to the swaynag instance used for errors.
600 */ 629 */
601void config_add_swaynag_warning(char *fmt, ...); 630void config_add_swaynag_warning(char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2);
602 631
603/** 632/**
604 * Free config struct 633 * Free config struct
@@ -675,6 +704,8 @@ void free_sway_binding(struct sway_binding *sb);
675 704
676void free_switch_binding(struct sway_switch_binding *binding); 705void free_switch_binding(struct sway_switch_binding *binding);
677 706
707void free_gesture_binding(struct sway_gesture_binding *binding);
708
678void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); 709void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
679 710
680void load_swaybar(struct bar_config *bar); 711void load_swaybar(struct bar_config *bar);
@@ -690,14 +721,13 @@ void free_bar_binding(struct bar_binding *binding);
690void free_workspace_config(struct workspace_config *wsc); 721void free_workspace_config(struct workspace_config *wsc);
691 722
692/** 723/**
693 * Updates the value of config->font_height based on the max title height 724 * Updates the value of config->font_height based on the metrics for title's
694 * reported by each container. If recalculate is true, the containers will 725 * font as reported by pango.
695 * recalculate their heights before reporting.
696 * 726 *
697 * If the height has changed, all containers will be rearranged to take on the 727 * If the height has changed, all containers will be rearranged to take on the
698 * new size. 728 * new size.
699 */ 729 */
700void config_update_font_height(bool recalculate); 730void config_update_font_height(void);
701 731
702/** 732/**
703 * Convert bindsym into bindcode using the first configured layout. 733 * Convert bindsym into bindcode using the first configured layout.
diff --git a/include/sway/criteria.h b/include/sway/criteria.h
index ad8610cd..8da345ea 100644
--- a/include/sway/criteria.h
+++ b/include/sway/criteria.h
@@ -1,7 +1,8 @@
1#ifndef _SWAY_CRITERIA_H 1#ifndef _SWAY_CRITERIA_H
2#define _SWAY_CRITERIA_H 2#define _SWAY_CRITERIA_H
3 3
4#include <pcre.h> 4#define PCRE2_CODE_UNIT_WIDTH 8
5#include <pcre2.h>
5#include "config.h" 6#include "config.h"
6#include "list.h" 7#include "list.h"
7#include "tree/view.h" 8#include "tree/view.h"
@@ -15,13 +16,13 @@ enum criteria_type {
15}; 16};
16 17
17enum pattern_type { 18enum pattern_type {
18 PATTERN_PCRE, 19 PATTERN_PCRE2,
19 PATTERN_FOCUSED, 20 PATTERN_FOCUSED,
20}; 21};
21 22
22struct pattern { 23struct pattern {
23 enum pattern_type match_type; 24 enum pattern_type match_type;
24 pcre *regex; 25 pcre2_code *regex;
25}; 26};
26 27
27struct criteria { 28struct criteria {
@@ -42,6 +43,7 @@ struct criteria {
42 struct pattern *window_role; 43 struct pattern *window_role;
43 enum atom_name window_type; 44 enum atom_name window_type;
44#endif 45#endif
46 bool all;
45 bool floating; 47 bool floating;
46 bool tiling; 48 bool tiling;
47 char urgent; // 'l' for latest or 'o' for oldest 49 char urgent; // 'l' for latest or 'o' for oldest
diff --git a/include/sway/desktop.h b/include/sway/desktop.h
deleted file mode 100644
index c969a76b..00000000
--- a/include/sway/desktop.h
+++ /dev/null
@@ -1,13 +0,0 @@
1#include <wlr/types/wlr_surface.h>
2
3struct sway_container;
4struct sway_view;
5
6void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
7 bool whole);
8
9void desktop_damage_whole_container(struct sway_container *con);
10
11void desktop_damage_box(struct wlr_box *box);
12
13void desktop_damage_view(struct sway_view *view);
diff --git a/include/sway/desktop/idle_inhibit_v1.h b/include/sway/desktop/idle_inhibit_v1.h
index 0adafdb9..84cc666d 100644
--- a/include/sway/desktop/idle_inhibit_v1.h
+++ b/include/sway/desktop/idle_inhibit_v1.h
@@ -1,8 +1,6 @@
1#ifndef _SWAY_DESKTOP_IDLE_INHIBIT_V1_H 1#ifndef _SWAY_DESKTOP_IDLE_INHIBIT_V1_H
2#define _SWAY_DESKTOP_IDLE_INHIBIT_V1_H 2#define _SWAY_DESKTOP_IDLE_INHIBIT_V1_H
3#include <wlr/types/wlr_idle_inhibit_v1.h> 3#include <wlr/types/wlr_idle_inhibit_v1.h>
4#include <wlr/types/wlr_idle.h>
5#include "sway/server.h"
6 4
7enum sway_idle_inhibit_mode { 5enum sway_idle_inhibit_mode {
8 INHIBIT_IDLE_APPLICATION, // Application set inhibitor (when visible) 6 INHIBIT_IDLE_APPLICATION, // Application set inhibitor (when visible)
@@ -16,12 +14,10 @@ struct sway_idle_inhibit_manager_v1 {
16 struct wlr_idle_inhibit_manager_v1 *wlr_manager; 14 struct wlr_idle_inhibit_manager_v1 *wlr_manager;
17 struct wl_listener new_idle_inhibitor_v1; 15 struct wl_listener new_idle_inhibitor_v1;
18 struct wl_list inhibitors; 16 struct wl_list inhibitors;
19
20 struct wlr_idle *idle;
21}; 17};
22 18
23struct sway_idle_inhibitor_v1 { 19struct sway_idle_inhibitor_v1 {
24 struct sway_idle_inhibit_manager_v1 *manager; 20 struct wlr_idle_inhibitor_v1 *wlr_inhibitor;
25 struct sway_view *view; 21 struct sway_view *view;
26 enum sway_idle_inhibit_mode mode; 22 enum sway_idle_inhibit_mode mode;
27 23
@@ -32,8 +28,7 @@ struct sway_idle_inhibitor_v1 {
32bool sway_idle_inhibit_v1_is_active( 28bool sway_idle_inhibit_v1_is_active(
33 struct sway_idle_inhibitor_v1 *inhibitor); 29 struct sway_idle_inhibitor_v1 *inhibitor);
34 30
35void sway_idle_inhibit_v1_check_active( 31void sway_idle_inhibit_v1_check_active(void);
36 struct sway_idle_inhibit_manager_v1 *manager);
37 32
38void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, 33void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view,
39 enum sway_idle_inhibit_mode mode); 34 enum sway_idle_inhibit_mode mode);
@@ -47,6 +42,6 @@ struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_application_inhibitor_for_vi
47void sway_idle_inhibit_v1_user_inhibitor_destroy( 42void sway_idle_inhibit_v1_user_inhibitor_destroy(
48 struct sway_idle_inhibitor_v1 *inhibitor); 43 struct sway_idle_inhibitor_v1 *inhibitor);
49 44
50struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create( 45bool sway_idle_inhibit_manager_v1_init(void);
51 struct wl_display *wl_display, struct wlr_idle *idle); 46
52#endif 47#endif
diff --git a/include/sway/desktop/launcher.h b/include/sway/desktop/launcher.h
new file mode 100644
index 00000000..b7716e82
--- /dev/null
+++ b/include/sway/desktop/launcher.h
@@ -0,0 +1,36 @@
1#ifndef _SWAY_LAUNCHER_H
2#define _SWAY_LAUNCHER_H
3
4#include <stdlib.h>
5#include <wayland-server-core.h>
6
7struct launcher_ctx {
8 pid_t pid;
9 char *fallback_name;
10 struct wlr_xdg_activation_token_v1 *token;
11 struct wl_listener token_destroy;
12
13 bool activated;
14
15 struct sway_node *node;
16 struct wl_listener node_destroy;
17
18 struct wl_list link; // sway_server::pending_launcher_ctxs
19};
20
21struct launcher_ctx *launcher_ctx_find_pid(pid_t pid);
22
23struct sway_workspace *launcher_ctx_get_workspace(struct launcher_ctx *ctx);
24
25void launcher_ctx_consume(struct launcher_ctx *ctx);
26
27void launcher_ctx_destroy(struct launcher_ctx *ctx);
28
29struct launcher_ctx *launcher_ctx_create_internal(void);
30
31struct launcher_ctx *launcher_ctx_create(
32 struct wlr_xdg_activation_token_v1 *token, struct sway_node *node);
33
34const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx);
35
36#endif
diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h
index 175489c5..17d41fa3 100644
--- a/include/sway/desktop/transaction.h
+++ b/include/sway/desktop/transaction.h
@@ -1,6 +1,7 @@
1#ifndef _SWAY_TRANSACTION_H 1#ifndef _SWAY_TRANSACTION_H
2#define _SWAY_TRANSACTION_H 2#define _SWAY_TRANSACTION_H
3#include <stdint.h> 3#include <stdint.h>
4#include <stdbool.h>
4 5
5/** 6/**
6 * Transactions enable us to perform atomic layout updates. 7 * Transactions enable us to perform atomic layout updates.
@@ -28,12 +29,21 @@ struct sway_view;
28 */ 29 */
29void transaction_commit_dirty(void); 30void transaction_commit_dirty(void);
30 31
32/*
33 * Same as transaction_commit_dirty, but signalling that this is a
34 * client-initiated change has already taken effect.
35 */
36void transaction_commit_dirty_client(void);
37
31/** 38/**
32 * Notify the transaction system that a view is ready for the new layout. 39 * Notify the transaction system that a view is ready for the new layout.
33 * 40 *
34 * When all views in the transaction are ready, the layout will be applied. 41 * When all views in the transaction are ready, the layout will be applied.
42 *
43 * A success boolean is returned denoting that this part of the transaction is
44 * ready.
35 */ 45 */
36void transaction_notify_view_ready_by_serial(struct sway_view *view, 46bool transaction_notify_view_ready_by_serial(struct sway_view *view,
37 uint32_t serial); 47 uint32_t serial);
38 48
39/** 49/**
@@ -41,14 +51,11 @@ void transaction_notify_view_ready_by_serial(struct sway_view *view,
41 * identifying the instruction by geometry rather than by serial. 51 * identifying the instruction by geometry rather than by serial.
42 * 52 *
43 * This is used by xwayland views, as they don't have serials. 53 * This is used by xwayland views, as they don't have serials.
54 *
55 * A success boolean is returned denoting that this part of the transaction is
56 * ready.
44 */ 57 */
45void transaction_notify_view_ready_by_geometry(struct sway_view *view, 58bool transaction_notify_view_ready_by_geometry(struct sway_view *view,
46 double x, double y, int width, int height); 59 double x, double y, int width, int height);
47 60
48/**
49 * Unconditionally notify the transaction system that a view is ready for the
50 * new layout.
51 */
52void transaction_notify_view_ready_immediately(struct sway_view *view);
53
54#endif 61#endif
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 6a38190b..1e21c66f 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -4,7 +4,7 @@
4#include <stdint.h> 4#include <stdint.h>
5#include <wlr/types/wlr_pointer_constraints_v1.h> 5#include <wlr/types/wlr_pointer_constraints_v1.h>
6#include <wlr/types/wlr_pointer_gestures_v1.h> 6#include <wlr/types/wlr_pointer_gestures_v1.h>
7#include <wlr/types/wlr_surface.h> 7#include <wlr/types/wlr_compositor.h>
8#include "sway/input/seat.h" 8#include "sway/input/seat.h"
9#include "config.h" 9#include "config.h"
10 10
@@ -35,7 +35,8 @@ struct sway_cursor {
35 pixman_region32_t confine; // invalid if active_constraint == NULL 35 pixman_region32_t confine; // invalid if active_constraint == NULL
36 bool active_confine_requires_warp; 36 bool active_confine_requires_warp;
37 37
38 struct wlr_pointer_gestures_v1 *pointer_gestures; 38 struct wl_listener hold_begin;
39 struct wl_listener hold_end;
39 struct wl_listener pinch_begin; 40 struct wl_listener pinch_begin;
40 struct wl_listener pinch_update; 41 struct wl_listener pinch_update;
41 struct wl_listener pinch_end; 42 struct wl_listener pinch_end;
@@ -51,8 +52,11 @@ struct sway_cursor {
51 52
52 struct wl_listener touch_down; 53 struct wl_listener touch_down;
53 struct wl_listener touch_up; 54 struct wl_listener touch_up;
55 struct wl_listener touch_cancel;
54 struct wl_listener touch_motion; 56 struct wl_listener touch_motion;
57 struct wl_listener touch_frame;
55 bool simulating_pointer_from_touch; 58 bool simulating_pointer_from_touch;
59 bool pointer_touch_up;
56 int32_t pointer_touch_id; 60 int32_t pointer_touch_id;
57 61
58 struct wl_listener tool_axis; 62 struct wl_listener tool_axis;
@@ -60,6 +64,7 @@ struct sway_cursor {
60 struct wl_listener tool_proximity; 64 struct wl_listener tool_proximity;
61 struct wl_listener tool_button; 65 struct wl_listener tool_button;
62 bool simulating_pointer_from_tool_tip; 66 bool simulating_pointer_from_tool_tip;
67 bool simulating_pointer_from_tool_button;
63 uint32_t tool_buttons; 68 uint32_t tool_buttons;
64 69
65 struct wl_listener request_set_cursor; 70 struct wl_listener request_set_cursor;
@@ -103,12 +108,16 @@ void cursor_unhide(struct sway_cursor *cursor);
103int cursor_get_timeout(struct sway_cursor *cursor); 108int cursor_get_timeout(struct sway_cursor *cursor);
104void cursor_notify_key_press(struct sway_cursor *cursor); 109void cursor_notify_key_press(struct sway_cursor *cursor);
105 110
111void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
112 struct wlr_input_device *device, double dx, double dy,
113 double dx_unaccel, double dy_unaccel);
114
106void dispatch_cursor_button(struct sway_cursor *cursor, 115void dispatch_cursor_button(struct sway_cursor *cursor,
107 struct wlr_input_device *device, uint32_t time_msec, uint32_t button, 116 struct wlr_input_device *device, uint32_t time_msec, uint32_t button,
108 enum wlr_button_state state); 117 enum wlr_button_state state);
109 118
110void dispatch_cursor_axis(struct sway_cursor *cursor, 119void dispatch_cursor_axis(struct sway_cursor *cursor,
111 struct wlr_event_pointer_axis *event); 120 struct wlr_pointer_axis_event *event);
112 121
113void cursor_set_image(struct sway_cursor *cursor, const char *image, 122void cursor_set_image(struct sway_cursor *cursor, const char *image,
114 struct wl_client *client); 123 struct wl_client *client);
@@ -136,4 +145,6 @@ uint32_t get_mouse_button(const char *name, char **error);
136 145
137const char *get_mouse_button_name(uint32_t button); 146const char *get_mouse_button_name(uint32_t button);
138 147
148void handle_request_set_cursor_shape(struct wl_listener *listener, void *data);
149
139#endif 150#endif
diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h
index c9bd08f0..145edd4b 100644
--- a/include/sway/input/input-manager.h
+++ b/include/sway/input/input-manager.h
@@ -1,7 +1,6 @@
1#ifndef _SWAY_INPUT_INPUT_MANAGER_H 1#ifndef _SWAY_INPUT_INPUT_MANAGER_H
2#define _SWAY_INPUT_INPUT_MANAGER_H 2#define _SWAY_INPUT_INPUT_MANAGER_H
3#include <libinput.h> 3#include <libinput.h>
4#include <wlr/types/wlr_input_inhibitor.h>
5#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h> 4#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h>
6#include <wlr/types/wlr_virtual_keyboard_v1.h> 5#include <wlr/types/wlr_virtual_keyboard_v1.h>
7#include <wlr/types/wlr_virtual_pointer_v1.h> 6#include <wlr/types/wlr_virtual_pointer_v1.h>
@@ -21,10 +20,10 @@ struct sway_input_manager {
21 struct wl_list devices; 20 struct wl_list devices;
22 struct wl_list seats; 21 struct wl_list seats;
23 22
24 struct wlr_input_inhibit_manager *inhibit;
25 struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit; 23 struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit;
26 struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; 24 struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard;
27 struct wlr_virtual_pointer_manager_v1 *virtual_pointer; 25 struct wlr_virtual_pointer_manager_v1 *virtual_pointer;
26 struct wlr_pointer_gestures_v1 *pointer_gestures;
28 27
29 struct wl_listener new_input; 28 struct wl_listener new_input;
30 struct wl_listener inhibit_activate; 29 struct wl_listener inhibit_activate;
@@ -44,7 +43,7 @@ void input_manager_configure_xcursor(void);
44 43
45void input_manager_apply_input_config(struct input_config *input_config); 44void input_manager_apply_input_config(struct input_config *input_config);
46 45
47void input_manager_configure_all_inputs(void); 46void input_manager_configure_all_input_mappings(void);
48 47
49void input_manager_reset_input(struct sway_input_device *input_device); 48void input_manager_reset_input(struct sway_input_device *input_device);
50 49
diff --git a/include/sway/input/keyboard.h b/include/sway/input/keyboard.h
index 2c61e5a7..571d9e6f 100644
--- a/include/sway/input/keyboard.h
+++ b/include/sway/input/keyboard.h
@@ -50,6 +50,7 @@ struct sway_shortcut_state {
50 50
51struct sway_keyboard { 51struct sway_keyboard {
52 struct sway_seat_device *seat_device; 52 struct sway_seat_device *seat_device;
53 struct wlr_keyboard *wlr;
53 54
54 struct xkb_keymap *keymap; 55 struct xkb_keymap *keymap;
55 xkb_layout_index_t effective_layout; 56 xkb_layout_index_t effective_layout;
diff --git a/include/sway/input/libinput.h b/include/sway/input/libinput.h
index de019976..1f84a8e3 100644
--- a/include/sway/input/libinput.h
+++ b/include/sway/input/libinput.h
@@ -2,8 +2,13 @@
2#define _SWAY_INPUT_LIBINPUT_H 2#define _SWAY_INPUT_LIBINPUT_H
3#include "sway/input/input-manager.h" 3#include "sway/input/input-manager.h"
4 4
5void sway_input_configure_libinput_device(struct sway_input_device *device); 5bool sway_input_configure_libinput_device(struct sway_input_device *device);
6
7void sway_input_configure_libinput_device_send_events(
8 struct sway_input_device *device);
6 9
7void sway_input_reset_libinput_device(struct sway_input_device *device); 10void sway_input_reset_libinput_device(struct sway_input_device *device);
8 11
12bool sway_libinput_device_is_builtin(struct sway_input_device *device);
13
9#endif 14#endif
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index 4118df66..e5aa8478 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -3,7 +3,9 @@
3 3
4#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h> 4#include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h>
5#include <wlr/types/wlr_layer_shell_v1.h> 5#include <wlr/types/wlr_layer_shell_v1.h>
6#include <wlr/types/wlr_scene.h>
6#include <wlr/types/wlr_seat.h> 7#include <wlr/types/wlr_seat.h>
8#include <wlr/types/wlr_touch.h>
7#include <wlr/util/edges.h> 9#include <wlr/util/edges.h>
8#include "sway/config.h" 10#include "sway/config.h"
9#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
@@ -18,16 +20,38 @@ struct sway_seatop_impl {
18 enum wlr_button_state state); 20 enum wlr_button_state state);
19 void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec); 21 void (*pointer_motion)(struct sway_seat *seat, uint32_t time_msec);
20 void (*pointer_axis)(struct sway_seat *seat, 22 void (*pointer_axis)(struct sway_seat *seat,
21 struct wlr_event_pointer_axis *event); 23 struct wlr_pointer_axis_event *event);
24 void (*hold_begin)(struct sway_seat *seat,
25 struct wlr_pointer_hold_begin_event *event);
26 void (*hold_end)(struct sway_seat *seat,
27 struct wlr_pointer_hold_end_event *event);
28 void (*pinch_begin)(struct sway_seat *seat,
29 struct wlr_pointer_pinch_begin_event *event);
30 void (*pinch_update)(struct sway_seat *seat,
31 struct wlr_pointer_pinch_update_event *event);
32 void (*pinch_end)(struct sway_seat *seat,
33 struct wlr_pointer_pinch_end_event *event);
34 void (*swipe_begin)(struct sway_seat *seat,
35 struct wlr_pointer_swipe_begin_event *event);
36 void (*swipe_update)(struct sway_seat *seat,
37 struct wlr_pointer_swipe_update_event *event);
38 void (*swipe_end)(struct sway_seat *seat,
39 struct wlr_pointer_swipe_end_event *event);
22 void (*rebase)(struct sway_seat *seat, uint32_t time_msec); 40 void (*rebase)(struct sway_seat *seat, uint32_t time_msec);
41 void (*touch_motion)(struct sway_seat *seat,
42 struct wlr_touch_motion_event *event, double lx, double ly);
43 void (*touch_up)(struct sway_seat *seat,
44 struct wlr_touch_up_event *event);
45 void (*touch_down)(struct sway_seat *seat,
46 struct wlr_touch_down_event *event, double lx, double ly);
47 void (*touch_cancel)(struct sway_seat *seat,
48 struct wlr_touch_cancel_event *event);
23 void (*tablet_tool_motion)(struct sway_seat *seat, 49 void (*tablet_tool_motion)(struct sway_seat *seat,
24 struct sway_tablet_tool *tool, uint32_t time_msec); 50 struct sway_tablet_tool *tool, uint32_t time_msec);
25 void (*tablet_tool_tip)(struct sway_seat *seat, struct sway_tablet_tool *tool, 51 void (*tablet_tool_tip)(struct sway_seat *seat, struct sway_tablet_tool *tool,
26 uint32_t time_msec, enum wlr_tablet_tool_tip_state state); 52 uint32_t time_msec, enum wlr_tablet_tool_tip_state state);
27 void (*end)(struct sway_seat *seat); 53 void (*end)(struct sway_seat *seat);
28 void (*unref)(struct sway_seat *seat, struct sway_container *con); 54 void (*unref)(struct sway_seat *seat, struct sway_container *con);
29 void (*render)(struct sway_seat *seat, struct sway_output *output,
30 pixman_region32_t *damage);
31 bool allow_set_cursor; 55 bool allow_set_cursor;
32}; 56};
33 57
@@ -50,19 +74,6 @@ struct sway_seat_node {
50 struct wl_listener destroy; 74 struct wl_listener destroy;
51}; 75};
52 76
53struct sway_drag_icon {
54 struct sway_seat *seat;
55 struct wlr_drag_icon *wlr_drag_icon;
56 struct wl_list link; // sway_root::drag_icons
57
58 double x, y; // in layout-local coordinates
59
60 struct wl_listener surface_commit;
61 struct wl_listener map;
62 struct wl_listener unmap;
63 struct wl_listener destroy;
64};
65
66struct sway_drag { 77struct sway_drag {
67 struct sway_seat *seat; 78 struct sway_seat *seat;
68 struct wlr_drag *wlr_drag; 79 struct wlr_drag *wlr_drag;
@@ -73,16 +84,23 @@ struct sway_seat {
73 struct wlr_seat *wlr_seat; 84 struct wlr_seat *wlr_seat;
74 struct sway_cursor *cursor; 85 struct sway_cursor *cursor;
75 86
87 // Seat scene tree structure
88 // - scene_tree
89 // - drag icons
90 // - drag icon 1
91 // - drag icon 2
92 // - seatop specific stuff
93 struct wlr_scene_tree *scene_tree;
94 struct wlr_scene_tree *drag_icons;
95
76 bool has_focus; 96 bool has_focus;
77 struct wl_list focus_stack; // list of containers in focus order 97 struct wl_list focus_stack; // list of containers in focus order
78 struct sway_workspace *workspace; 98 struct sway_workspace *workspace;
79 char *prev_workspace_name; // for workspace back_and_forth 99 char *prev_workspace_name; // for workspace back_and_forth
80 100
81 // If the focused layer is set, views cannot receive keyboard focus
82 struct wlr_layer_surface_v1 *focused_layer; 101 struct wlr_layer_surface_v1 *focused_layer;
83 102 // If the exclusive layer is set, views cannot receive keyboard focus
84 // If exclusive_client is set, no other clients will receive input events 103 bool has_exclusive_layer;
85 struct wl_client *exclusive_client;
86 104
87 // Last touch point 105 // Last touch point
88 int32_t touch_id; 106 int32_t touch_id;
@@ -141,6 +159,9 @@ void seat_add_device(struct sway_seat *seat,
141void seat_configure_device(struct sway_seat *seat, 159void seat_configure_device(struct sway_seat *seat,
142 struct sway_input_device *device); 160 struct sway_input_device *device);
143 161
162void seat_configure_device_mapping(struct sway_seat *seat,
163 struct sway_input_device *input_device);
164
144void seat_reset_device(struct sway_seat *seat, 165void seat_reset_device(struct sway_seat *seat,
145 struct sway_input_device *input_device); 166 struct sway_input_device *input_device);
146 167
@@ -171,8 +192,7 @@ void seat_set_focus_surface(struct sway_seat *seat,
171void seat_set_focus_layer(struct sway_seat *seat, 192void seat_set_focus_layer(struct sway_seat *seat,
172 struct wlr_layer_surface_v1 *layer); 193 struct wlr_layer_surface_v1 *layer);
173 194
174void seat_set_exclusive_client(struct sway_seat *seat, 195void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client);
175 struct wl_client *client);
176 196
177struct sway_node *seat_get_focus(struct sway_seat *seat); 197struct sway_node *seat_get_focus(struct sway_seat *seat);
178 198
@@ -231,7 +251,7 @@ void seat_idle_notify_activity(struct sway_seat *seat,
231 251
232bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); 252bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
233 253
234void drag_icon_update_position(struct sway_drag_icon *icon); 254void drag_icons_update_position(struct sway_seat *seat);
235 255
236enum wlr_edges find_resize_edge(struct sway_container *cont, 256enum wlr_edges find_resize_edge(struct sway_container *cont,
237 struct wlr_surface *surface, struct sway_cursor *cursor); 257 struct wlr_surface *surface, struct sway_cursor *cursor);
@@ -239,7 +259,13 @@ enum wlr_edges find_resize_edge(struct sway_container *cont,
239void seatop_begin_default(struct sway_seat *seat); 259void seatop_begin_default(struct sway_seat *seat);
240 260
241void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, 261void seatop_begin_down(struct sway_seat *seat, struct sway_container *con,
242 uint32_t time_msec, int sx, int sy); 262 double sx, double sy);
263
264void seatop_begin_down_on_surface(struct sway_seat *seat,
265 struct wlr_surface *surface, double sx, double sy);
266
267void seatop_begin_touch_down(struct sway_seat *seat, struct wlr_surface *surface,
268 struct wlr_touch_down_event *event, double sx, double sy, double lx, double ly);
243 269
244void seatop_begin_move_floating(struct sway_seat *seat, 270void seatop_begin_move_floating(struct sway_seat *seat,
245 struct sway_container *con); 271 struct sway_container *con);
@@ -271,7 +297,7 @@ void seatop_button(struct sway_seat *seat, uint32_t time_msec,
271void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec); 297void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec);
272 298
273void seatop_pointer_axis(struct sway_seat *seat, 299void seatop_pointer_axis(struct sway_seat *seat,
274 struct wlr_event_pointer_axis *event); 300 struct wlr_pointer_axis_event *event);
275 301
276void seatop_tablet_tool_tip(struct sway_seat *seat, 302void seatop_tablet_tool_tip(struct sway_seat *seat,
277 struct sway_tablet_tool *tool, uint32_t time_msec, 303 struct sway_tablet_tool *tool, uint32_t time_msec,
@@ -280,6 +306,37 @@ void seatop_tablet_tool_tip(struct sway_seat *seat,
280void seatop_tablet_tool_motion(struct sway_seat *seat, 306void seatop_tablet_tool_motion(struct sway_seat *seat,
281 struct sway_tablet_tool *tool, uint32_t time_msec); 307 struct sway_tablet_tool *tool, uint32_t time_msec);
282 308
309void seatop_hold_begin(struct sway_seat *seat,
310 struct wlr_pointer_hold_begin_event *event);
311void seatop_hold_end(struct sway_seat *seat,
312 struct wlr_pointer_hold_end_event *event);
313
314void seatop_pinch_begin(struct sway_seat *seat,
315 struct wlr_pointer_pinch_begin_event *event);
316void seatop_pinch_update(struct sway_seat *seat,
317 struct wlr_pointer_pinch_update_event *event);
318void seatop_pinch_end(struct sway_seat *seat,
319 struct wlr_pointer_pinch_end_event *event);
320
321void seatop_swipe_begin(struct sway_seat *seat,
322 struct wlr_pointer_swipe_begin_event *event);
323void seatop_swipe_update(struct sway_seat *seat,
324 struct wlr_pointer_swipe_update_event *event);
325void seatop_swipe_end(struct sway_seat *seat,
326 struct wlr_pointer_swipe_end_event *event);
327
328void seatop_touch_motion(struct sway_seat *seat,
329 struct wlr_touch_motion_event *event, double lx, double ly);
330
331void seatop_touch_up(struct sway_seat *seat,
332 struct wlr_touch_up_event *event);
333
334void seatop_touch_down(struct sway_seat *seat,
335 struct wlr_touch_down_event *event, double lx, double ly);
336
337void seatop_touch_cancel(struct sway_seat *seat,
338 struct wlr_touch_cancel_event *event);
339
283void seatop_rebase(struct sway_seat *seat, uint32_t time_msec); 340void seatop_rebase(struct sway_seat *seat, uint32_t time_msec);
284 341
285/** 342/**
@@ -294,13 +351,6 @@ void seatop_end(struct sway_seat *seat);
294 */ 351 */
295void seatop_unref(struct sway_seat *seat, struct sway_container *con); 352void seatop_unref(struct sway_seat *seat, struct sway_container *con);
296 353
297/**
298 * Instructs a seatop to render anything that it needs to render
299 * (eg. dropzone for move-tiling)
300 */
301void seatop_render(struct sway_seat *seat, struct sway_output *output,
302 pixman_region32_t *damage);
303
304bool seatop_allows_set_cursor(struct sway_seat *seat); 354bool seatop_allows_set_cursor(struct sway_seat *seat);
305 355
306/** 356/**
diff --git a/include/sway/input/switch.h b/include/sway/input/switch.h
index 213b471d..de6787b7 100644
--- a/include/sway/input/switch.h
+++ b/include/sway/input/switch.h
@@ -5,6 +5,7 @@
5 5
6struct sway_switch { 6struct sway_switch {
7 struct sway_seat_device *seat_device; 7 struct sway_seat_device *seat_device;
8 struct wlr_switch *wlr;
8 enum wlr_switch_state state; 9 enum wlr_switch_state state;
9 enum wlr_switch_type type; 10 enum wlr_switch_type type;
10 11
diff --git a/include/sway/input/tablet.h b/include/sway/input/tablet.h
index d7e4c242..2fa5db6d 100644
--- a/include/sway/input/tablet.h
+++ b/include/sway/input/tablet.h
@@ -32,6 +32,7 @@ struct sway_tablet_pad {
32 struct wl_list link; 32 struct wl_list link;
33 struct sway_seat_device *seat_device; 33 struct sway_seat_device *seat_device;
34 struct sway_tablet *tablet; 34 struct sway_tablet *tablet;
35 struct wlr_tablet_pad *wlr;
35 struct wlr_tablet_v2_tablet_pad *tablet_v2_pad; 36 struct wlr_tablet_v2_tablet_pad *tablet_v2_pad;
36 37
37 struct wl_listener attach; 38 struct wl_listener attach;
@@ -62,7 +63,7 @@ void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad);
62 63
63void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad); 64void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad);
64 65
65void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad, 66void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad,
66 struct wlr_surface *surface); 67 struct wlr_surface *surface);
67 68
68#endif 69#endif
diff --git a/include/sway/input/text_input.h b/include/sway/input/text_input.h
index 6cf9bdb3..214e61d1 100644
--- a/include/sway/input/text_input.h
+++ b/include/sway/input/text_input.h
@@ -3,13 +3,12 @@
3 3
4#include <wlr/types/wlr_text_input_v3.h> 4#include <wlr/types/wlr_text_input_v3.h>
5#include <wlr/types/wlr_input_method_v2.h> 5#include <wlr/types/wlr_input_method_v2.h>
6#include <wlr/types/wlr_surface.h> 6#include <wlr/types/wlr_compositor.h>
7#include "sway/input/seat.h"
8 7
9/** 8/**
10 * The relay structure manages the relationship between text-input and 9 * The relay structure manages the relationship between text-input and
11 * input_method interfaces on a given seat. Multiple text-input interfaces may 10 * input_method interfaces on a given seat. Multiple text-input interfaces may
12 * be bound to a relay, but at most one will be focused (reveiving events) at 11 * be bound to a relay, but at most one will be focused (receiving events) at
13 * a time. At most one input-method interface may be bound to the seat. The 12 * a time. At most one input-method interface may be bound to the seat. The
14 * relay manages life cycle of both sides. When both sides are present and 13 * relay manages life cycle of both sides. When both sides are present and
15 * focused, the relay passes messages between them. 14 * focused, the relay passes messages between them.
@@ -28,7 +27,10 @@ struct sway_input_method_relay {
28 27
29 struct wl_listener input_method_new; 28 struct wl_listener input_method_new;
30 struct wl_listener input_method_commit; 29 struct wl_listener input_method_commit;
30 struct wl_listener input_method_grab_keyboard;
31 struct wl_listener input_method_destroy; 31 struct wl_listener input_method_destroy;
32
33 struct wl_listener input_method_keyboard_grab_destroy;
32}; 34};
33 35
34struct sway_text_input { 36struct sway_text_input {
diff --git a/include/sway/ipc-json.h b/include/sway/ipc-json.h
index 6f4ade1a..bc9f4985 100644
--- a/include/sway/ipc-json.h
+++ b/include/sway/ipc-json.h
@@ -1,6 +1,7 @@
1#ifndef _SWAY_IPC_JSON_H 1#ifndef _SWAY_IPC_JSON_H
2#define _SWAY_IPC_JSON_H 2#define _SWAY_IPC_JSON_H
3#include <json.h> 3#include <json.h>
4#include "sway/output.h"
4#include "sway/tree/container.h" 5#include "sway/tree/container.h"
5#include "sway/input/input-manager.h" 6#include "sway/input/input-manager.h"
6 7
@@ -9,6 +10,7 @@ json_object *ipc_json_get_version(void);
9json_object *ipc_json_get_binding_mode(void); 10json_object *ipc_json_get_binding_mode(void);
10 11
11json_object *ipc_json_describe_disabled_output(struct sway_output *o); 12json_object *ipc_json_describe_disabled_output(struct sway_output *o);
13json_object *ipc_json_describe_non_desktop_output(struct sway_output_non_desktop *o);
12json_object *ipc_json_describe_node(struct sway_node *node); 14json_object *ipc_json_describe_node(struct sway_node *node);
13json_object *ipc_json_describe_node_recursive(struct sway_node *node); 15json_object *ipc_json_describe_node_recursive(struct sway_node *node);
14json_object *ipc_json_describe_input(struct sway_input_device *device); 16json_object *ipc_json_describe_input(struct sway_input_device *device);
diff --git a/include/sway/ipc-server.h b/include/sway/ipc-server.h
index bc4f781a..d4c00942 100644
--- a/include/sway/ipc-server.h
+++ b/include/sway/ipc-server.h
@@ -21,5 +21,6 @@ void ipc_event_mode(const char *mode, bool pango);
21void ipc_event_shutdown(const char *reason); 21void ipc_event_shutdown(const char *reason);
22void ipc_event_binding(struct sway_binding *binding); 22void ipc_event_binding(struct sway_binding *binding);
23void ipc_event_input(const char *change, struct sway_input_device *device); 23void ipc_event_input(const char *change, struct sway_input_device *device);
24void ipc_event_output(void);
24 25
25#endif 26#endif
diff --git a/include/sway/layers.h b/include/sway/layers.h
index 457634c2..a7afb900 100644
--- a/include/sway/layers.h
+++ b/include/sway/layers.h
@@ -1,59 +1,41 @@
1#ifndef _SWAY_LAYERS_H 1#ifndef _SWAY_LAYERS_H
2#define _SWAY_LAYERS_H 2#define _SWAY_LAYERS_H
3#include <stdbool.h> 3#include <stdbool.h>
4#include <wlr/types/wlr_box.h> 4#include <wlr/types/wlr_compositor.h>
5#include <wlr/types/wlr_surface.h>
6#include <wlr/types/wlr_layer_shell_v1.h> 5#include <wlr/types/wlr_layer_shell_v1.h>
7 6
8enum layer_parent {
9 LAYER_PARENT_LAYER,
10 LAYER_PARENT_POPUP,
11};
12
13struct sway_layer_surface { 7struct sway_layer_surface {
14 struct wlr_layer_surface_v1 *layer_surface;
15 struct wl_list link;
16
17 struct wl_listener destroy;
18 struct wl_listener map; 8 struct wl_listener map;
19 struct wl_listener unmap; 9 struct wl_listener unmap;
20 struct wl_listener surface_commit; 10 struct wl_listener surface_commit;
21 struct wl_listener output_destroy; 11 struct wl_listener output_destroy;
12 struct wl_listener node_destroy;
22 struct wl_listener new_popup; 13 struct wl_listener new_popup;
23 struct wl_listener new_subsurface;
24 14
25 struct wlr_box geo; 15 bool mapped;
26 enum zwlr_layer_shell_v1_layer layer; 16
17 struct sway_output *output;
18 struct wlr_scene_layer_surface_v1 *scene;
19 struct wlr_scene_tree *tree;
20 struct wlr_scene_tree *popups;
21 struct wlr_layer_surface_v1 *layer_surface;
27}; 22};
28 23
29struct sway_layer_popup { 24struct sway_layer_popup {
30 struct wlr_xdg_popup *wlr_popup; 25 struct wlr_xdg_popup *wlr_popup;
31 enum layer_parent parent_type; 26 struct wlr_scene_tree *scene;
32 union { 27 struct sway_layer_surface *toplevel;
33 struct sway_layer_surface *parent_layer;
34 struct sway_layer_popup *parent_popup;
35 };
36 struct wl_listener map;
37 struct wl_listener unmap;
38 struct wl_listener destroy;
39 struct wl_listener commit;
40 struct wl_listener new_popup;
41};
42 28
43struct sway_layer_subsurface {
44 struct wlr_subsurface *wlr_subsurface;
45 struct sway_layer_surface *layer_surface;
46
47 struct wl_listener map;
48 struct wl_listener unmap;
49 struct wl_listener destroy; 29 struct wl_listener destroy;
30 struct wl_listener new_popup;
50 struct wl_listener commit; 31 struct wl_listener commit;
51}; 32};
52 33
53struct sway_output; 34struct sway_output;
54void arrange_layers(struct sway_output *output);
55 35
56struct sway_layer_surface *layer_from_wlr_layer_surface_v1( 36struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface(
57 struct wlr_layer_surface_v1 *layer_surface); 37 struct wlr_surface *surface);
38
39void arrange_layers(struct sway_output *output);
58 40
59#endif 41#endif
diff --git a/include/sway/output.h b/include/sway/output.h
index 96986700..30595f54 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -3,8 +3,9 @@
3#include <time.h> 3#include <time.h>
4#include <unistd.h> 4#include <unistd.h>
5#include <wayland-server-core.h> 5#include <wayland-server-core.h>
6#include <wlr/types/wlr_box.h> 6#include <wlr/types/wlr_damage_ring.h>
7#include <wlr/types/wlr_output.h> 7#include <wlr/types/wlr_output.h>
8#include <wlr/types/wlr_scene.h>
8#include "config.h" 9#include "config.h"
9#include "sway/tree/node.h" 10#include "sway/tree/node.h"
10#include "sway/tree/view.h" 11#include "sway/tree/view.h"
@@ -19,43 +20,63 @@ struct sway_output_state {
19 20
20struct sway_output { 21struct sway_output {
21 struct sway_node node; 22 struct sway_node node;
23
24 struct {
25 struct wlr_scene_tree *shell_background;
26 struct wlr_scene_tree *shell_bottom;
27 struct wlr_scene_tree *tiling;
28 struct wlr_scene_tree *fullscreen;
29 struct wlr_scene_tree *shell_top;
30 struct wlr_scene_tree *shell_overlay;
31 struct wlr_scene_tree *session_lock;
32 } layers;
33
34 // when a container is fullscreen, in case the fullscreen surface is
35 // translucent (can see behind) we must make sure that the background is a
36 // solid color in order to conform to the wayland protocol. This rect
37 // ensures that when looking through a surface, all that will be seen
38 // is black.
39 struct wlr_scene_rect *fullscreen_background;
40
22 struct wlr_output *wlr_output; 41 struct wlr_output *wlr_output;
42 struct wlr_scene_output *scene_output;
23 struct sway_server *server; 43 struct sway_server *server;
24 struct wl_list link; 44 struct wl_list link;
25 45
26 struct wl_list layers[4]; // sway_layer_surface::link
27 struct wlr_box usable_area; 46 struct wlr_box usable_area;
28 47
29 struct timespec last_frame;
30 struct wlr_output_damage *damage;
31
32 int lx, ly; // layout coords 48 int lx, ly; // layout coords
33 int width, height; // transformed buffer size 49 int width, height; // transformed buffer size
34 enum wl_output_subpixel detected_subpixel; 50 enum wl_output_subpixel detected_subpixel;
35 enum scale_filter_mode scale_filter; 51 enum scale_filter_mode scale_filter;
36 // last applied mode when the output is DPMS'ed
37 struct wlr_output_mode *current_mode;
38 52
39 bool enabling, enabled; 53 bool enabling, enabled;
40 list_t *workspaces; 54 list_t *workspaces;
41 55
42 struct sway_output_state current; 56 struct sway_output_state current;
43 57
58 struct wl_listener layout_destroy;
44 struct wl_listener destroy; 59 struct wl_listener destroy;
45 struct wl_listener commit; 60 struct wl_listener commit;
46 struct wl_listener mode;
47 struct wl_listener present; 61 struct wl_listener present;
48 struct wl_listener damage_destroy; 62 struct wl_listener frame;
49 struct wl_listener damage_frame; 63 struct wl_listener request_state;
50 64
51 struct { 65 struct {
52 struct wl_signal destroy; 66 struct wl_signal disable;
53 } events; 67 } events;
54 68
55 struct timespec last_presentation; 69 struct timespec last_presentation;
56 uint32_t refresh_nsec; 70 uint32_t refresh_nsec;
57 int max_render_time; // In milliseconds 71 int max_render_time; // In milliseconds
58 struct wl_event_source *repaint_timer; 72 struct wl_event_source *repaint_timer;
73 bool gamma_lut_changed;
74};
75
76struct sway_output_non_desktop {
77 struct wlr_output *wlr_output;
78
79 struct wl_listener destroy;
59}; 80};
60 81
61struct sway_output *output_create(struct wlr_output *wlr_output); 82struct sway_output *output_create(struct wlr_output *wlr_output);
@@ -72,22 +93,12 @@ struct sway_output *output_get_in_direction(struct sway_output *reference,
72void output_add_workspace(struct sway_output *output, 93void output_add_workspace(struct sway_output *output,
73 struct sway_workspace *workspace); 94 struct sway_workspace *workspace);
74 95
75typedef void (*sway_surface_iterator_func_t)(struct sway_output *output, struct sway_view *view, 96typedef void (*sway_surface_iterator_func_t)(struct sway_output *output,
76 struct wlr_surface *surface, struct wlr_box *box, float rotation, 97 struct sway_view *view, struct wlr_surface *surface, struct wlr_box *box,
77 void *user_data); 98 void *user_data);
78 99
79void output_damage_whole(struct sway_output *output); 100bool output_match_name_or_id(struct sway_output *output,
80 101 const char *name_or_id);
81void output_damage_surface(struct sway_output *output, double ox, double oy,
82 struct wlr_surface *surface, bool whole);
83
84void output_damage_from_view(struct sway_output *output,
85 struct sway_view *view);
86
87void output_damage_box(struct sway_output *output, struct wlr_box *box);
88
89void output_damage_whole_container(struct sway_output *output,
90 struct sway_container *con);
91 102
92// this ONLY includes the enabled outputs 103// this ONLY includes the enabled outputs
93struct sway_output *output_by_name_or_id(const char *name_or_id); 104struct sway_output *output_by_name_or_id(const char *name_or_id);
@@ -101,47 +112,8 @@ void output_enable(struct sway_output *output);
101 112
102void output_disable(struct sway_output *output); 113void output_disable(struct sway_output *output);
103 114
104bool output_has_opaque_overlay_layer_surface(struct sway_output *output);
105
106struct sway_workspace *output_get_active_workspace(struct sway_output *output); 115struct sway_workspace *output_get_active_workspace(struct sway_output *output);
107 116
108void output_render(struct sway_output *output, struct timespec *when,
109 pixman_region32_t *damage);
110
111void output_surface_for_each_surface(struct sway_output *output,
112 struct wlr_surface *surface, double ox, double oy,
113 sway_surface_iterator_func_t iterator, void *user_data);
114
115void output_view_for_each_surface(struct sway_output *output,
116 struct sway_view *view, sway_surface_iterator_func_t iterator,
117 void *user_data);
118
119void output_view_for_each_popup_surface(struct sway_output *output,
120 struct sway_view *view, sway_surface_iterator_func_t iterator,
121 void *user_data);
122
123void output_layer_for_each_surface(struct sway_output *output,
124 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator,
125 void *user_data);
126
127void output_layer_for_each_toplevel_surface(struct sway_output *output,
128 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator,
129 void *user_data);
130
131void output_layer_for_each_popup_surface(struct sway_output *output,
132 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator,
133 void *user_data);
134
135#if HAVE_XWAYLAND
136void output_unmanaged_for_each_surface(struct sway_output *output,
137 struct wl_list *unmanaged, sway_surface_iterator_func_t iterator,
138 void *user_data);
139#endif
140
141void output_drag_icons_for_each_surface(struct sway_output *output,
142 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator,
143 void *user_data);
144
145void output_for_each_workspace(struct sway_output *output, 117void output_for_each_workspace(struct sway_output *output,
146 void (*f)(struct sway_workspace *ws, void *data), void *data); 118 void (*f)(struct sway_workspace *ws, void *data), void *data);
147 119
@@ -159,18 +131,12 @@ void output_get_box(struct sway_output *output, struct wlr_box *box);
159enum sway_container_layout output_get_default_layout( 131enum sway_container_layout output_get_default_layout(
160 struct sway_output *output); 132 struct sway_output *output);
161 133
162void render_rect(struct sway_output *output,
163 pixman_region32_t *output_damage, const struct wlr_box *_box,
164 float color[static 4]);
165
166void premultiply_alpha(float color[4], float opacity);
167
168void scale_box(struct wlr_box *box, float scale);
169
170enum wlr_direction opposite_direction(enum wlr_direction d); 134enum wlr_direction opposite_direction(enum wlr_direction d);
171 135
172void handle_output_layout_change(struct wl_listener *listener, void *data); 136void handle_output_layout_change(struct wl_listener *listener, void *data);
173 137
138void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data);
139
174void handle_output_manager_apply(struct wl_listener *listener, void *data); 140void handle_output_manager_apply(struct wl_listener *listener, void *data);
175 141
176void handle_output_manager_test(struct wl_listener *listener, void *data); 142void handle_output_manager_test(struct wl_listener *listener, void *data);
@@ -178,4 +144,6 @@ void handle_output_manager_test(struct wl_listener *listener, void *data);
178void handle_output_power_manager_set_mode(struct wl_listener *listener, 144void handle_output_power_manager_set_mode(struct wl_listener *listener,
179 void *data); 145 void *data);
180 146
147struct sway_output_non_desktop *output_non_desktop_create(struct wlr_output *wlr_output);
148
181#endif 149#endif
diff --git a/include/sway/scene_descriptor.h b/include/sway/scene_descriptor.h
new file mode 100644
index 00000000..2649d7c2
--- /dev/null
+++ b/include/sway/scene_descriptor.h
@@ -0,0 +1,33 @@
1/**
2 * Across a wayland compositor, there are multiple shells: It can be
3 * a toplevel, or a layer_shell, or even something more meta like a drag
4 * icon or highlight indicators when dragging windows around.
5 *
6 * This object lets us store values that represent these modes of operation
7 * and keep track of what object is being represented.
8 */
9#ifndef _SWAY_SCENE_DESCRIPTOR_H
10#define _SWAY_SCENE_DESCRIPTOR_H
11#include <wlr/types/wlr_scene.h>
12
13enum sway_scene_descriptor_type {
14 SWAY_SCENE_DESC_BUFFER_TIMER,
15 SWAY_SCENE_DESC_NON_INTERACTIVE,
16 SWAY_SCENE_DESC_CONTAINER,
17 SWAY_SCENE_DESC_VIEW,
18 SWAY_SCENE_DESC_LAYER_SHELL,
19 SWAY_SCENE_DESC_XWAYLAND_UNMANAGED,
20 SWAY_SCENE_DESC_POPUP,
21 SWAY_SCENE_DESC_DRAG_ICON,
22};
23
24bool scene_descriptor_assign(struct wlr_scene_node *node,
25 enum sway_scene_descriptor_type type, void *data);
26
27void *scene_descriptor_try_get(struct wlr_scene_node *node,
28 enum sway_scene_descriptor_type type);
29
30void scene_descriptor_destroy(struct wlr_scene_node *node,
31 enum sway_scene_descriptor_type type);
32
33#endif
diff --git a/include/sway/server.h b/include/sway/server.h
index 0f5e3ab2..adb62cda 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -3,38 +3,59 @@
3#include <stdbool.h> 3#include <stdbool.h>
4#include <wayland-server-core.h> 4#include <wayland-server-core.h>
5#include <wlr/backend.h> 5#include <wlr/backend.h>
6#include <wlr/backend/session.h> 6#include <wlr/render/allocator.h>
7#include <wlr/render/wlr_renderer.h> 7#include <wlr/render/wlr_renderer.h>
8#include <wlr/types/wlr_compositor.h> 8#include <wlr/types/wlr_compositor.h>
9#include <wlr/types/wlr_data_device.h> 9#include <wlr/types/wlr_data_device.h>
10#include <wlr/types/wlr_input_method_v2.h> 10#include <wlr/types/wlr_input_method_v2.h>
11#include <wlr/types/wlr_foreign_toplevel_management_v1.h> 11#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
12#include <wlr/types/wlr_drm_lease_v1.h>
12#include <wlr/types/wlr_layer_shell_v1.h> 13#include <wlr/types/wlr_layer_shell_v1.h>
13#include <wlr/types/wlr_output_management_v1.h> 14#include <wlr/types/wlr_output_management_v1.h>
14#include <wlr/types/wlr_output_power_management_v1.h> 15#include <wlr/types/wlr_output_power_management_v1.h>
15#include <wlr/types/wlr_presentation_time.h> 16#include <wlr/types/wlr_presentation_time.h>
16#include <wlr/types/wlr_relative_pointer_v1.h> 17#include <wlr/types/wlr_relative_pointer_v1.h>
18#include <wlr/types/wlr_session_lock_v1.h>
17#include <wlr/types/wlr_server_decoration.h> 19#include <wlr/types/wlr_server_decoration.h>
18#include <wlr/types/wlr_text_input_v3.h> 20#include <wlr/types/wlr_text_input_v3.h>
19#include <wlr/types/wlr_xdg_shell.h> 21#include <wlr/types/wlr_xdg_shell.h>
20#include "config.h" 22#include "config.h"
21#include "list.h" 23#include "list.h"
24#include "sway/desktop/idle_inhibit_v1.h"
22#if HAVE_XWAYLAND 25#if HAVE_XWAYLAND
23#include "sway/xwayland.h" 26#include "sway/xwayland.h"
24#endif 27#endif
25 28
29struct sway_transaction;
30
31struct sway_session_lock {
32 struct wlr_session_lock_v1 *lock;
33 struct wlr_surface *focused;
34 bool abandoned;
35
36 struct wl_list outputs; // struct sway_session_lock_output
37
38 // invalid if the session is abandoned
39 struct wl_listener new_surface;
40 struct wl_listener unlock;
41 struct wl_listener destroy;
42};
43
26struct sway_server { 44struct sway_server {
27 struct wl_display *wl_display; 45 struct wl_display *wl_display;
28 struct wl_event_loop *wl_event_loop; 46 struct wl_event_loop *wl_event_loop;
29 const char *socket; 47 const char *socket;
30 48
31 struct wlr_backend *backend; 49 struct wlr_backend *backend;
32 struct wlr_backend *noop_backend; 50 struct wlr_session *session;
33 // secondary headless backend used for creating virtual outputs on-the-fly 51 // secondary headless backend used for creating virtual outputs on-the-fly
34 struct wlr_backend *headless_backend; 52 struct wlr_backend *headless_backend;
53 struct wlr_renderer *renderer;
54 struct wlr_allocator *allocator;
35 55
36 struct wlr_compositor *compositor; 56 struct wlr_compositor *compositor;
37 struct wl_listener compositor_new_surface; 57
58 struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1;
38 59
39 struct wlr_data_device_manager *data_device_manager; 60 struct wlr_data_device_manager *data_device_manager;
40 61
@@ -43,14 +64,14 @@ struct sway_server {
43 struct wl_listener new_output; 64 struct wl_listener new_output;
44 struct wl_listener output_layout_change; 65 struct wl_listener output_layout_change;
45 66
46 struct wlr_idle *idle; 67 struct wlr_idle_notifier_v1 *idle_notifier_v1;
47 struct sway_idle_inhibit_manager_v1 *idle_inhibit_manager_v1; 68 struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1;
48 69
49 struct wlr_layer_shell_v1 *layer_shell; 70 struct wlr_layer_shell_v1 *layer_shell;
50 struct wl_listener layer_shell_surface; 71 struct wl_listener layer_shell_surface;
51 72
52 struct wlr_xdg_shell *xdg_shell; 73 struct wlr_xdg_shell *xdg_shell;
53 struct wl_listener xdg_shell_surface; 74 struct wl_listener xdg_shell_toplevel;
54 75
55 struct wlr_tablet_manager_v2 *tablet_v2; 76 struct wlr_tablet_manager_v2 *tablet_v2;
56 77
@@ -70,7 +91,8 @@ struct sway_server {
70 struct wl_listener xdg_decoration; 91 struct wl_listener xdg_decoration;
71 struct wl_list xdg_decorations; // sway_xdg_decoration::link 92 struct wl_list xdg_decorations; // sway_xdg_decoration::link
72 93
73 struct wlr_presentation *presentation; 94 struct wlr_drm_lease_v1_manager *drm_lease_manager;
95 struct wl_listener drm_lease_request;
74 96
75 struct wlr_pointer_constraints_v1 *pointer_constraints; 97 struct wlr_pointer_constraints_v1 *pointer_constraints;
76 struct wl_listener pointer_constraint; 98 struct wl_listener pointer_constraint;
@@ -79,14 +101,52 @@ struct sway_server {
79 struct wl_listener output_manager_apply; 101 struct wl_listener output_manager_apply;
80 struct wl_listener output_manager_test; 102 struct wl_listener output_manager_test;
81 103
104 struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1;
105 struct wl_listener gamma_control_set_gamma;
106
107 struct {
108 struct sway_session_lock *lock;
109 struct wlr_session_lock_manager_v1 *manager;
110
111 struct wl_listener new_lock;
112 struct wl_listener manager_destroy;
113 } session_lock;
114
82 struct wlr_output_power_manager_v1 *output_power_manager_v1; 115 struct wlr_output_power_manager_v1 *output_power_manager_v1;
83 struct wl_listener output_power_manager_set_mode; 116 struct wl_listener output_power_manager_set_mode;
84 struct wlr_input_method_manager_v2 *input_method; 117 struct wlr_input_method_manager_v2 *input_method;
85 struct wlr_text_input_manager_v3 *text_input; 118 struct wlr_text_input_manager_v3 *text_input;
86 struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; 119 struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager;
120 struct wlr_content_type_manager_v1 *content_type_manager_v1;
121 struct wlr_data_control_manager_v1 *data_control_manager_v1;
122 struct wlr_screencopy_manager_v1 *screencopy_manager_v1;
123 struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1;
124 struct wlr_security_context_manager_v1 *security_context_manager_v1;
125
126 struct wlr_xdg_activation_v1 *xdg_activation_v1;
127 struct wl_listener xdg_activation_v1_request_activate;
128 struct wl_listener xdg_activation_v1_new_token;
87 129
130 struct wl_listener request_set_cursor_shape;
131
132 struct wl_list pending_launcher_ctxs; // launcher_ctx::link
133
134 // The timeout for transactions, after which a transaction is applied
135 // regardless of readiness.
88 size_t txn_timeout_ms; 136 size_t txn_timeout_ms;
89 list_t *transactions; 137
138 // Stores a transaction after it has been committed, but is waiting for
139 // views to ack the new dimensions before being applied. A queued
140 // transaction is frozen and must not have new instructions added to it.
141 struct sway_transaction *queued_transaction;
142
143 // Stores a pending transaction that will be committed once the existing
144 // queued transaction is applied and freed. The pending transaction can be
145 // updated with new instructions as needed.
146 struct sway_transaction *pending_transaction;
147
148 // Stores the nodes that have been marked as "dirty" and will be put into
149 // the pending transaction.
90 list_t *dirty_nodes; 150 list_t *dirty_nodes;
91}; 151};
92 152
@@ -96,34 +156,41 @@ struct sway_debug {
96 bool noatomic; // Ignore atomic layout updates 156 bool noatomic; // Ignore atomic layout updates
97 bool txn_timings; // Log verbose messages about transactions 157 bool txn_timings; // Log verbose messages about transactions
98 bool txn_wait; // Always wait for the timeout before applying 158 bool txn_wait; // Always wait for the timeout before applying
99 159 bool legacy_wl_drm; // Enable the legacy wl_drm interface
100 enum {
101 DAMAGE_DEFAULT, // Default behaviour
102 DAMAGE_HIGHLIGHT, // Highlight regions of the screen being damaged
103 DAMAGE_RERENDER, // Render the full output when any damage occurs
104 } damage;
105}; 160};
106 161
107extern struct sway_debug debug; 162extern struct sway_debug debug;
108 163
109/* Prepares an unprivileged server_init by performing all privileged operations in advance */ 164extern bool allow_unsupported_gpu;
110bool server_privileged_prepare(struct sway_server *server); 165
111bool server_init(struct sway_server *server); 166bool server_init(struct sway_server *server);
112void server_fini(struct sway_server *server); 167void server_fini(struct sway_server *server);
113bool server_start(struct sway_server *server); 168bool server_start(struct sway_server *server);
114void server_run(struct sway_server *server); 169void server_run(struct sway_server *server);
115 170
116void handle_compositor_new_surface(struct wl_listener *listener, void *data); 171void restore_nofile_limit(void);
172
117void handle_new_output(struct wl_listener *listener, void *data); 173void handle_new_output(struct wl_listener *listener, void *data);
118 174
119void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); 175void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data);
120void handle_layer_shell_surface(struct wl_listener *listener, void *data); 176void handle_layer_shell_surface(struct wl_listener *listener, void *data);
121void handle_xdg_shell_surface(struct wl_listener *listener, void *data); 177void sway_session_lock_init(void);
178void sway_session_lock_add_output(struct sway_session_lock *lock,
179 struct sway_output *output);
180bool sway_session_lock_has_surface(struct sway_session_lock *lock,
181 struct wlr_surface *surface);
182void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data);
122#if HAVE_XWAYLAND 183#if HAVE_XWAYLAND
123void handle_xwayland_surface(struct wl_listener *listener, void *data); 184void handle_xwayland_surface(struct wl_listener *listener, void *data);
124#endif 185#endif
125void handle_server_decoration(struct wl_listener *listener, void *data); 186void handle_server_decoration(struct wl_listener *listener, void *data);
126void handle_xdg_decoration(struct wl_listener *listener, void *data); 187void handle_xdg_decoration(struct wl_listener *listener, void *data);
127void handle_pointer_constraint(struct wl_listener *listener, void *data); 188void handle_pointer_constraint(struct wl_listener *listener, void *data);
189void xdg_activation_v1_handle_request_activate(struct wl_listener *listener,
190 void *data);
191void xdg_activation_v1_handle_new_token(struct wl_listener *listener,
192 void *data);
193
194void set_rr_scheduling(void);
128 195
129#endif 196#endif
diff --git a/include/sway/surface.h b/include/sway/surface.h
deleted file mode 100644
index 4da96c02..00000000
--- a/include/sway/surface.h
+++ /dev/null
@@ -1,18 +0,0 @@
1#ifndef _SWAY_SURFACE_H
2#define _SWAY_SURFACE_H
3#include <wlr/types/wlr_surface.h>
4
5struct sway_surface {
6 struct wlr_surface *wlr_surface;
7
8 struct wl_listener destroy;
9
10 /**
11 * This timer can be used for issuing delayed frame done callbacks (for
12 * example, to improve presentation latency). Its handler is set to a
13 * function that issues a frame done callback to this surface.
14 */
15 struct wl_event_source *frame_done_timer;
16};
17
18#endif
diff --git a/include/sway/sway_text_node.h b/include/sway/sway_text_node.h
new file mode 100644
index 00000000..0d4209bb
--- /dev/null
+++ b/include/sway/sway_text_node.h
@@ -0,0 +1,28 @@
1#ifndef _SWAY_BUFFER_H
2#define _SWAY_BUFFER_H
3#include <wlr/types/wlr_scene.h>
4
5struct sway_text_node {
6 int width;
7 int max_width;
8 int height;
9 int baseline;
10 bool pango_markup;
11 float color[4];
12 float background[4];
13
14 struct wlr_scene_node *node;
15};
16
17struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent,
18 char *text, float color[4], bool pango_markup);
19
20void sway_text_node_set_color(struct sway_text_node *node, float color[4]);
21
22void sway_text_node_set_text(struct sway_text_node *node, char *text);
23
24void sway_text_node_set_max_width(struct sway_text_node *node, int max_width);
25
26void sway_text_node_set_background(struct sway_text_node *node, float background[4]);
27
28#endif
diff --git a/include/sway/swaynag.h b/include/sway/swaynag.h
index 74d9ea18..03bd52c3 100644
--- a/include/sway/swaynag.h
+++ b/include/sway/swaynag.h
@@ -1,6 +1,7 @@
1#ifndef _SWAY_SWAYNAG_H 1#ifndef _SWAY_SWAYNAG_H
2#define _SWAY_SWAYNAG_H 2#define _SWAY_SWAYNAG_H
3#include <wayland-server-core.h> 3#include <wayland-server-core.h>
4#include "stringop.h"
4 5
5struct swaynag_instance { 6struct swaynag_instance {
6 struct wl_client *client; 7 struct wl_client *client;
@@ -21,7 +22,7 @@ bool swaynag_spawn(const char *swaynag_command,
21// Write a log message to swaynag->fd[1]. This will fail when swaynag->detailed 22// Write a log message to swaynag->fd[1]. This will fail when swaynag->detailed
22// is false. 23// is false.
23void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag, 24void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag,
24 const char *fmt, ...); 25 const char *fmt, ...) _SWAY_ATTRIB_PRINTF(3, 4);
25 26
26// If swaynag->detailed, close swaynag->fd[1] so swaynag displays 27// If swaynag->detailed, close swaynag->fd[1] so swaynag displays
27void swaynag_show(struct swaynag_instance *swaynag); 28void swaynag_show(struct swaynag_instance *swaynag);
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index 7e9df59f..93f6bfbb 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -2,8 +2,8 @@
2#define _SWAY_CONTAINER_H 2#define _SWAY_CONTAINER_H
3#include <stdint.h> 3#include <stdint.h>
4#include <sys/types.h> 4#include <sys/types.h>
5#include <wlr/types/wlr_box.h> 5#include <wlr/types/wlr_compositor.h>
6#include <wlr/types/wlr_surface.h> 6#include <wlr/types/wlr_scene.h>
7#include "list.h" 7#include "list.h"
8#include "sway/tree/node.h" 8#include "sway/tree/node.h"
9 9
@@ -46,9 +46,9 @@ struct sway_container_state {
46 46
47 enum sway_fullscreen_mode fullscreen_mode; 47 enum sway_fullscreen_mode fullscreen_mode;
48 48
49 struct sway_workspace *workspace; 49 struct sway_workspace *workspace; // NULL when hidden in the scratchpad
50 struct sway_container *parent; 50 struct sway_container *parent; // NULL if container in root of workspace
51 list_t *children; 51 list_t *children; // struct sway_container
52 52
53 struct sway_container *focused_inactive_child; 53 struct sway_container *focused_inactive_child;
54 bool focused; 54 bool focused;
@@ -60,6 +60,7 @@ struct sway_container_state {
60 bool border_left; 60 bool border_left;
61 bool border_right; 61 bool border_right;
62 62
63 // These are in layout coordinates.
63 double content_x, content_y; 64 double content_x, content_y;
64 double content_width, content_height; 65 double content_width, content_height;
65}; 66};
@@ -68,14 +69,40 @@ struct sway_container {
68 struct sway_node node; 69 struct sway_node node;
69 struct sway_view *view; 70 struct sway_view *view;
70 71
71 // The pending state is the main container properties, and the current state is in the below struct. 72 struct wlr_scene_tree *scene_tree;
72 // This means most places of the code can refer to the main variables (pending state) and it'll just work. 73
74 struct {
75 struct wlr_scene_tree *tree;
76
77 struct wlr_scene_tree *border;
78 struct wlr_scene_tree *background;
79
80 struct sway_text_node *title_text;
81 struct sway_text_node *marks_text;
82 } title_bar;
83
84 struct {
85 struct wlr_scene_tree *tree;
86
87 struct wlr_scene_rect *top;
88 struct wlr_scene_rect *bottom;
89 struct wlr_scene_rect *left;
90 struct wlr_scene_rect *right;
91 } border;
92
93 struct wlr_scene_tree *content_tree;
94 struct wlr_scene_buffer *output_handler;
95
96 struct wl_listener output_enter;
97 struct wl_listener output_leave;
98
73 struct sway_container_state current; 99 struct sway_container_state current;
100 struct sway_container_state pending;
74 101
75 char *title; // The view's title (unformatted) 102 char *title; // The view's title (unformatted)
76 char *formatted_title; // The title displayed in the title bar 103 char *formatted_title; // The title displayed in the title bar
104 int title_width;
77 105
78 enum sway_container_layout layout;
79 enum sway_container_layout prev_split_layout; 106 enum sway_container_layout prev_split_layout;
80 107
81 // Whether stickiness has been enabled on this container. Use 108 // Whether stickiness has been enabled on this container. Use
@@ -86,11 +113,13 @@ struct sway_container {
86 // For C_ROOT, this has no meaning 113 // For C_ROOT, this has no meaning
87 // For other types, this is the position in layout coordinates 114 // For other types, this is the position in layout coordinates
88 // Includes borders 115 // Includes borders
89 double x, y;
90 double width, height;
91 double saved_x, saved_y; 116 double saved_x, saved_y;
92 double saved_width, saved_height; 117 double saved_width, saved_height;
93 118
119 // Used when the view changes to CSD unexpectedly. This will be a non-B_CSD
120 // border which we use to restore when the view returns to SSD.
121 enum sway_container_border saved_border;
122
94 // The share of the space of parent container this container occupies 123 // The share of the space of parent container this container occupies
95 double width_fraction; 124 double width_fraction;
96 double height_fraction; 125 double height_fraction;
@@ -100,55 +129,19 @@ struct sway_container {
100 double child_total_width; 129 double child_total_width;
101 double child_total_height; 130 double child_total_height;
102 131
103 // These are in layout coordinates.
104 double content_x, content_y;
105 int content_width, content_height;
106
107 // In most cases this is the same as the content x and y, but if the view
108 // refuses to resize to the content dimensions then it can be smaller.
109 // These are in layout coordinates.
110 double surface_x, surface_y;
111
112 enum sway_fullscreen_mode fullscreen_mode;
113
114 enum sway_container_border border;
115
116 // Used when the view changes to CSD unexpectedly. This will be a non-B_CSD
117 // border which we use to restore when the view returns to SSD.
118 enum sway_container_border saved_border;
119
120 int border_thickness;
121 bool border_top;
122 bool border_bottom;
123 bool border_left;
124 bool border_right;
125
126 struct sway_workspace *workspace; // NULL when hidden in the scratchpad
127 struct sway_container *parent; // NULL if container in root of workspace
128 list_t *children; // struct sway_container
129
130 // Outputs currently being intersected
131 list_t *outputs; // struct sway_output
132
133 // Indicates that the container is a scratchpad container. 132 // Indicates that the container is a scratchpad container.
134 // Both hidden and visible scratchpad containers have scratchpad=true. 133 // Both hidden and visible scratchpad containers have scratchpad=true.
135 // Hidden scratchpad containers have a NULL parent. 134 // Hidden scratchpad containers have a NULL parent.
136 bool scratchpad; 135 bool scratchpad;
137 136
138 float alpha; 137 // Stores last output size and position for adjusting coordinates of
138 // scratchpad windows.
139 // Unused for non-scratchpad windows.
140 struct wlr_box transform;
139 141
140 struct wlr_texture *title_focused; 142 float alpha;
141 struct wlr_texture *title_focused_inactive;
142 struct wlr_texture *title_unfocused;
143 struct wlr_texture *title_urgent;
144 size_t title_height;
145 size_t title_baseline;
146 143
147 list_t *marks; // char * 144 list_t *marks; // char *
148 struct wlr_texture *marks_focused;
149 struct wlr_texture *marks_focused_inactive;
150 struct wlr_texture *marks_unfocused;
151 struct wlr_texture *marks_urgent;
152 145
153 struct { 146 struct {
154 struct wl_signal destroy; 147 struct wl_signal destroy;
@@ -168,23 +161,15 @@ void container_begin_destroy(struct sway_container *con);
168struct sway_container *container_find_child(struct sway_container *container, 161struct sway_container *container_find_child(struct sway_container *container,
169 bool (*test)(struct sway_container *view, void *data), void *data); 162 bool (*test)(struct sway_container *view, void *data), void *data);
170 163
171/**
172 * Find a container at the given coordinates. Returns the surface and
173 * surface-local coordinates of the given layout coordinates if the container
174 * is a view and the view contains a surface at those coordinates.
175 */
176struct sway_container *container_at(struct sway_workspace *workspace,
177 double lx, double ly, struct wlr_surface **surface,
178 double *sx, double *sy);
179
180struct sway_container *tiling_container_at(
181 struct sway_node *parent, double lx, double ly,
182 struct wlr_surface **surface, double *sx, double *sy);
183
184void container_for_each_child(struct sway_container *container, 164void container_for_each_child(struct sway_container *container,
185 void (*f)(struct sway_container *container, void *data), void *data); 165 void (*f)(struct sway_container *container, void *data), void *data);
186 166
187/** 167/**
168 * Returns the fullscreen container obstructing this container if it exists.
169 */
170struct sway_container *container_obstructing_fullscreen_container(struct sway_container *container);
171
172/**
188 * Returns true if the given container is an ancestor of this container. 173 * Returns true if the given container is an ancestor of this container.
189 */ 174 */
190bool container_has_ancestor(struct sway_container *container, 175bool container_has_ancestor(struct sway_container *container,
@@ -192,18 +177,13 @@ bool container_has_ancestor(struct sway_container *container,
192 177
193void container_update_textures_recursive(struct sway_container *con); 178void container_update_textures_recursive(struct sway_container *con);
194 179
195void container_damage_whole(struct sway_container *container);
196
197void container_reap_empty(struct sway_container *con); 180void container_reap_empty(struct sway_container *con);
198 181
199struct sway_container *container_flatten(struct sway_container *container); 182struct sway_container *container_flatten(struct sway_container *container);
200 183
201void container_update_title_textures(struct sway_container *container); 184void container_update_title_bar(struct sway_container *container);
202 185
203/** 186void container_update_marks(struct sway_container *container);
204 * Calculate the container's title_height property.
205 */
206void container_calculate_title_height(struct sway_container *container);
207 187
208size_t container_build_representation(enum sway_container_layout layout, 188size_t container_build_representation(enum sway_container_layout layout,
209 list_t *children, char *buffer); 189 list_t *children, char *buffer);
@@ -218,6 +198,9 @@ size_t container_titlebar_height(void);
218void floating_calculate_constraints(int *min_width, int *max_width, 198void floating_calculate_constraints(int *min_width, int *max_width,
219 int *min_height, int *max_height); 199 int *min_height, int *max_height);
220 200
201void floating_fix_coordinates(struct sway_container *con,
202 struct wlr_box *old, struct wlr_box *new);
203
221void container_floating_resize_and_center(struct sway_container *con); 204void container_floating_resize_and_center(struct sway_container *con);
222 205
223void container_floating_set_default_size(struct sway_container *con); 206void container_floating_set_default_size(struct sway_container *con);
@@ -231,6 +214,8 @@ void container_set_geometry_from_content(struct sway_container *con);
231/** 214/**
232 * Determine if the given container is itself floating. 215 * Determine if the given container is itself floating.
233 * This will return false for any descendants of a floating container. 216 * This will return false for any descendants of a floating container.
217 *
218 * Uses pending container state.
234 */ 219 */
235bool container_is_floating(struct sway_container *container); 220bool container_is_floating(struct sway_container *container);
236 221
@@ -296,25 +281,12 @@ bool container_is_floating_or_child(struct sway_container *container);
296 */ 281 */
297bool container_is_fullscreen_or_child(struct sway_container *container); 282bool container_is_fullscreen_or_child(struct sway_container *container);
298 283
299/**
300 * Return the output which will be used for scale purposes.
301 * This is the most recently entered output.
302 */
303struct sway_output *container_get_effective_output(struct sway_container *con);
304
305void container_discover_outputs(struct sway_container *con);
306
307enum sway_container_layout container_parent_layout(struct sway_container *con); 284enum sway_container_layout container_parent_layout(struct sway_container *con);
308 285
309enum sway_container_layout container_current_parent_layout(
310 struct sway_container *con);
311
312list_t *container_get_siblings(struct sway_container *container); 286list_t *container_get_siblings(struct sway_container *container);
313 287
314int container_sibling_index(struct sway_container *child); 288int container_sibling_index(struct sway_container *child);
315 289
316list_t *container_get_current_siblings(struct sway_container *container);
317
318void container_handle_fullscreen_reparent(struct sway_container *con); 290void container_handle_fullscreen_reparent(struct sway_container *con);
319 291
320void container_add_child(struct sway_container *parent, 292void container_add_child(struct sway_container *parent,
@@ -362,8 +334,6 @@ bool container_has_mark(struct sway_container *container, char *mark);
362 334
363void container_add_mark(struct sway_container *container, char *mark); 335void container_add_mark(struct sway_container *container, char *mark);
364 336
365void container_update_marks_textures(struct sway_container *container);
366
367void container_raise_floating(struct sway_container *con); 337void container_raise_floating(struct sway_container *con);
368 338
369bool container_is_scratchpad_hidden(struct sway_container *con); 339bool container_is_scratchpad_hidden(struct sway_container *con);
@@ -378,7 +348,7 @@ bool container_is_sticky_or_child(struct sway_container *con);
378 * This will destroy pairs of redundant H/V splits 348 * This will destroy pairs of redundant H/V splits
379 * e.g. H[V[H[app app]] app] -> H[app app app] 349 * e.g. H[V[H[app app]] app] -> H[app app app]
380 * The middle "V[H[" are eliminated by a call to container_squash 350 * The middle "V[H[" are eliminated by a call to container_squash
381 * on the V[ con. It's grandchildren are added to it's parent. 351 * on the V[ con. It's grandchildren are added to its parent.
382 * 352 *
383 * This function is roughly equivalent to i3's tree_flatten here: 353 * This function is roughly equivalent to i3's tree_flatten here:
384 * https://github.com/i3/i3/blob/1f0c628cde40cf87371481041b7197344e0417c6/src/tree.c#L651 354 * https://github.com/i3/i3/blob/1f0c628cde40cf87371481041b7197344e0417c6/src/tree.c#L651
@@ -387,4 +357,10 @@ bool container_is_sticky_or_child(struct sway_container *con);
387 */ 357 */
388int container_squash(struct sway_container *con); 358int container_squash(struct sway_container *con);
389 359
360void container_arrange_title_bar(struct sway_container *con);
361
362void container_update(struct sway_container *con);
363
364void container_update_itself_and_parents(struct sway_container *con);
365
390#endif 366#endif
diff --git a/include/sway/tree/node.h b/include/sway/tree/node.h
index 470ee3b5..e2dbcdf0 100644
--- a/include/sway/tree/node.h
+++ b/include/sway/tree/node.h
@@ -1,6 +1,8 @@
1#ifndef _SWAY_NODE_H 1#ifndef _SWAY_NODE_H
2#define _SWAY_NODE_H 2#define _SWAY_NODE_H
3#include <wayland-server-core.h>
3#include <stdbool.h> 4#include <stdbool.h>
5#include <wlr/types/wlr_scene.h>
4#include "list.h" 6#include "list.h"
5 7
6#define MIN_SANE_W 100 8#define MIN_SANE_W 100
@@ -74,4 +76,15 @@ list_t *node_get_children(struct sway_node *node);
74 76
75bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor); 77bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor);
76 78
79// when destroying a sway tree, it's not known which order the tree will be
80// destroyed. To prevent freeing of scene_nodes recursing up the tree,
81// let's use this helper function to disown them to the staging node.
82void scene_node_disown_children(struct wlr_scene_tree *tree);
83
84// a helper function used to allocate tree nodes. If an allocation failure
85// occurs a flag is flipped that can be checked later to destroy a parent
86// of this scene node preventing memory leaks.
87struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent,
88 bool *failed);
89
77#endif 90#endif
diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h
index e8f4d573..15df0f55 100644
--- a/include/sway/tree/root.h
+++ b/include/sway/tree/root.h
@@ -3,6 +3,7 @@
3#include <wayland-server-core.h> 3#include <wayland-server-core.h>
4#include <wayland-util.h> 4#include <wayland-util.h>
5#include <wlr/types/wlr_output_layout.h> 5#include <wlr/types/wlr_output_layout.h>
6#include <wlr/types/wlr_scene.h>
6#include <wlr/render/wlr_texture.h> 7#include <wlr/render/wlr_texture.h>
7#include "sway/tree/container.h" 8#include "sway/tree/container.h"
8#include "sway/tree/node.h" 9#include "sway/tree/node.h"
@@ -16,10 +17,44 @@ struct sway_root {
16 struct wlr_output_layout *output_layout; 17 struct wlr_output_layout *output_layout;
17 18
18 struct wl_listener output_layout_change; 19 struct wl_listener output_layout_change;
20
21 // scene node layout:
22 // - root
23 // - staging
24 // - layer shell stuff
25 // - tiling
26 // - floating
27 // - fullscreen stuff
28 // - seat stuff
29 // - ext_session_lock
30 struct wlr_scene *root_scene;
31
32 // since wlr_scene nodes can't be orphaned and must always
33 // have a parent, use this staging scene_tree so that a
34 // node always have a valid parent. Nothing in this
35 // staging node will be visible.
36 struct wlr_scene_tree *staging;
37
38 // tree containing all layers the compositor will render. Cursor handling
39 // will end up iterating this tree.
40 struct wlr_scene_tree *layer_tree;
41
42 struct {
43 struct wlr_scene_tree *shell_background;
44 struct wlr_scene_tree *shell_bottom;
45 struct wlr_scene_tree *tiling;
46 struct wlr_scene_tree *floating;
47 struct wlr_scene_tree *shell_top;
48 struct wlr_scene_tree *fullscreen;
49 struct wlr_scene_tree *fullscreen_global;
19#if HAVE_XWAYLAND 50#if HAVE_XWAYLAND
20 struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link 51 struct wlr_scene_tree *unmanaged;
21#endif 52#endif
22 struct wl_list drag_icons; // sway_drag_icon::link 53 struct wlr_scene_tree *shell_overlay;
54 struct wlr_scene_tree *popup;
55 struct wlr_scene_tree *seat;
56 struct wlr_scene_tree *session_lock;
57 } layers;
23 58
24 // Includes disabled outputs 59 // Includes disabled outputs
25 struct wl_list all_outputs; // sway_output::link 60 struct wl_list all_outputs; // sway_output::link
@@ -28,10 +63,11 @@ struct sway_root {
28 double width, height; 63 double width, height;
29 64
30 list_t *outputs; // struct sway_output 65 list_t *outputs; // struct sway_output
66 list_t *non_desktop_outputs; // struct sway_output_non_desktop
31 list_t *scratchpad; // struct sway_container 67 list_t *scratchpad; // struct sway_container
32 68
33 // For when there's no connected outputs 69 // For when there's no connected outputs
34 struct sway_output *noop_output; 70 struct sway_output *fallback_output;
35 71
36 struct sway_container *fullscreen_global; 72 struct sway_container *fullscreen_global;
37 73
@@ -40,7 +76,7 @@ struct sway_root {
40 } events; 76 } events;
41}; 77};
42 78
43struct sway_root *root_create(void); 79struct sway_root *root_create(struct wl_display *display);
44 80
45void root_destroy(struct sway_root *root); 81void root_destroy(struct sway_root *root);
46 82
@@ -68,12 +104,6 @@ void root_scratchpad_show(struct sway_container *con);
68 */ 104 */
69void root_scratchpad_hide(struct sway_container *con); 105void root_scratchpad_hide(struct sway_container *con);
70 106
71struct sway_workspace *root_workspace_for_pid(pid_t pid);
72
73void root_record_workspace_pid(pid_t pid);
74
75void root_remove_workspace_pid(pid_t pid);
76
77void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data), 107void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data),
78 void *data); 108 void *data);
79 109
@@ -91,6 +121,4 @@ struct sway_container *root_find_container(
91 121
92void root_get_box(struct sway_root *root, struct wlr_box *box); 122void root_get_box(struct sway_root *root, struct wlr_box *box);
93 123
94void root_rename_pid_workspaces(const char *old_name, const char *new_name);
95
96#endif 124#endif
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index e071e6c9..3e5a9bfe 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -1,8 +1,9 @@
1#ifndef _SWAY_VIEW_H 1#ifndef _SWAY_VIEW_H
2#define _SWAY_VIEW_H 2#define _SWAY_VIEW_H
3#include <wayland-server-core.h> 3#include <wayland-server-core.h>
4#include <wlr/types/wlr_surface.h> 4#include <wlr/types/wlr_compositor.h>
5#include "config.h" 5#include <wlr/types/wlr_scene.h>
6#include "sway/config.h"
6#if HAVE_XWAYLAND 7#if HAVE_XWAYLAND
7#include <wlr/xwayland.h> 8#include <wlr/xwayland.h>
8#endif 9#endif
@@ -45,10 +46,6 @@ struct sway_view_impl {
45 void (*set_fullscreen)(struct sway_view *view, bool fullscreen); 46 void (*set_fullscreen)(struct sway_view *view, bool fullscreen);
46 void (*set_resizing)(struct sway_view *view, bool resizing); 47 void (*set_resizing)(struct sway_view *view, bool resizing);
47 bool (*wants_floating)(struct sway_view *view); 48 bool (*wants_floating)(struct sway_view *view);
48 void (*for_each_surface)(struct sway_view *view,
49 wlr_surface_iterator_func_t iterator, void *user_data);
50 void (*for_each_popup_surface)(struct sway_view *view,
51 wlr_surface_iterator_func_t iterator, void *user_data);
52 bool (*is_transient_for)(struct sway_view *child, 49 bool (*is_transient_for)(struct sway_view *child,
53 struct sway_view *ancestor); 50 struct sway_view *ancestor);
54 void (*close)(struct sway_view *view); 51 void (*close)(struct sway_view *view);
@@ -56,24 +53,20 @@ struct sway_view_impl {
56 void (*destroy)(struct sway_view *view); 53 void (*destroy)(struct sway_view *view);
57}; 54};
58 55
59struct sway_saved_buffer {
60 struct wlr_client_buffer *buffer;
61 int x, y;
62 int width, height;
63 enum wl_output_transform transform;
64 struct wlr_fbox source_box;
65 struct wl_list link; // sway_view::saved_buffers
66};
67
68struct sway_view { 56struct sway_view {
69 enum sway_view_type type; 57 enum sway_view_type type;
70 const struct sway_view_impl *impl; 58 const struct sway_view_impl *impl;
71 59
60 struct wlr_scene_tree *scene_tree;
61 struct wlr_scene_tree *content_tree;
62 struct wlr_scene_tree *saved_surface_tree;
63
72 struct sway_container *container; // NULL if unmapped and transactions finished 64 struct sway_container *container; // NULL if unmapped and transactions finished
73 struct wlr_surface *surface; // NULL for unmapped views 65 struct wlr_surface *surface; // NULL for unmapped views
74 struct sway_xdg_decoration *xdg_decoration; 66 struct sway_xdg_decoration *xdg_decoration;
75 67
76 pid_t pid; 68 pid_t pid;
69 struct launcher_ctx *ctx;
77 70
78 // The size the view would want to be if it weren't tiled. 71 // The size the view would want to be if it weren't tiled.
79 // Used when changing a view from tiled to floating. 72 // Used when changing a view from tiled to floating.
@@ -87,16 +80,10 @@ struct sway_view {
87 bool allow_request_urgent; 80 bool allow_request_urgent;
88 struct wl_event_source *urgent_timer; 81 struct wl_event_source *urgent_timer;
89 82
90 struct wl_list saved_buffers; // sway_saved_buffer::link
91
92 // The geometry for whatever the client is committing, regardless of 83 // The geometry for whatever the client is committing, regardless of
93 // transaction state. Updated on every commit. 84 // transaction state. Updated on every commit.
94 struct wlr_box geometry; 85 struct wlr_box geometry;
95 86
96 // The "old" geometry during a transaction. Used to damage the old location
97 // when a transaction is applied.
98 struct wlr_box saved_geometry;
99
100 struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; 87 struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel;
101 struct wl_listener foreign_activate_request; 88 struct wl_listener foreign_activate_request;
102 struct wl_listener foreign_fullscreen_request; 89 struct wl_listener foreign_fullscreen_request;
@@ -108,19 +95,16 @@ struct sway_view {
108 list_t *executed_criteria; // struct criteria * 95 list_t *executed_criteria; // struct criteria *
109 96
110 union { 97 union {
111 struct wlr_xdg_surface *wlr_xdg_surface; 98 struct wlr_xdg_toplevel *wlr_xdg_toplevel;
112#if HAVE_XWAYLAND 99#if HAVE_XWAYLAND
113 struct wlr_xwayland_surface *wlr_xwayland_surface; 100 struct wlr_xwayland_surface *wlr_xwayland_surface;
114#endif 101#endif
115 struct wlr_wl_shell_surface *wlr_wl_shell_surface;
116 }; 102 };
117 103
118 struct { 104 struct {
119 struct wl_signal unmap; 105 struct wl_signal unmap;
120 } events; 106 } events;
121 107
122 struct wl_listener surface_new_subsurface;
123
124 int max_render_time; // In milliseconds 108 int max_render_time; // In milliseconds
125 109
126 enum seat_config_shortcuts_inhibit shortcuts_inhibit; 110 enum seat_config_shortcuts_inhibit shortcuts_inhibit;
@@ -145,6 +129,8 @@ struct sway_xdg_shell_view {
145struct sway_xwayland_view { 129struct sway_xwayland_view {
146 struct sway_view view; 130 struct sway_view view;
147 131
132 struct wlr_scene_tree *surface_tree;
133
148 struct wl_listener commit; 134 struct wl_listener commit;
149 struct wl_listener request_move; 135 struct wl_listener request_move;
150 struct wl_listener request_resize; 136 struct wl_listener request_resize;
@@ -156,70 +142,46 @@ struct sway_xwayland_view {
156 struct wl_listener set_title; 142 struct wl_listener set_title;
157 struct wl_listener set_class; 143 struct wl_listener set_class;
158 struct wl_listener set_role; 144 struct wl_listener set_role;
145 struct wl_listener set_startup_id;
159 struct wl_listener set_window_type; 146 struct wl_listener set_window_type;
160 struct wl_listener set_hints; 147 struct wl_listener set_hints;
161 struct wl_listener set_decorations; 148 struct wl_listener set_decorations;
149 struct wl_listener associate;
150 struct wl_listener dissociate;
162 struct wl_listener map; 151 struct wl_listener map;
163 struct wl_listener unmap; 152 struct wl_listener unmap;
164 struct wl_listener destroy; 153 struct wl_listener destroy;
165 struct wl_listener override_redirect; 154 struct wl_listener override_redirect;
155
156 struct wl_listener surface_tree_destroy;
166}; 157};
167 158
168struct sway_xwayland_unmanaged { 159struct sway_xwayland_unmanaged {
169 struct wlr_xwayland_surface *wlr_xwayland_surface; 160 struct wlr_xwayland_surface *wlr_xwayland_surface;
170 struct wl_list link;
171 161
172 int lx, ly; 162 struct wlr_scene_surface *surface_scene;
173 163
164 struct wl_listener request_activate;
174 struct wl_listener request_configure; 165 struct wl_listener request_configure;
175 struct wl_listener request_fullscreen; 166 struct wl_listener request_fullscreen;
176 struct wl_listener commit;
177 struct wl_listener set_geometry; 167 struct wl_listener set_geometry;
168 struct wl_listener associate;
169 struct wl_listener dissociate;
178 struct wl_listener map; 170 struct wl_listener map;
179 struct wl_listener unmap; 171 struct wl_listener unmap;
180 struct wl_listener destroy; 172 struct wl_listener destroy;
181 struct wl_listener override_redirect; 173 struct wl_listener override_redirect;
182}; 174};
183#endif 175#endif
184struct sway_view_child;
185
186struct sway_view_child_impl {
187 void (*get_root_coords)(struct sway_view_child *child, int *sx, int *sy);
188 void (*destroy)(struct sway_view_child *child);
189};
190
191/**
192 * A view child is a surface in the view tree, such as a subsurface or a popup.
193 */
194struct sway_view_child {
195 const struct sway_view_child_impl *impl;
196 struct wl_list link;
197
198 struct sway_view *view;
199 struct sway_view_child *parent;
200 struct wl_list children; // sway_view_child::link
201 struct wlr_surface *surface;
202 bool mapped;
203
204 struct wl_listener surface_commit;
205 struct wl_listener surface_new_subsurface;
206 struct wl_listener surface_map;
207 struct wl_listener surface_unmap;
208 struct wl_listener surface_destroy;
209 struct wl_listener view_unmap;
210};
211
212struct sway_subsurface {
213 struct sway_view_child child;
214
215 struct wl_listener destroy;
216};
217 176
218struct sway_xdg_popup { 177struct sway_xdg_popup {
219 struct sway_view_child child; 178 struct sway_view *view;
220 179
221 struct wlr_xdg_surface *wlr_xdg_surface; 180 struct wlr_scene_tree *scene_tree;
181 struct wlr_scene_tree *xdg_surface_tree;
182 struct wlr_xdg_popup *wlr_xdg_popup;
222 183
184 struct wl_listener surface_commit;
223 struct wl_listener new_popup; 185 struct wl_listener new_popup;
224 struct wl_listener destroy; 186 struct wl_listener destroy;
225}; 187};
@@ -269,7 +231,7 @@ void view_set_activated(struct sway_view *view, bool activated);
269/** 231/**
270 * Called when the view requests to be focused. 232 * Called when the view requests to be focused.
271 */ 233 */
272void view_request_activate(struct sway_view *view); 234void view_request_activate(struct sway_view *view, struct sway_seat *seat);
273 235
274/** 236/**
275 * If possible, instructs the client to change their decoration mode. 237 * If possible, instructs the client to change their decoration mode.
@@ -288,42 +250,31 @@ void view_close(struct sway_view *view);
288 250
289void view_close_popups(struct sway_view *view); 251void view_close_popups(struct sway_view *view);
290 252
291void view_damage_from(struct sway_view *view);
292
293/**
294 * Iterate all surfaces of a view (toplevels + popups).
295 */
296void view_for_each_surface(struct sway_view *view,
297 wlr_surface_iterator_func_t iterator, void *user_data);
298
299/**
300 * Iterate all popup surfaces of a view.
301 */
302void view_for_each_popup_surface(struct sway_view *view,
303 wlr_surface_iterator_func_t iterator, void *user_data);
304
305// view implementation 253// view implementation
306 254
307void view_init(struct sway_view *view, enum sway_view_type type, 255bool view_init(struct sway_view *view, enum sway_view_type type,
308 const struct sway_view_impl *impl); 256 const struct sway_view_impl *impl);
309 257
310void view_destroy(struct sway_view *view); 258void view_destroy(struct sway_view *view);
311 259
312void view_begin_destroy(struct sway_view *view); 260void view_begin_destroy(struct sway_view *view);
313 261
262/**
263 * Map a view, ie. make it visible in the tree.
264 *
265 * `fullscreen` should be set to true (and optionally `fullscreen_output`
266 * should be populated) if the view should be made fullscreen immediately.
267 *
268 * `decoration` should be set to true if the client prefers CSD. The client's
269 * preference may be ignored.
270 */
314void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, 271void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
315 bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); 272 bool fullscreen, struct wlr_output *fullscreen_output, bool decoration);
316 273
317void view_unmap(struct sway_view *view); 274void view_unmap(struct sway_view *view);
318 275
319void view_update_size(struct sway_view *view, int width, int height); 276void view_update_size(struct sway_view *view);
320 277void view_center_and_clip_surface(struct sway_view *view);
321void view_child_init(struct sway_view_child *child,
322 const struct sway_view_child_impl *impl, struct sway_view *view,
323 struct wlr_surface *surface);
324
325void view_child_destroy(struct sway_view_child *child);
326
327 278
328struct sway_view *view_from_wlr_xdg_surface( 279struct sway_view *view_from_wlr_xdg_surface(
329 struct wlr_xdg_surface *xdg_surface); 280 struct wlr_xdg_surface *xdg_surface);
@@ -362,4 +313,8 @@ void view_save_buffer(struct sway_view *view);
362 313
363bool view_is_transient_for(struct sway_view *child, struct sway_view *ancestor); 314bool view_is_transient_for(struct sway_view *child, struct sway_view *ancestor);
364 315
316void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx);
317
318void view_send_frame_done(struct sway_view *view);
319
365#endif 320#endif
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index fdd92f64..58bde20c 100644
--- a/include/sway/tree/workspace.h
+++ b/include/sway/tree/workspace.h
@@ -2,6 +2,8 @@
2#define _SWAY_WORKSPACE_H 2#define _SWAY_WORKSPACE_H
3 3
4#include <stdbool.h> 4#include <stdbool.h>
5#include <wlr/types/wlr_scene.h>
6#include "sway/config.h"
5#include "sway/tree/container.h" 7#include "sway/tree/container.h"
6#include "sway/tree/node.h" 8#include "sway/tree/node.h"
7 9
@@ -22,6 +24,12 @@ struct sway_workspace_state {
22 24
23struct sway_workspace { 25struct sway_workspace {
24 struct sway_node node; 26 struct sway_node node;
27
28 struct {
29 struct wlr_scene_tree *tiling;
30 struct wlr_scene_tree *fullscreen;
31 } layers;
32
25 struct sway_container *fullscreen; 33 struct sway_container *fullscreen;
26 34
27 char *name; 35 char *name;
@@ -60,20 +68,20 @@ void workspace_consider_destroy(struct sway_workspace *ws);
60 68
61char *workspace_next_name(const char *output_name); 69char *workspace_next_name(const char *output_name);
62 70
63bool workspace_switch(struct sway_workspace *workspace, 71struct sway_workspace *workspace_auto_back_and_forth(
64 bool no_auto_back_and_forth); 72 struct sway_workspace *workspace);
73
74bool workspace_switch(struct sway_workspace *workspace);
65 75
66struct sway_workspace *workspace_by_number(const char* name); 76struct sway_workspace *workspace_by_number(const char* name);
67 77
68struct sway_workspace *workspace_by_name(const char*); 78struct sway_workspace *workspace_by_name(const char*);
69 79
70struct sway_workspace *workspace_output_next( 80struct sway_workspace *workspace_output_next(struct sway_workspace *current);
71 struct sway_workspace *current, bool create);
72 81
73struct sway_workspace *workspace_next(struct sway_workspace *current); 82struct sway_workspace *workspace_next(struct sway_workspace *current);
74 83
75struct sway_workspace *workspace_output_prev( 84struct sway_workspace *workspace_output_prev(struct sway_workspace *current);
76 struct sway_workspace *current, bool create);
77 85
78struct sway_workspace *workspace_prev(struct sway_workspace *current); 86struct sway_workspace *workspace_prev(struct sway_workspace *current);
79 87
diff --git a/include/sway/xdg_decoration.h b/include/sway/xdg_decoration.h
index 8bef4c6d..2388ebcb 100644
--- a/include/sway/xdg_decoration.h
+++ b/include/sway/xdg_decoration.h
@@ -16,4 +16,6 @@ struct sway_xdg_decoration {
16struct sway_xdg_decoration *xdg_decoration_from_surface( 16struct sway_xdg_decoration *xdg_decoration_from_surface(
17 struct wlr_surface *surface); 17 struct wlr_surface *surface);
18 18
19void set_xdg_decoration_mode(struct sway_xdg_decoration *deco);
20
19#endif 21#endif
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 545a66a8..197d2190 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -4,6 +4,7 @@
4#include "config.h" 4#include "config.h"
5#include "input.h" 5#include "input.h"
6#include "pool-buffer.h" 6#include "pool-buffer.h"
7#include "cursor-shape-v1-client-protocol.h"
7#include "wlr-layer-shell-unstable-v1-client-protocol.h" 8#include "wlr-layer-shell-unstable-v1-client-protocol.h"
8#include "xdg-output-unstable-v1-client-protocol.h" 9#include "xdg-output-unstable-v1-client-protocol.h"
9 10
@@ -30,6 +31,7 @@ struct swaybar {
30 struct wl_compositor *compositor; 31 struct wl_compositor *compositor;
31 struct zwlr_layer_shell_v1 *layer_shell; 32 struct zwlr_layer_shell_v1 *layer_shell;
32 struct zxdg_output_manager_v1 *xdg_output_manager; 33 struct zxdg_output_manager_v1 *xdg_output_manager;
34 struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
33 struct wl_shm *shm; 35 struct wl_shm *shm;
34 36
35 struct swaybar_config *config; 37 struct swaybar_config *config;
@@ -58,7 +60,6 @@ struct swaybar_output {
58 struct zxdg_output_v1 *xdg_output; 60 struct zxdg_output_v1 *xdg_output;
59 struct wl_surface *surface; 61 struct wl_surface *surface;
60 struct zwlr_layer_surface_v1 *layer_surface; 62 struct zwlr_layer_surface_v1 *layer_surface;
61 struct wl_region *input_region;
62 uint32_t wl_name; 63 uint32_t wl_name;
63 64
64 struct wl_list workspaces; // swaybar_workspace::link 65 struct wl_list workspaces; // swaybar_workspace::link
diff --git a/include/swaybar/config.h b/include/swaybar/config.h
index 4cacd21a..361acd99 100644
--- a/include/swaybar/config.h
+++ b/include/swaybar/config.h
@@ -6,6 +6,7 @@
6#include "../include/config.h" 6#include "../include/config.h"
7#include "list.h" 7#include "list.h"
8#include "util.h" 8#include "util.h"
9#include <pango/pangocairo.h>
9 10
10struct box_colors { 11struct box_colors {
11 uint32_t border; 12 uint32_t border;
@@ -28,7 +29,7 @@ struct swaybar_config {
28 char *status_command; 29 char *status_command;
29 bool pango_markup; 30 bool pango_markup;
30 uint32_t position; // zwlr_layer_surface_v1_anchor 31 uint32_t position; // zwlr_layer_surface_v1_anchor
31 char *font; 32 PangoFontDescription *font_description;
32 char *sep_symbol; 33 char *sep_symbol;
33 char *mode; 34 char *mode;
34 char *hidden_state; 35 char *hidden_state;
diff --git a/include/swaybar/i3bar.h b/include/swaybar/i3bar.h
index df8cdd09..dced2a6c 100644
--- a/include/swaybar/i3bar.h
+++ b/include/swaybar/i3bar.h
@@ -19,6 +19,7 @@ struct i3bar_block {
19 // Airblader features 19 // Airblader features
20 uint32_t background; 20 uint32_t background;
21 uint32_t border; 21 uint32_t border;
22 bool border_set;
22 int border_top; 23 int border_top;
23 int border_bottom; 24 int border_bottom;
24 int border_left; 25 int border_left;
@@ -29,6 +30,6 @@ void i3bar_block_unref(struct i3bar_block *block);
29bool i3bar_handle_readable(struct status_line *status); 30bool i3bar_handle_readable(struct status_line *status);
30enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, 31enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
31 struct i3bar_block *block, double x, double y, double rx, double ry, 32 struct i3bar_block *block, double x, double y, double rx, double ry,
32 double w, double h, int scale, uint32_t button); 33 double w, double h, int scale, uint32_t button, bool released);
33 34
34#endif 35#endif
diff --git a/include/swaybar/image.h b/include/swaybar/image.h
new file mode 100644
index 00000000..53a210dd
--- /dev/null
+++ b/include/swaybar/image.h
@@ -0,0 +1,7 @@
1#ifndef _SWAYBAR_IMAGE_H
2#define _SWAYBAR_IMAGE_H
3#include <cairo.h>
4
5cairo_surface_t *load_image(const char *path);
6
7#endif
diff --git a/include/swaybar/input.h b/include/swaybar/input.h
index e8735d88..8ea88a69 100644
--- a/include/swaybar/input.h
+++ b/include/swaybar/input.h
@@ -49,7 +49,7 @@ struct swaybar_hotspot {
49 int x, y, width, height; 49 int x, y, width, height;
50 enum hotspot_event_handling (*callback)(struct swaybar_output *output, 50 enum hotspot_event_handling (*callback)(struct swaybar_output *output,
51 struct swaybar_hotspot *hotspot, double x, double y, uint32_t button, 51 struct swaybar_hotspot *hotspot, double x, double y, uint32_t button,
52 void *data); 52 bool released, void *data);
53 void (*destroy)(void *data); 53 void (*destroy)(void *data);
54 void *data; 54 void *data;
55}; 55};
diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h
index c02a5582..73937a0c 100644
--- a/include/swaybar/tray/item.h
+++ b/include/swaybar/tray/item.h
@@ -4,6 +4,7 @@
4#include <cairo.h> 4#include <cairo.h>
5#include <stdbool.h> 5#include <stdbool.h>
6#include <stdint.h> 6#include <stdint.h>
7#include <wayland-util.h>
7#include "swaybar/tray/tray.h" 8#include "swaybar/tray/tray.h"
8#include "list.h" 9#include "list.h"
9 10
diff --git a/include/swaynag/swaynag.h b/include/swaynag/swaynag.h
index 9e39e716..fb9e9c21 100644
--- a/include/swaynag/swaynag.h
+++ b/include/swaynag/swaynag.h
@@ -4,8 +4,9 @@
4#include <strings.h> 4#include <strings.h>
5#include "list.h" 5#include "list.h"
6#include "pool-buffer.h" 6#include "pool-buffer.h"
7#include "cursor-shape-v1-client-protocol.h"
8
7#include "swaynag/types.h" 9#include "swaynag/types.h"
8#include "xdg-output-unstable-v1-client-protocol.h"
9 10
10#define SWAYNAG_MAX_HEIGHT 500 11#define SWAYNAG_MAX_HEIGHT 500
11 12
@@ -59,6 +60,7 @@ struct swaynag_button {
59struct swaynag_details { 60struct swaynag_details {
60 bool visible; 61 bool visible;
61 char *message; 62 char *message;
63 char *details_text;
62 64
63 int x; 65 int x;
64 int y; 66 int y;
@@ -75,18 +77,17 @@ struct swaynag_details {
75 77
76struct swaynag { 78struct swaynag {
77 bool run_display; 79 bool run_display;
78 int querying_outputs;
79 80
80 struct wl_display *display; 81 struct wl_display *display;
81 struct wl_compositor *compositor; 82 struct wl_compositor *compositor;
82 struct wl_seat *seat; 83 struct wl_seat *seat;
83 struct wl_shm *shm; 84 struct wl_shm *shm;
84 struct zxdg_output_manager_v1 *xdg_output_manager;
85 struct wl_list outputs; // swaynag_output::link 85 struct wl_list outputs; // swaynag_output::link
86 struct wl_list seats; // swaynag_seat::link 86 struct wl_list seats; // swaynag_seat::link
87 struct swaynag_output *output; 87 struct swaynag_output *output;
88 struct zwlr_layer_shell_v1 *layer_shell; 88 struct zwlr_layer_shell_v1 *layer_shell;
89 struct zwlr_layer_surface_v1 *layer_surface; 89 struct zwlr_layer_surface_v1 *layer_surface;
90 struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
90 struct wl_surface *surface; 91 struct wl_surface *surface;
91 92
92 uint32_t width; 93 uint32_t width;
diff --git a/include/swaynag/types.h b/include/swaynag/types.h
index 24da9418..9c3c50db 100644
--- a/include/swaynag/types.h
+++ b/include/swaynag/types.h
@@ -1,12 +1,17 @@
1#ifndef _SWAYNAG_TYPES_H 1#ifndef _SWAYNAG_TYPES_H
2#define _SWAYNAG_TYPES_H 2#define _SWAYNAG_TYPES_H
3 3
4#include <stdint.h>
5#include <pango/pangocairo.h>
6#include "list.h"
7
4struct swaynag_type { 8struct swaynag_type {
5 char *name; 9 char *name;
6 10
7 char *font; 11 PangoFontDescription *font_description;
8 char *output; 12 char *output;
9 uint32_t anchors; 13 uint32_t anchors;
14 int32_t layer; // enum zwlr_layer_shell_v1_layer or -1 if unset
10 15
11 // Colors 16 // Colors
12 uint32_t button_text; 17 uint32_t button_text;
diff --git a/include/util.h b/include/util.h
index c80da1cb..f887d489 100644
--- a/include/util.h
+++ b/include/util.h
@@ -30,12 +30,6 @@ int parse_movement_amount(int argc, char **argv,
30 struct movement_amount *amount); 30 struct movement_amount *amount);
31 31
32/** 32/**
33 * Get the current time, in milliseconds.
34 */
35
36uint32_t get_current_time_msec(void);
37
38/**
39 * Wrap i into the range [0, max] 33 * Wrap i into the range [0, max]
40 */ 34 */
41int wrap(int i, int max); 35int wrap(int i, int max);
diff --git a/meson.build b/meson.build
index 38a55678..f8bf4f80 100644
--- a/meson.build
+++ b/meson.build
@@ -1,9 +1,9 @@
1project( 1project(
2 'sway', 2 'sway',
3 'c', 3 'c',
4 version: '1.5', #release_version 4 version: '1.10-dev',
5 license: 'MIT', 5 license: 'MIT',
6 meson_version: '>=0.53.0', 6 meson_version: '>=0.60.0',
7 default_options: [ 7 default_options: [
8 'c_std=c11', 8 'c_std=c11',
9 'warning_level=2', 9 'warning_level=2',
@@ -18,6 +18,7 @@ add_project_arguments(
18 '-Wno-unused-parameter', 18 '-Wno-unused-parameter',
19 '-Wno-unused-result', 19 '-Wno-unused-result',
20 '-Wno-missing-braces', 20 '-Wno-missing-braces',
21 '-Wno-format-zero-length',
21 '-Wundef', 22 '-Wundef',
22 '-Wvla', 23 '-Wvla',
23 ], 24 ],
@@ -35,79 +36,65 @@ if is_freebsd
35 add_project_arguments('-D_C11_SOURCE', language: 'c') 36 add_project_arguments('-D_C11_SOURCE', language: 'c')
36endif 37endif
37 38
38jsonc = dependency('json-c', version: '>=0.13') 39# Execute the wlroots subproject, if any
39pcre = dependency('libpcre') 40wlroots_version = ['>=0.18.0', '<0.19.0']
40wayland_server = dependency('wayland-server') 41subproject(
41wayland_client = dependency('wayland-client')
42wayland_cursor = dependency('wayland-cursor')
43wayland_egl = dependency('wayland-egl')
44wayland_protos = dependency('wayland-protocols', version: '>=1.14')
45xkbcommon = dependency('xkbcommon')
46cairo = dependency('cairo')
47pango = dependency('pango')
48pangocairo = dependency('pangocairo')
49gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
50pixman = dependency('pixman-1')
51glesv2 = dependency('glesv2')
52libevdev = dependency('libevdev')
53libinput = dependency('libinput', version: '>=1.6.0')
54xcb = dependency('xcb', required: get_option('xwayland'))
55bash_comp = dependency('bash-completion', required: false)
56fish_comp = dependency('fish', required: false)
57math = cc.find_library('m')
58rt = cc.find_library('rt')
59
60# Try first to find wlroots as a subproject, then as a system dependency
61wlroots_version = ['>=0.12.0', '<0.13.0']
62wlroots_proj = subproject(
63 'wlroots', 42 'wlroots',
64 default_options: ['examples=false'], 43 default_options: ['examples=false'],
65 required: false, 44 required: false,
66 version: wlroots_version, 45 version: wlroots_version,
67) 46)
47wlroots = dependency('wlroots', version: wlroots_version)
68wlroots_features = { 48wlroots_features = {
69 'xwayland': false, 49 'xwayland': false,
70 'systemd': false, 50 'libinput_backend': false,
71 'elogind': false, 51 'session': false,
72 'libseat': false,
73} 52}
74if wlroots_proj.found() 53foreach name, _ : wlroots_features
75 wlroots = wlroots_proj.get_variable('wlroots') 54 var_name = 'have_' + name.underscorify()
76 wlroots_conf = wlroots_proj.get_variable('conf_data') 55 have = wlroots.get_variable(pkgconfig: var_name, internal: var_name) == 'true'
77 foreach name, _ : wlroots_features 56 wlroots_features += { name: have }
78 has = wlroots_conf.get('WLR_HAS_' + name.to_upper()) == 1 57endforeach
79 wlroots_features += { name: has }
80 endforeach
81else
82 wlroots = dependency('wlroots', version: wlroots_version)
83 foreach name, _ : wlroots_features
84 has = cc.get_define('WLR_HAS_' + name.to_upper(), prefix: '#include <wlr/config.h>', dependencies: wlroots) == '1'
85 wlroots_features += { name: has }
86 endforeach
87endif
88 58
89if get_option('xwayland').enabled() and not wlroots_features['xwayland'] 59if get_option('xwayland').enabled() and not wlroots_features['xwayland']
90 error('Cannot enable Xwayland in sway: wlroots has been built without Xwayland support') 60 error('Cannot enable Xwayland in sway: wlroots has been built without Xwayland support')
91endif 61endif
92have_xwayland = xcb.found() and wlroots_features['xwayland'] 62
63null_dep = dependency('', required: false)
64
65jsonc = dependency('json-c', version: '>=0.13')
66pcre2 = dependency('libpcre2-8')
67wayland_server = dependency('wayland-server', version: '>=1.21.0')
68wayland_client = dependency('wayland-client')
69wayland_cursor = dependency('wayland-cursor')
70wayland_protos = dependency('wayland-protocols', version: '>=1.24')
71xkbcommon = dependency('xkbcommon', version: '>=1.5.0')
72cairo = dependency('cairo')
73pango = dependency('pango')
74pangocairo = dependency('pangocairo')
75gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
76pixman = dependency('pixman-1')
77libevdev = dependency('libevdev')
78libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.21.0') : null_dep
79xcb = dependency('xcb', required: get_option('xwayland'))
80drm = dependency('libdrm')
81libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep
82math = cc.find_library('m')
83rt = cc.find_library('rt')
84xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland'))
85threads = dependency('threads') # for pthread_setschedparam
86
87have_xwayland = xcb.found() and xcb_icccm.found() and wlroots_features['xwayland']
93 88
94if get_option('sd-bus-provider') == 'auto' 89if get_option('sd-bus-provider') == 'auto'
95 if not get_option('tray').disabled() 90 if not get_option('tray').disabled()
96 assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto') 91 assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto')
97 endif 92 endif
98 sdbus = dependency('libsystemd', 93 sdbus = dependency(['libsystemd', 'libelogind'],
99 required: false, 94 required: false,
100 version: '>=239', 95 version: '>=239',
101 not_found_message: 'libsystemd not found, trying libelogind',
102 ) 96 )
103 if not sdbus.found() 97 if not sdbus.found()
104 sdbus = dependency('libelogind',
105 required: false,
106 version: '>=239',
107 not_found_message: 'libelogind not found, trying basu',
108 )
109 endif
110 if not sdbus.found()
111 sdbus = dependency('basu', required: false) 98 sdbus = dependency('basu', required: false)
112 endif 99 endif
113else 100else
@@ -128,11 +115,15 @@ conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd
128conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind') 115conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind')
129conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu') 116conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu')
130conf_data.set10('HAVE_TRAY', have_tray) 117conf_data.set10('HAVE_TRAY', have_tray)
118conf_data.set10('HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', cc.has_header_symbol(
119 'libinput.h',
120 'LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM',
121 dependencies: libinput,
122))
131 123
132scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) 124scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))
133if scdoc.found() 125if scdoc.found()
134 scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) 126 scdoc_prog = find_program(scdoc.get_variable(pkgconfig: 'scdoc'), native: true)
135 sh = find_program('sh', native: true)
136 mandir = get_option('mandir') 127 mandir = get_option('mandir')
137 man_files = [ 128 man_files = [
138 'sway/sway.1.scd', 129 'sway/sway.1.scd',
@@ -143,9 +134,15 @@ if scdoc.found()
143 'sway/sway-output.5.scd', 134 'sway/sway-output.5.scd',
144 'swaybar/swaybar-protocol.7.scd', 135 'swaybar/swaybar-protocol.7.scd',
145 'swaymsg/swaymsg.1.scd', 136 'swaymsg/swaymsg.1.scd',
146 'swaynag/swaynag.1.scd',
147 'swaynag/swaynag.5.scd',
148 ] 137 ]
138
139 if get_option('swaynag')
140 man_files += [
141 'swaynag/swaynag.1.scd',
142 'swaynag/swaynag.5.scd',
143 ]
144 endif
145
149 foreach filename : man_files 146 foreach filename : man_files
150 topic = filename.split('.')[-3].split('/')[-1] 147 topic = filename.split('.')[-3].split('/')[-1]
151 section = filename.split('.')[-2] 148 section = filename.split('.')[-2]
@@ -155,10 +152,10 @@ if scdoc.found()
155 output, 152 output,
156 input: filename, 153 input: filename,
157 output: output, 154 output: output,
158 command: [ 155 command: scdoc_prog,
159 sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)
160 ],
161 install: true, 156 install: true,
157 feed: true,
158 capture: true,
162 install_dir: '@0@/man@1@'.format(mandir, section) 159 install_dir: '@0@/man@1@'.format(mandir, section)
163 ) 160 )
164 endforeach 161 endforeach
@@ -169,8 +166,8 @@ add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir
169version = '"@0@"'.format(meson.project_version()) 166version = '"@0@"'.format(meson.project_version())
170git = find_program('git', native: true, required: false) 167git = find_program('git', native: true, required: false)
171if git.found() 168if git.found()
172 git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) 169 git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
173 git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) 170 git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
174 if git_commit.returncode() == 0 and git_branch.returncode() == 0 171 if git_commit.returncode() == 0 and git_branch.returncode() == 0
175 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format( 172 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format(
176 meson.project_version(), 173 meson.project_version(),
@@ -183,7 +180,7 @@ add_project_arguments('-DSWAY_VERSION=@0@'.format(version), language: 'c')
183 180
184# Compute the relative path used by compiler invocations. 181# Compute the relative path used by compiler invocations.
185source_root = meson.current_source_dir().split('/') 182source_root = meson.current_source_dir().split('/')
186build_root = meson.build_root().split('/') 183build_root = meson.global_build_root().split('/')
187relative_dir_parts = [] 184relative_dir_parts = []
188i = 0 185i = 0
189in_prefix = true 186in_prefix = true
@@ -227,9 +224,15 @@ subdir('common')
227subdir('sway') 224subdir('sway')
228subdir('swaymsg') 225subdir('swaymsg')
229 226
230subdir('client') 227if get_option('swaybar') or get_option('swaynag')
231subdir('swaybar') 228 subdir('client')
232subdir('swaynag') 229endif
230if get_option('swaybar')
231 subdir('swaybar')
232endif
233if get_option('swaynag')
234 subdir('swaynag')
235endif
233 236
234config = configuration_data() 237config = configuration_data()
235config.set('datadir', join_paths(prefix, datadir)) 238config.set('datadir', join_paths(prefix, datadir))
@@ -264,61 +267,11 @@ if get_option('default-wallpaper')
264 install_data(wallpaper_files, install_dir: wallpaper_install_dir) 267 install_data(wallpaper_files, install_dir: wallpaper_install_dir)
265endif 268endif
266 269
267if get_option('zsh-completions') 270subdir('completions')
268 zsh_files = files(
269 'completions/zsh/_sway',
270 'completions/zsh/_swaymsg',
271 )
272 zsh_install_dir = join_paths(datadir, 'zsh', 'site-functions')
273
274 install_data(zsh_files, install_dir: zsh_install_dir)
275endif
276
277if get_option('bash-completions')
278 bash_files = files(
279 'completions/bash/sway',
280 'completions/bash/swaybar',
281 'completions/bash/swaymsg',
282 )
283 if bash_comp.found()
284 bash_install_dir = bash_comp.get_pkgconfig_variable(
285 'completionsdir',
286 define_variable: ['datadir', datadir]
287 )
288 else
289 bash_install_dir = join_paths(datadir, 'bash-completion', 'completions')
290 endif
291
292 install_data(bash_files, install_dir: bash_install_dir)
293endif
294
295if get_option('fish-completions')
296 fish_files = files(
297 'completions/fish/sway.fish',
298 'completions/fish/swaymsg.fish',
299 'completions/fish/swaynag.fish',
300 )
301 if fish_comp.found()
302 fish_install_dir = fish_comp.get_pkgconfig_variable(
303 'completionsdir',
304 define_variable: ['datadir', datadir]
305 )
306 else
307 fish_install_dir = join_paths(datadir, 'fish', 'vendor_completions.d')
308 endif
309
310 install_data(fish_files, install_dir: fish_install_dir)
311endif
312 271
313summary({ 272summary({
314 'xwayland': have_xwayland, 273 'xwayland': have_xwayland,
315 'gdk-pixbuf': gdk_pixbuf.found(), 274 'gdk-pixbuf': gdk_pixbuf.found(),
316 'sd-bus': sdbus.found(),
317 'tray': have_tray, 275 'tray': have_tray,
318 'man-pages': scdoc.found(), 276 'man-pages': scdoc.found(),
319}, bool_yn: true) 277}, bool_yn: true)
320
321if not wlroots_features['systemd'] and not wlroots_features['elogind'] and not wlroots_features['libseat']
322 warning('The sway binary must be setuid when compiled without (e)logind or libseat')
323 warning('You must do this manually post-install: chmod a+s /path/to/sway')
324endif
diff --git a/meson_options.txt b/meson_options.txt
index e36900b6..8d0d6509 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -2,8 +2,10 @@ option('default-wallpaper', type: 'boolean', value: true, description: 'Install
2option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') 2option('zsh-completions', type: 'boolean', value: true, description: 'Install zsh shell completions.')
3option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') 3option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
4option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') 4option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.')
5option('swaybar', type: 'boolean', value: true, description: 'Enable support for swaybar')
6option('swaynag', type: 'boolean', value: true, description: 'Enable support for swaynag')
5option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') 7option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications')
6option('tray', type: 'feature', value: 'auto', description: 'Enable support for swaybar tray') 8option('tray', type: 'feature', value: 'auto', description: 'Enable support for swaybar tray')
7option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybg') 9option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats in swaybar tray')
8option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 10option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
9option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library') 11option('sd-bus-provider', type: 'combo', choices: ['auto', 'libsystemd', 'libelogind', 'basu'], value: 'auto', description: 'Provider of the sd-bus library')
diff --git a/protocols/meson.build b/protocols/meson.build
index 124e9777..81edb584 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -1,80 +1,43 @@
1wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') 1wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
2 2
3wayland_scanner_dep = dependency('wayland-scanner', required: false, native: true) 3wayland_scanner_dep = dependency('wayland-scanner', native: true)
4if wayland_scanner_dep.found() 4wayland_scanner = find_program(
5 wayland_scanner = find_program( 5 wayland_scanner_dep.get_variable('wayland_scanner'),
6 wayland_scanner_dep.get_pkgconfig_variable('wayland_scanner'), 6 native: true,
7 native: true, 7)
8 )
9else
10 wayland_scanner = find_program('wayland-scanner', native: true)
11endif
12 8
13protocols = [ 9protocols = [
14 [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], 10 wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
15 [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], 11 wl_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
16 [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], 12 wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml',
17 [wl_protocol_dir, 'unstable/tablet/tablet-unstable-v2.xml'], 13 wl_protocol_dir / 'unstable/tablet/tablet-unstable-v2.xml',
18 ['wlr-layer-shell-unstable-v1.xml'], 14 wl_protocol_dir / 'unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml',
19 ['idle.xml'], 15 wl_protocol_dir / 'staging/content-type/content-type-v1.xml',
20 ['wlr-input-inhibitor-unstable-v1.xml'], 16 wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
21 ['wlr-output-power-management-unstable-v1.xml'], 17 'wlr-layer-shell-unstable-v1.xml',
22] 18 'idle.xml',
23 19 'wlr-output-power-management-unstable-v1.xml',
24client_protocols = [
25 [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
26 [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
27 ['wlr-layer-shell-unstable-v1.xml'],
28 ['wlr-input-inhibitor-unstable-v1.xml'],
29] 20]
30 21
31wl_protos_src = [] 22wl_protos_src = []
32wl_protos_headers = []
33 23
34foreach p : protocols 24foreach xml : protocols
35 xml = join_paths(p)
36 wl_protos_src += custom_target( 25 wl_protos_src += custom_target(
37 xml.underscorify() + '_server_c', 26 xml.underscorify() + '_c',
38 input: xml, 27 input: xml,
39 output: '@BASENAME@-protocol.c', 28 output: '@BASENAME@-protocol.c',
40 command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'], 29 command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
41 ) 30 )
42 wl_protos_headers += custom_target( 31 wl_protos_src += custom_target(
43 xml.underscorify() + '_server_h', 32 xml.underscorify() + '_server_h',
44 input: xml, 33 input: xml,
45 output: '@BASENAME@-protocol.h', 34 output: '@BASENAME@-protocol.h',
46 command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'], 35 command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
47 ) 36 )
48endforeach 37 wl_protos_src += custom_target(
49
50foreach p : client_protocols
51 xml = join_paths(p)
52 wl_protos_headers += custom_target(
53 xml.underscorify() + '_client_h', 38 xml.underscorify() + '_client_h',
54 input: xml, 39 input: xml,
55 output: '@BASENAME@-client-protocol.h', 40 output: '@BASENAME@-client-protocol.h',
56 command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'], 41 command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
57 ) 42 )
58endforeach 43endforeach
59
60lib_client_protos = static_library(
61 'client_protos',
62 wl_protos_src + wl_protos_headers,
63 dependencies: wayland_client.partial_dependency(compile_args: true),
64)
65
66client_protos = declare_dependency(
67 link_with: lib_client_protos,
68 sources: wl_protos_headers,
69)
70
71lib_server_protos = static_library(
72 'server_protos',
73 wl_protos_src + wl_protos_headers,
74 dependencies: wayland_server.partial_dependency(compile_args: true),
75)
76
77server_protos = declare_dependency(
78 link_with: lib_server_protos,
79 sources: wl_protos_headers,
80)
diff --git a/protocols/wlr-input-inhibitor-unstable-v1.xml b/protocols/wlr-input-inhibitor-unstable-v1.xml
deleted file mode 100644
index b62d1bb4..00000000
--- a/protocols/wlr-input-inhibitor-unstable-v1.xml
+++ /dev/null
@@ -1,67 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<protocol name="wlr_input_inhibit_unstable_v1">
3 <copyright>
4 Copyright © 2018 Drew DeVault
5
6 Permission to use, copy, modify, distribute, and sell this
7 software and its documentation for any purpose is hereby granted
8 without fee, provided that the above copyright notice appear in
9 all copies and that both that copyright notice and this permission
10 notice appear in supporting documentation, and that the name of
11 the copyright holders not be used in advertising or publicity
12 pertaining to distribution of the software without specific,
13 written prior permission. The copyright holders make no
14 representations about the suitability of this software for any
15 purpose. It is provided "as is" without express or implied
16 warranty.
17
18 THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
19 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
21 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
23 AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
24 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
25 THIS SOFTWARE.
26 </copyright>
27
28 <interface name="zwlr_input_inhibit_manager_v1" version="1">
29 <description summary="inhibits input events to other clients">
30 Clients can use this interface to prevent input events from being sent to
31 any surfaces but its own, which is useful for example in lock screen
32 software. It is assumed that access to this interface will be locked down
33 to whitelisted clients by the compositor.
34 </description>
35
36 <request name="get_inhibitor">
37 <description summary="inhibit input to other clients">
38 Activates the input inhibitor. As long as the inhibitor is active, the
39 compositor will not send input events to other clients.
40 </description>
41 <arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/>
42 </request>
43
44 <enum name="error">
45 <entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/>
46 </enum>
47 </interface>
48
49 <interface name="zwlr_input_inhibitor_v1" version="1">
50 <description summary="inhibits input to other clients">
51 While this resource exists, input to clients other than the owner of the
52 inhibitor resource will not receive input events. The client that owns
53 this resource will receive all input events normally. The compositor will
54 also disable all of its own input processing (such as keyboard shortcuts)
55 while the inhibitor is active.
56
57 The compositor may continue to send input events to selected clients,
58 such as an on-screen keyboard (via the input-method protocol).
59 </description>
60
61 <request name="destroy" type="destructor">
62 <description summary="destroy the input inhibitor object">
63 Destroy the inhibitor and allow other clients to receive input.
64 </description>
65 </request>
66 </interface>
67</protocol>
diff --git a/sway/commands.c b/sway/commands.c
index fe1e98b5..55eda183 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -42,15 +42,17 @@ struct cmd_results *checkarg(int argc, const char *name, enum expected_args type
42} 42}
43 43
44/* Keep alphabetized */ 44/* Keep alphabetized */
45static struct cmd_handler handlers[] = { 45static const struct cmd_handler handlers[] = {
46 { "assign", cmd_assign }, 46 { "assign", cmd_assign },
47 { "bar", cmd_bar }, 47 { "bar", cmd_bar },
48 { "bindcode", cmd_bindcode }, 48 { "bindcode", cmd_bindcode },
49 { "bindgesture", cmd_bindgesture },
49 { "bindswitch", cmd_bindswitch }, 50 { "bindswitch", cmd_bindswitch },
50 { "bindsym", cmd_bindsym }, 51 { "bindsym", cmd_bindsym },
51 { "client.background", cmd_client_noop }, 52 { "client.background", cmd_client_noop },
52 { "client.focused", cmd_client_focused }, 53 { "client.focused", cmd_client_focused },
53 { "client.focused_inactive", cmd_client_focused_inactive }, 54 { "client.focused_inactive", cmd_client_focused_inactive },
55 { "client.focused_tab_title", cmd_client_focused_tab_title },
54 { "client.placeholder", cmd_client_noop }, 56 { "client.placeholder", cmd_client_noop },
55 { "client.unfocused", cmd_client_unfocused }, 57 { "client.unfocused", cmd_client_unfocused },
56 { "client.urgent", cmd_client_urgent }, 58 { "client.urgent", cmd_client_urgent },
@@ -80,6 +82,7 @@ static struct cmd_handler handlers[] = {
80 { "no_focus", cmd_no_focus }, 82 { "no_focus", cmd_no_focus },
81 { "output", cmd_output }, 83 { "output", cmd_output },
82 { "popup_during_fullscreen", cmd_popup_during_fullscreen }, 84 { "popup_during_fullscreen", cmd_popup_during_fullscreen },
85 { "primary_selection", cmd_primary_selection },
83 { "seat", cmd_seat }, 86 { "seat", cmd_seat },
84 { "set", cmd_set }, 87 { "set", cmd_set },
85 { "show_marks", cmd_show_marks }, 88 { "show_marks", cmd_show_marks },
@@ -91,6 +94,7 @@ static struct cmd_handler handlers[] = {
91 { "titlebar_border_thickness", cmd_titlebar_border_thickness }, 94 { "titlebar_border_thickness", cmd_titlebar_border_thickness },
92 { "titlebar_padding", cmd_titlebar_padding }, 95 { "titlebar_padding", cmd_titlebar_padding },
93 { "unbindcode", cmd_unbindcode }, 96 { "unbindcode", cmd_unbindcode },
97 { "unbindgesture", cmd_unbindgesture },
94 { "unbindswitch", cmd_unbindswitch }, 98 { "unbindswitch", cmd_unbindswitch },
95 { "unbindsym", cmd_unbindsym }, 99 { "unbindsym", cmd_unbindsym },
96 { "workspace", cmd_workspace }, 100 { "workspace", cmd_workspace },
@@ -98,7 +102,7 @@ static struct cmd_handler handlers[] = {
98}; 102};
99 103
100/* Config-time only commands. Keep alphabetized */ 104/* Config-time only commands. Keep alphabetized */
101static struct cmd_handler config_handlers[] = { 105static const struct cmd_handler config_handlers[] = {
102 { "default_orientation", cmd_default_orientation }, 106 { "default_orientation", cmd_default_orientation },
103 { "include", cmd_include }, 107 { "include", cmd_include },
104 { "swaybg_command", cmd_swaybg_command }, 108 { "swaybg_command", cmd_swaybg_command },
@@ -108,7 +112,7 @@ static struct cmd_handler config_handlers[] = {
108}; 112};
109 113
110/* Runtime-only commands. Keep alphabetized */ 114/* Runtime-only commands. Keep alphabetized */
111static struct cmd_handler command_handlers[] = { 115static const struct cmd_handler command_handlers[] = {
112 { "border", cmd_border }, 116 { "border", cmd_border },
113 { "create_output", cmd_create_output }, 117 { "create_output", cmd_create_output },
114 { "exit", cmd_exit }, 118 { "exit", cmd_exit },
@@ -144,22 +148,22 @@ static int handler_compare(const void *_a, const void *_b) {
144 return strcasecmp(a->command, b->command); 148 return strcasecmp(a->command, b->command);
145} 149}
146 150
147struct cmd_handler *find_handler(char *line, struct cmd_handler *handlers, 151const struct cmd_handler *find_handler(const char *line,
148 size_t handlers_size) { 152 const struct cmd_handler *handlers, size_t handlers_size) {
149 if (!handlers || !handlers_size) { 153 if (!handlers || !handlers_size) {
150 return NULL; 154 return NULL;
151 } 155 }
152 struct cmd_handler query = { .command = line }; 156 const struct cmd_handler query = { .command = line };
153 return bsearch(&query, handlers, 157 return bsearch(&query, handlers,
154 handlers_size / sizeof(struct cmd_handler), 158 handlers_size / sizeof(struct cmd_handler),
155 sizeof(struct cmd_handler), handler_compare); 159 sizeof(struct cmd_handler), handler_compare);
156} 160}
157 161
158static struct cmd_handler *find_handler_ex(char *line, 162static const struct cmd_handler *find_handler_ex(char *line,
159 struct cmd_handler *config_handlers, size_t config_handlers_size, 163 const struct cmd_handler *config_handlers, size_t config_handlers_size,
160 struct cmd_handler *command_handlers, size_t command_handlers_size, 164 const struct cmd_handler *command_handlers, size_t command_handlers_size,
161 struct cmd_handler *handlers, size_t handlers_size) { 165 const struct cmd_handler *handlers, size_t handlers_size) {
162 struct cmd_handler *handler = NULL; 166 const struct cmd_handler *handler = NULL;
163 if (config->reading) { 167 if (config->reading) {
164 handler = find_handler(line, config_handlers, config_handlers_size); 168 handler = find_handler(line, config_handlers, config_handlers_size);
165 } else if (config->active) { 169 } else if (config->active) {
@@ -168,16 +172,17 @@ static struct cmd_handler *find_handler_ex(char *line,
168 return handler ? handler : find_handler(line, handlers, handlers_size); 172 return handler ? handler : find_handler(line, handlers, handlers_size);
169} 173}
170 174
171static struct cmd_handler *find_core_handler(char *line) { 175static const struct cmd_handler *find_core_handler(char *line) {
172 return find_handler_ex(line, config_handlers, sizeof(config_handlers), 176 return find_handler_ex(line, config_handlers, sizeof(config_handlers),
173 command_handlers, sizeof(command_handlers), 177 command_handlers, sizeof(command_handlers),
174 handlers, sizeof(handlers)); 178 handlers, sizeof(handlers));
175} 179}
176 180
177static void set_config_node(struct sway_node *node) { 181static void set_config_node(struct sway_node *node, bool node_overridden) {
178 config->handler_context.node = node; 182 config->handler_context.node = node;
179 config->handler_context.container = NULL; 183 config->handler_context.container = NULL;
180 config->handler_context.workspace = NULL; 184 config->handler_context.workspace = NULL;
185 config->handler_context.node_overridden = node_overridden;
181 186
182 if (node == NULL) { 187 if (node == NULL) {
183 return; 188 return;
@@ -186,7 +191,7 @@ static void set_config_node(struct sway_node *node) {
186 switch (node->type) { 191 switch (node->type) {
187 case N_CONTAINER: 192 case N_CONTAINER:
188 config->handler_context.container = node->sway_container; 193 config->handler_context.container = node->sway_container;
189 config->handler_context.workspace = node->sway_container->workspace; 194 config->handler_context.workspace = node->sway_container->pending.workspace;
190 break; 195 break;
191 case N_WORKSPACE: 196 case N_WORKSPACE:
192 config->handler_context.workspace = node->sway_workspace; 197 config->handler_context.workspace = node->sway_workspace;
@@ -202,6 +207,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
202 char *cmd; 207 char *cmd;
203 char matched_delim = ';'; 208 char matched_delim = ';';
204 list_t *containers = NULL; 209 list_t *containers = NULL;
210 bool using_criteria = false;
205 211
206 if (seat == NULL) { 212 if (seat == NULL) {
207 // passing a NULL seat means we just pick the default seat 213 // passing a NULL seat means we just pick the default seat
@@ -225,7 +231,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
225 for (; isspace(*head); ++head) {} 231 for (; isspace(*head); ++head) {}
226 // Extract criteria (valid for this command list only). 232 // Extract criteria (valid for this command list only).
227 if (matched_delim == ';') { 233 if (matched_delim == ';') {
228 config->handler_context.using_criteria = false; 234 using_criteria = false;
229 if (*head == '[') { 235 if (*head == '[') {
230 char *error = NULL; 236 char *error = NULL;
231 struct criteria *criteria = criteria_parse(head, &error); 237 struct criteria *criteria = criteria_parse(head, &error);
@@ -239,7 +245,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
239 containers = criteria_get_containers(criteria); 245 containers = criteria_get_containers(criteria);
240 head += strlen(criteria->raw); 246 head += strlen(criteria->raw);
241 criteria_destroy(criteria); 247 criteria_destroy(criteria);
242 config->handler_context.using_criteria = true; 248 using_criteria = true;
243 // Skip leading whitespace 249 // Skip leading whitespace
244 for (; isspace(*head); ++head) {} 250 for (; isspace(*head); ++head) {}
245 } 251 }
@@ -265,7 +271,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
265 } 271 }
266 } 272 }
267 } 273 }
268 struct cmd_handler *handler = find_core_handler(argv[0]); 274 const struct cmd_handler *handler = find_core_handler(argv[0]);
269 if (!handler) { 275 if (!handler) {
270 list_add(res_list, cmd_results_new(CMD_INVALID, 276 list_add(res_list, cmd_results_new(CMD_INVALID,
271 "Unknown/invalid command '%s'", argv[0])); 277 "Unknown/invalid command '%s'", argv[0]));
@@ -278,11 +284,14 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
278 argv[i] = do_var_replacement(argv[i]); 284 argv[i] = do_var_replacement(argv[i]);
279 } 285 }
280 286
281 if (!config->handler_context.using_criteria) { 287
282 // The container or workspace which this command will run on. 288 if (!using_criteria) {
283 struct sway_node *node = con ? &con->node : 289 if (con) {
284 seat_get_focus_inactive(seat, &root->node); 290 set_config_node(&con->node, true);
285 set_config_node(node); 291 } else {
292 set_config_node(seat_get_focus_inactive(seat, &root->node),
293 false);
294 }
286 struct cmd_results *res = handler->handle(argc-1, argv+1); 295 struct cmd_results *res = handler->handle(argc-1, argv+1);
287 list_add(res_list, res); 296 list_add(res_list, res);
288 if (res->status == CMD_INVALID) { 297 if (res->status == CMD_INVALID) {
@@ -296,7 +305,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
296 struct cmd_results *fail_res = NULL; 305 struct cmd_results *fail_res = NULL;
297 for (int i = 0; i < containers->length; ++i) { 306 for (int i = 0; i < containers->length; ++i) {
298 struct sway_container *container = containers->items[i]; 307 struct sway_container *container = containers->items[i];
299 set_config_node(&container->node); 308 set_config_node(&container->node, true);
300 struct cmd_results *res = handler->handle(argc-1, argv+1); 309 struct cmd_results *res = handler->handle(argc-1, argv+1);
301 if (res->status == CMD_SUCCESS) { 310 if (res->status == CMD_SUCCESS) {
302 free_cmd_results(res); 311 free_cmd_results(res);
@@ -370,12 +379,15 @@ struct cmd_results *config_command(char *exec, char **new_block) {
370 379
371 // Determine the command handler 380 // Determine the command handler
372 sway_log(SWAY_INFO, "Config command: %s", exec); 381 sway_log(SWAY_INFO, "Config command: %s", exec);
373 struct cmd_handler *handler = find_core_handler(argv[0]); 382 const struct cmd_handler *handler = find_core_handler(argv[0]);
374 if (!handler || !handler->handle) { 383 if (!handler || !handler->handle) {
375 const char *error = handler 384 if (handler) {
376 ? "Command '%s' is shimmed, but unimplemented" 385 results = cmd_results_new(CMD_INVALID,
377 : "Unknown/invalid command '%s'"; 386 "Command '%s' is shimmed, but unimplemented", argv[0]);
378 results = cmd_results_new(CMD_INVALID, error, argv[0]); 387 } else {
388 results = cmd_results_new(CMD_INVALID,
389 "Unknown/invalid command '%s'", argv[0]);
390 }
379 goto cleanup; 391 goto cleanup;
380 } 392 }
381 393
@@ -401,6 +413,7 @@ struct cmd_results *config_command(char *exec, char **new_block) {
401 && handler->handle != cmd_bindsym 413 && handler->handle != cmd_bindsym
402 && handler->handle != cmd_bindcode 414 && handler->handle != cmd_bindcode
403 && handler->handle != cmd_bindswitch 415 && handler->handle != cmd_bindswitch
416 && handler->handle != cmd_bindgesture
404 && handler->handle != cmd_set 417 && handler->handle != cmd_set
405 && handler->handle != cmd_for_window 418 && handler->handle != cmd_for_window
406 && (*argv[i] == '\"' || *argv[i] == '\'')) { 419 && (*argv[i] == '\"' || *argv[i] == '\'')) {
@@ -418,12 +431,12 @@ cleanup:
418} 431}
419 432
420struct cmd_results *config_subcommand(char **argv, int argc, 433struct cmd_results *config_subcommand(char **argv, int argc,
421 struct cmd_handler *handlers, size_t handlers_size) { 434 const struct cmd_handler *handlers, size_t handlers_size) {
422 char *command = join_args(argv, argc); 435 char *command = join_args(argv, argc);
423 sway_log(SWAY_DEBUG, "Subcommand: %s", command); 436 sway_log(SWAY_DEBUG, "Subcommand: %s", command);
424 free(command); 437 free(command);
425 438
426 struct cmd_handler *handler = find_handler(argv[0], handlers, 439 const struct cmd_handler *handler = find_handler(argv[0], handlers,
427 handlers_size); 440 handlers_size);
428 if (!handler) { 441 if (!handler) {
429 return cmd_results_new(CMD_INVALID, 442 return cmd_results_new(CMD_INVALID,
@@ -453,41 +466,13 @@ struct cmd_results *config_commands_command(char *exec) {
453 goto cleanup; 466 goto cleanup;
454 } 467 }
455 468
456 struct cmd_handler *handler = find_handler(cmd, NULL, 0); 469 const struct cmd_handler *handler = find_handler(cmd, NULL, 0);
457 if (!handler && strcmp(cmd, "*") != 0) { 470 if (!handler && strcmp(cmd, "*") != 0) {
458 results = cmd_results_new(CMD_INVALID, 471 results = cmd_results_new(CMD_INVALID,
459 "Unknown/invalid command '%s'", cmd); 472 "Unknown/invalid command '%s'", cmd);
460 goto cleanup; 473 goto cleanup;
461 } 474 }
462 475
463 enum command_context context = 0;
464
465 struct {
466 char *name;
467 enum command_context context;
468 } context_names[] = {
469 { "config", CONTEXT_CONFIG },
470 { "binding", CONTEXT_BINDING },
471 { "ipc", CONTEXT_IPC },
472 { "criteria", CONTEXT_CRITERIA },
473 { "all", CONTEXT_ALL },
474 };
475
476 for (int i = 1; i < argc; ++i) {
477 size_t j;
478 for (j = 0; j < sizeof(context_names) / sizeof(context_names[0]); ++j) {
479 if (strcmp(context_names[j].name, argv[i]) == 0) {
480 break;
481 }
482 }
483 if (j == sizeof(context_names) / sizeof(context_names[0])) {
484 results = cmd_results_new(CMD_INVALID,
485 "Invalid command context %s", argv[i]);
486 goto cleanup;
487 }
488 context |= context_names[j].context;
489 }
490
491 results = cmd_results_new(CMD_SUCCESS, NULL); 476 results = cmd_results_new(CMD_SUCCESS, NULL);
492 477
493cleanup: 478cleanup:
@@ -504,14 +489,10 @@ struct cmd_results *cmd_results_new(enum cmd_status status,
504 } 489 }
505 results->status = status; 490 results->status = status;
506 if (format) { 491 if (format) {
507 char *error = malloc(256);
508 va_list args; 492 va_list args;
509 va_start(args, format); 493 va_start(args, format);
510 if (error) { 494 results->error = vformat_str(format, args);
511 vsnprintf(error, 256, format, args);
512 }
513 va_end(args); 495 va_end(args);
514 results->error = error;
515 } else { 496 } else {
516 results->error = NULL; 497 results->error = NULL;
517 } 498 }
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
index 976bc3cc..f7d911f7 100644
--- a/sway/commands/assign.c
+++ b/sway/commands/assign.c
@@ -17,7 +17,7 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
17 char *err_str = NULL; 17 char *err_str = NULL;
18 struct criteria *criteria = criteria_parse(argv[0], &err_str); 18 struct criteria *criteria = criteria_parse(argv[0], &err_str);
19 if (!criteria) { 19 if (!criteria) {
20 error = cmd_results_new(CMD_INVALID, err_str); 20 error = cmd_results_new(CMD_INVALID, "%s", err_str);
21 free(err_str); 21 free(err_str);
22 return error; 22 return error;
23 } 23 }
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index d42b7fc2..22756acb 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -8,7 +8,7 @@
8#include "log.h" 8#include "log.h"
9 9
10// Must be in alphabetical order for bsearch 10// Must be in alphabetical order for bsearch
11static struct cmd_handler bar_handlers[] = { 11static const struct cmd_handler bar_handlers[] = {
12 { "bindcode", bar_cmd_bindcode }, 12 { "bindcode", bar_cmd_bindcode },
13 { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, 13 { "binding_mode_indicator", bar_cmd_binding_mode_indicator },
14 { "bindsym", bar_cmd_bindsym }, 14 { "bindsym", bar_cmd_bindsym },
@@ -41,7 +41,7 @@ static struct cmd_handler bar_handlers[] = {
41}; 41};
42 42
43// Must be in alphabetical order for bsearch 43// Must be in alphabetical order for bsearch
44static struct cmd_handler bar_config_handlers[] = { 44static const struct cmd_handler bar_config_handlers[] = {
45 { "id", bar_cmd_id }, 45 { "id", bar_cmd_id },
46 { "swaybar_command", bar_cmd_swaybar_command }, 46 { "swaybar_command", bar_cmd_swaybar_command },
47}; 47};
@@ -73,12 +73,10 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
73 } 73 }
74 ++argv; --argc; 74 ++argv; --argc;
75 } else if (config->reading && !config->current_bar) { 75 } else if (config->reading && !config->current_bar) {
76 int len = snprintf(NULL, 0, "bar-%d", config->bars->length) + 1; 76 id = format_str("bar-%d", config->bars->length);
77 id = malloc(len * sizeof(char));
78 if (!id) { 77 if (!id) {
79 return cmd_results_new(CMD_FAILURE, "Unable to allocate bar id"); 78 return cmd_results_new(CMD_FAILURE, "Unable to allocate bar id");
80 } 79 }
81 snprintf(id, len, "bar-%d", config->bars->length);
82 } else if (!config->reading && strcmp(argv[0], "mode") != 0 && 80 } else if (!config->reading && strcmp(argv[0], "mode") != 0 &&
83 strcmp(argv[0], "hidden_state") != 0) { 81 strcmp(argv[0], "hidden_state") != 0) {
84 if (is_subcommand(argv[0])) { 82 if (is_subcommand(argv[0])) {
@@ -116,6 +114,7 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
116 if (res && res->status != CMD_SUCCESS) { 114 if (res && res->status != CMD_SUCCESS) {
117 if (id) { 115 if (id) {
118 free_bar_config(config->current_bar); 116 free_bar_config(config->current_bar);
117 config->current_bar = NULL;
119 id = NULL; 118 id = NULL;
120 } 119 }
121 return res; 120 return res;
diff --git a/sway/commands/bar/bind.c b/sway/commands/bar/bind.c
index b4b5bc45..8a837e3f 100644
--- a/sway/commands/bar/bind.c
+++ b/sway/commands/bar/bind.c
@@ -96,7 +96,7 @@ static struct cmd_results *bar_cmd_bind(int argc, char **argv, bool code,
96 } 96 }
97 if (message) { 97 if (message) {
98 free_bar_binding(binding); 98 free_bar_binding(binding);
99 error = cmd_results_new(CMD_INVALID, message); 99 error = cmd_results_new(CMD_INVALID, "%s", message);
100 free(message); 100 free(message);
101 return error; 101 return error;
102 } else if (!binding->button) { 102 } else if (!binding->button) {
diff --git a/sway/commands/bar/colors.c b/sway/commands/bar/colors.c
index 2d5b22bf..275fa3c6 100644
--- a/sway/commands/bar/colors.c
+++ b/sway/commands/bar/colors.c
@@ -4,7 +4,7 @@
4#include "util.h" 4#include "util.h"
5 5
6// Must be in alphabetical order for bsearch 6// Must be in alphabetical order for bsearch
7static struct cmd_handler bar_colors_handlers[] = { 7static const struct cmd_handler bar_colors_handlers[] = {
8 { "active_workspace", bar_colors_cmd_active_workspace }, 8 { "active_workspace", bar_colors_cmd_active_workspace },
9 { "background", bar_colors_cmd_background }, 9 { "background", bar_colors_cmd_background },
10 { "binding_mode", bar_colors_cmd_binding_mode }, 10 { "binding_mode", bar_colors_cmd_binding_mode },
diff --git a/sway/commands/bar/font.c b/sway/commands/bar/font.c
index 62987f3e..891c87af 100644
--- a/sway/commands/bar/font.c
+++ b/sway/commands/bar/font.c
@@ -11,7 +11,20 @@ struct cmd_results *bar_cmd_font(int argc, char **argv) {
11 } 11 }
12 char *font = join_args(argv, argc); 12 char *font = join_args(argv, argc);
13 free(config->current_bar->font); 13 free(config->current_bar->font);
14 config->current_bar->font = font; 14
15 if (strncmp(font, "pango:", 6) == 0) {
16 if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) {
17 config->current_bar->pango_markup = true;
18 }
19 config->current_bar->font = strdup(font + 6);
20 } else {
21 if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) {
22 config->current_bar->pango_markup = false;
23 }
24 config->current_bar->font = strdup(font);
25 }
26
27 free(font);
15 sway_log(SWAY_DEBUG, "Settings font '%s' for bar: %s", 28 sway_log(SWAY_DEBUG, "Settings font '%s' for bar: %s",
16 config->current_bar->font, config->current_bar->id); 29 config->current_bar->font, config->current_bar->id);
17 return cmd_results_new(CMD_SUCCESS, NULL); 30 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/bar/hidden_state.c b/sway/commands/bar/hidden_state.c
index 1f08a5d2..8b661e3a 100644
--- a/sway/commands/bar/hidden_state.c
+++ b/sway/commands/bar/hidden_state.c
@@ -54,7 +54,7 @@ struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) {
54 } 54 }
55 55
56 const char *state = argv[0]; 56 const char *state = argv[0];
57 if (config->reading) { 57 if (config->current_bar) {
58 error = bar_set_hidden_state(config->current_bar, state); 58 error = bar_set_hidden_state(config->current_bar, state);
59 } else { 59 } else {
60 const char *id = argc == 2 ? argv[1] : NULL; 60 const char *id = argc == 2 ? argv[1] : NULL;
diff --git a/sway/commands/bar/mode.c b/sway/commands/bar/mode.c
index 8b3fb275..7c2f423b 100644
--- a/sway/commands/bar/mode.c
+++ b/sway/commands/bar/mode.c
@@ -58,7 +58,7 @@ struct cmd_results *bar_cmd_mode(int argc, char **argv) {
58 } 58 }
59 59
60 const char *mode = argv[0]; 60 const char *mode = argv[0];
61 if (config->reading) { 61 if (config->current_bar) {
62 error = bar_set_mode(config->current_bar, mode); 62 error = bar_set_mode(config->current_bar, mode);
63 } else { 63 } else {
64 const char *id = argc == 2 ? argv[1] : NULL; 64 const char *id = argc == 2 ? argv[1] : NULL;
diff --git a/sway/commands/bar/tray_bind.c b/sway/commands/bar/tray_bind.c
index 243834ba..3dc9bc4c 100644
--- a/sway/commands/bar/tray_bind.c
+++ b/sway/commands/bar/tray_bind.c
@@ -26,7 +26,7 @@ static struct cmd_results *tray_bind(int argc, char **argv, bool code) {
26 } 26 }
27 if (message) { 27 if (message) {
28 free(binding); 28 free(binding);
29 error = cmd_results_new(CMD_INVALID, message); 29 error = cmd_results_new(CMD_INVALID, "%s", message);
30 free(message); 30 free(message);
31 return error; 31 return error;
32 } else if (!binding->button) { 32 } else if (!binding->button) {
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index f6e58d99..979e178f 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -8,6 +8,7 @@
8#include <wlr/types/wlr_cursor.h> 8#include <wlr/types/wlr_cursor.h>
9#include "sway/commands.h" 9#include "sway/commands.h"
10#include "sway/config.h" 10#include "sway/config.h"
11#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 12#include "sway/input/cursor.h"
12#include "sway/input/keyboard.h" 13#include "sway/input/keyboard.h"
13#include "sway/ipc-server.h" 14#include "sway/ipc-server.h"
@@ -46,7 +47,7 @@ static bool binding_switch_compare(struct sway_switch_binding *binding_a,
46 if (binding_a->type != binding_b->type) { 47 if (binding_a->type != binding_b->type) {
47 return false; 48 return false;
48 } 49 }
49 if (binding_a->state != binding_b->state) { 50 if (binding_a->trigger != binding_b->trigger) {
50 return false; 51 return false;
51 } 52 }
52 if ((binding_a->flags & BINDING_LOCKED) != 53 if ((binding_a->flags & BINDING_LOCKED) !=
@@ -126,7 +127,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
126 if (!button) { 127 if (!button) {
127 if (message) { 128 if (message) {
128 struct cmd_results *error = 129 struct cmd_results *error =
129 cmd_results_new(CMD_INVALID, message); 130 cmd_results_new(CMD_INVALID, "%s", message);
130 free(message); 131 free(message);
131 return error; 132 return error;
132 } else { 133 } else {
@@ -142,7 +143,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
142 if (!button) { 143 if (!button) {
143 if (message) { 144 if (message) {
144 struct cmd_results *error = 145 struct cmd_results *error =
145 cmd_results_new(CMD_INVALID, message); 146 cmd_results_new(CMD_INVALID, "%s", message);
146 free(message); 147 free(message);
147 return error; 148 return error;
148 } else { 149 } else {
@@ -181,7 +182,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
181 uint32_t button = get_mouse_bindsym(name, &message); 182 uint32_t button = get_mouse_bindsym(name, &message);
182 if (message) { 183 if (message) {
183 struct cmd_results *error = 184 struct cmd_results *error =
184 cmd_results_new(CMD_INVALID, message); 185 cmd_results_new(CMD_INVALID, "%s", message);
185 free(message); 186 free(message);
186 return error; 187 return error;
187 } else if (button) { 188 } else if (button) {
@@ -371,6 +372,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
371 strlen("--input-device=")) == 0) { 372 strlen("--input-device=")) == 0) {
372 free(binding->input); 373 free(binding->input);
373 binding->input = strdup(argv[0] + strlen("--input-device=")); 374 binding->input = strdup(argv[0] + strlen("--input-device="));
375 strip_quotes(binding->input);
374 } else if (strcmp("--no-warn", argv[0]) == 0) { 376 } else if (strcmp("--no-warn", argv[0]) == 0) {
375 warn = false; 377 warn = false;
376 } else if (strcmp("--no-repeat", argv[0]) == 0) { 378 } else if (strcmp("--no-repeat", argv[0]) == 0) {
@@ -537,7 +539,7 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv,
537 free_switch_binding(binding); 539 free_switch_binding(binding);
538 return cmd_results_new(CMD_FAILURE, 540 return cmd_results_new(CMD_FAILURE,
539 "Invalid %s command (expected binding with the form " 541 "Invalid %s command (expected binding with the form "
540 "<switch>:<state>)", bindtype, argc); 542 "<switch>:<state>)", bindtype);
541 } 543 }
542 if (strcmp(split->items[0], "tablet") == 0) { 544 if (strcmp(split->items[0], "tablet") == 0) {
543 binding->type = WLR_SWITCH_TYPE_TABLET_MODE; 545 binding->type = WLR_SWITCH_TYPE_TABLET_MODE;
@@ -547,20 +549,21 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv,
547 free_switch_binding(binding); 549 free_switch_binding(binding);
548 return cmd_results_new(CMD_FAILURE, 550 return cmd_results_new(CMD_FAILURE,
549 "Invalid %s command (expected switch binding: " 551 "Invalid %s command (expected switch binding: "
550 "unknown switch %s)", bindtype, split->items[0]); 552 "unknown switch %s)", bindtype,
553 (const char *)split->items[0]);
551 } 554 }
552 if (strcmp(split->items[1], "on") == 0) { 555 if (strcmp(split->items[1], "on") == 0) {
553 binding->state = WLR_SWITCH_STATE_ON; 556 binding->trigger = SWAY_SWITCH_TRIGGER_ON;
554 } else if (strcmp(split->items[1], "off") == 0) { 557 } else if (strcmp(split->items[1], "off") == 0) {
555 binding->state = WLR_SWITCH_STATE_OFF; 558 binding->trigger = SWAY_SWITCH_TRIGGER_OFF;
556 } else if (strcmp(split->items[1], "toggle") == 0) { 559 } else if (strcmp(split->items[1], "toggle") == 0) {
557 binding->state = WLR_SWITCH_STATE_TOGGLE; 560 binding->trigger = SWAY_SWITCH_TRIGGER_TOGGLE;
558 } else { 561 } else {
559 free_switch_binding(binding); 562 free_switch_binding(binding);
560 return cmd_results_new(CMD_FAILURE, 563 return cmd_results_new(CMD_FAILURE,
561 "Invalid %s command " 564 "Invalid %s command "
562 "(expected switch state: unknown state %d)", 565 "(expected switch state: unknown state %s)",
563 bindtype, split->items[0]); 566 bindtype, (const char *)split->items[1]);
564 } 567 }
565 list_free_items_and_destroy(split); 568 list_free_items_and_destroy(split);
566 569
@@ -642,6 +645,8 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
642 if (success) { 645 if (success) {
643 ipc_event_binding(binding); 646 ipc_event_binding(binding);
644 } 647 }
648
649 transaction_commit_dirty();
645} 650}
646 651
647/** 652/**
diff --git a/sway/commands/border.c b/sway/commands/border.c
index 647663ac..7818fc96 100644
--- a/sway/commands/border.c
+++ b/sway/commands/border.c
@@ -19,11 +19,11 @@ static void set_border(struct sway_container *con,
19 view_set_csd_from_server(con->view, false); 19 view_set_csd_from_server(con->view, false);
20 } else if (!con->view->using_csd && new_border == B_CSD) { 20 } else if (!con->view->using_csd && new_border == B_CSD) {
21 view_set_csd_from_server(con->view, true); 21 view_set_csd_from_server(con->view, true);
22 con->saved_border = con->border; 22 con->saved_border = con->pending.border;
23 } 23 }
24 } 24 }
25 if (new_border != B_CSD || container_is_floating(con)) { 25 if (new_border != B_CSD || container_is_floating(con)) {
26 con->border = new_border; 26 con->pending.border = new_border;
27 } 27 }
28 if (con->view) { 28 if (con->view) {
29 con->view->using_csd = new_border == B_CSD; 29 con->view->using_csd = new_border == B_CSD;
@@ -35,7 +35,7 @@ static void border_toggle(struct sway_container *con) {
35 set_border(con, B_NONE); 35 set_border(con, B_NONE);
36 return; 36 return;
37 } 37 }
38 switch (con->border) { 38 switch (con->pending.border) {
39 case B_NONE: 39 case B_NONE:
40 set_border(con, B_PIXEL); 40 set_border(con, B_PIXEL);
41 break; 41 break;
@@ -88,7 +88,7 @@ struct cmd_results *cmd_border(int argc, char **argv) {
88 "or 'border pixel <px>'"); 88 "or 'border pixel <px>'");
89 } 89 }
90 if (argc == 2) { 90 if (argc == 2) {
91 container->border_thickness = atoi(argv[1]); 91 container->pending.border_thickness = atoi(argv[1]);
92 } 92 }
93 93
94 if (container_is_floating(container)) { 94 if (container_is_floating(container)) {
diff --git a/sway/commands/client.c b/sway/commands/client.c
index dd0694df..fd2ac7a8 100644
--- a/sway/commands/client.c
+++ b/sway/commands/client.c
@@ -5,9 +5,8 @@
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "util.h" 6#include "util.h"
7 7
8static void rebuild_textures_iterator(struct sway_container *con, void *data) { 8static void container_update_iterator(struct sway_container *con, void *data) {
9 container_update_marks_textures(con); 9 container_update(con);
10 container_update_title_textures(con);
11} 10}
12 11
13static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name, 12static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name,
@@ -18,6 +17,12 @@ static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name,
18 return error; 17 return error;
19 } 18 }
20 19
20 if (argc > 3 && strcmp(cmd_name, "client.focused_tab_title") == 0) {
21 sway_log(SWAY_ERROR,
22 "Warning: indicator and child_border colors have no effect for %s",
23 cmd_name);
24 }
25
21 struct border_colors colors = {0}; 26 struct border_colors colors = {0};
22 const char *ind_hex = argc > 3 ? argv[3] : default_indicator; 27 const char *ind_hex = argc > 3 ? argv[3] : default_indicator;
23 const char *child_hex = argc > 4 ? argv[4] : argv[1]; // def to background 28 const char *child_hex = argc > 4 ? argv[4] : argv[1]; // def to background
@@ -45,12 +50,7 @@ static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name,
45 memcpy(class, &colors, sizeof(struct border_colors)); 50 memcpy(class, &colors, sizeof(struct border_colors));
46 51
47 if (config->active) { 52 if (config->active) {
48 root_for_each_container(rebuild_textures_iterator, NULL); 53 root_for_each_container(container_update_iterator, NULL);
49
50 for (int i = 0; i < root->outputs->length; ++i) {
51 struct sway_output *output = root->outputs->items[i];
52 output_damage_whole(output);
53 }
54 } 54 }
55 55
56 return cmd_results_new(CMD_SUCCESS, NULL); 56 return cmd_results_new(CMD_SUCCESS, NULL);
@@ -80,3 +80,13 @@ struct cmd_results *cmd_client_noop(int argc, char **argv) {
80 sway_log(SWAY_INFO, "Warning: %s is ignored by sway", argv[-1]); 80 sway_log(SWAY_INFO, "Warning: %s is ignored by sway", argv[-1]);
81 return cmd_results_new(CMD_SUCCESS, NULL); 81 return cmd_results_new(CMD_SUCCESS, NULL);
82} 82}
83
84struct cmd_results *cmd_client_focused_tab_title(int argc, char **argv) {
85 struct cmd_results *result = handle_command(argc, argv,
86 "client.focused_tab_title",
87 &config->border_colors.focused_tab_title, "#2e9ef4ff");
88 if (result && result->status == CMD_SUCCESS) {
89 config->has_focused_tab_title = true;
90 }
91 return result;
92}
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index 39e48a44..8fca1909 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -7,6 +7,8 @@
7#include <signal.h> 7#include <signal.h>
8#include "sway/commands.h" 8#include "sway/commands.h"
9#include "sway/config.h" 9#include "sway/config.h"
10#include "sway/server.h"
11#include "sway/desktop/launcher.h"
10#include "sway/tree/container.h" 12#include "sway/tree/container.h"
11#include "sway/tree/root.h" 13#include "sway/tree/root.h"
12#include "sway/tree/workspace.h" 14#include "sway/tree/workspace.h"
@@ -24,11 +26,22 @@ struct cmd_results *cmd_exec_validate(int argc, char **argv) {
24 return error; 26 return error;
25} 27}
26 28
29static void export_xdga_token(struct launcher_ctx *ctx) {
30 const char *token = launcher_ctx_get_token_name(ctx);
31 setenv("XDG_ACTIVATION_TOKEN", token, 1);
32}
33
34static void export_startup_id(struct launcher_ctx *ctx) {
35 const char *token = launcher_ctx_get_token_name(ctx);
36 setenv("DESKTOP_STARTUP_ID", token, 1);
37}
38
27struct cmd_results *cmd_exec_process(int argc, char **argv) { 39struct cmd_results *cmd_exec_process(int argc, char **argv) {
28 struct cmd_results *error = NULL; 40 struct cmd_results *error = NULL;
29 char *tmp = NULL; 41 char *cmd = NULL;
42 bool no_startup_id = false;
30 if (strcmp(argv[0], "--no-startup-id") == 0) { 43 if (strcmp(argv[0], "--no-startup-id") == 0) {
31 sway_log(SWAY_INFO, "exec switch '--no-startup-id' not supported, ignored."); 44 no_startup_id = true;
32 --argc; ++argv; 45 --argc; ++argv;
33 if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) { 46 if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) {
34 return error; 47 return error;
@@ -36,17 +49,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
36 } 49 }
37 50
38 if (argc == 1 && (argv[0][0] == '\'' || argv[0][0] == '"')) { 51 if (argc == 1 && (argv[0][0] == '\'' || argv[0][0] == '"')) {
39 tmp = strdup(argv[0]); 52 cmd = strdup(argv[0]);
40 strip_quotes(tmp); 53 strip_quotes(cmd);
41 } else { 54 } else {
42 tmp = join_args(argv, argc); 55 cmd = join_args(argv, argc);
43 } 56 }
44 57
45 // Put argument into cmd array
46 char cmd[4096];
47 strncpy(cmd, tmp, sizeof(cmd) - 1);
48 cmd[sizeof(cmd) - 1] = 0;
49 free(tmp);
50 sway_log(SWAY_DEBUG, "Executing %s", cmd); 58 sway_log(SWAY_DEBUG, "Executing %s", cmd);
51 59
52 int fd[2]; 60 int fd[2];
@@ -55,18 +63,28 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
55 } 63 }
56 64
57 pid_t pid, child; 65 pid_t pid, child;
66 struct launcher_ctx *ctx = launcher_ctx_create_internal();
58 // Fork process 67 // Fork process
59 if ((pid = fork()) == 0) { 68 if ((pid = fork()) == 0) {
60 // Fork child process again 69 // Fork child process again
70 restore_nofile_limit();
61 setsid(); 71 setsid();
62 sigset_t set; 72 sigset_t set;
63 sigemptyset(&set); 73 sigemptyset(&set);
64 sigprocmask(SIG_SETMASK, &set, NULL); 74 sigprocmask(SIG_SETMASK, &set, NULL);
75 signal(SIGPIPE, SIG_DFL);
65 close(fd[0]); 76 close(fd[0]);
66 if ((child = fork()) == 0) { 77 if ((child = fork()) == 0) {
67 close(fd[1]); 78 close(fd[1]);
68 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL); 79 if (ctx) {
69 _exit(0); 80 export_xdga_token(ctx);
81 }
82 if (ctx && !no_startup_id) {
83 export_startup_id(ctx);
84 }
85 execlp("sh", "sh", "-c", cmd, (void *)NULL);
86 sway_log_errno(SWAY_ERROR, "execlp failed");
87 _exit(1);
70 } 88 }
71 ssize_t s = 0; 89 ssize_t s = 0;
72 while ((size_t)s < sizeof(pid_t)) { 90 while ((size_t)s < sizeof(pid_t)) {
@@ -75,10 +93,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
75 close(fd[1]); 93 close(fd[1]);
76 _exit(0); // Close child process 94 _exit(0); // Close child process
77 } else if (pid < 0) { 95 } else if (pid < 0) {
96 free(cmd);
78 close(fd[0]); 97 close(fd[0]);
79 close(fd[1]); 98 close(fd[1]);
80 return cmd_results_new(CMD_FAILURE, "fork() failed"); 99 return cmd_results_new(CMD_FAILURE, "fork() failed");
81 } 100 }
101 free(cmd);
82 close(fd[1]); // close write 102 close(fd[1]); // close write
83 ssize_t s = 0; 103 ssize_t s = 0;
84 while ((size_t)s < sizeof(pid_t)) { 104 while ((size_t)s < sizeof(pid_t)) {
@@ -89,8 +109,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
89 waitpid(pid, NULL, 0); 109 waitpid(pid, NULL, 0);
90 if (child > 0) { 110 if (child > 0) {
91 sway_log(SWAY_DEBUG, "Child process created with pid %d", child); 111 sway_log(SWAY_DEBUG, "Child process created with pid %d", child);
92 root_record_workspace_pid(child); 112 if (ctx != NULL) {
113 sway_log(SWAY_DEBUG, "Recording workspace for process %d", child);
114 ctx->pid = child;
115 }
93 } else { 116 } else {
117 launcher_ctx_destroy(ctx);
94 return cmd_results_new(CMD_FAILURE, "Second fork() failed"); 118 return cmd_results_new(CMD_FAILURE, "Second fork() failed");
95 } 119 }
96 120
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index ce123345..74f6522c 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -40,8 +40,8 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
40 // If the container is in a floating split container, 40 // If the container is in a floating split container,
41 // operate on the split container instead of the child. 41 // operate on the split container instead of the child.
42 if (container_is_floating_or_child(container)) { 42 if (container_is_floating_or_child(container)) {
43 while (container->parent) { 43 while (container->pending.parent) {
44 container = container->parent; 44 container = container->pending.parent;
45 } 45 }
46 } 46 }
47 47
@@ -51,8 +51,8 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
51 container_set_floating(container, wants_floating); 51 container_set_floating(container, wants_floating);
52 52
53 // Floating containers in the scratchpad should be ignored 53 // Floating containers in the scratchpad should be ignored
54 if (container->workspace) { 54 if (container->pending.workspace) {
55 arrange_workspace(container->workspace); 55 arrange_workspace(container->pending.workspace);
56 } 56 }
57 57
58 return cmd_results_new(CMD_SUCCESS, NULL); 58 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/floating_minmax_size.c b/sway/commands/floating_minmax_size.c
index 3a1d606a..e8c24ace 100644
--- a/sway/commands/floating_minmax_size.c
+++ b/sway/commands/floating_minmax_size.c
@@ -23,16 +23,16 @@ static struct cmd_results *handle_command(int argc, char **argv, char *cmd_name,
23 char *err; 23 char *err;
24 int width = (int)strtol(argv[0], &err, 10); 24 int width = (int)strtol(argv[0], &err, 10);
25 if (*err) { 25 if (*err) {
26 return cmd_results_new(CMD_INVALID, cmd_name, usage); 26 return cmd_results_new(CMD_INVALID, "%s", usage);
27 } 27 }
28 28
29 if (strcmp(argv[1], "x") != 0) { 29 if (strcmp(argv[1], "x") != 0) {
30 return cmd_results_new(CMD_INVALID, cmd_name, usage); 30 return cmd_results_new(CMD_INVALID, "%s", usage);
31 } 31 }
32 32
33 int height = (int)strtol(argv[2], &err, 10); 33 int height = (int)strtol(argv[2], &err, 10);
34 if (*err) { 34 if (*err) {
35 return cmd_results_new(CMD_INVALID, cmd_name, usage); 35 return cmd_results_new(CMD_INVALID, "%s", usage);
36 } 36 }
37 37
38 *config_width = width; 38 *config_width = width;
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 79b7aed5..facd82de 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -54,7 +54,7 @@ static bool get_direction_from_next_prev(struct sway_container *container,
54 } else { 54 } else {
55 return false; 55 return false;
56 } 56 }
57 57
58 return true; 58 return true;
59} 59}
60 60
@@ -141,9 +141,9 @@ static struct sway_node *node_get_in_direction_tiling(
141 struct sway_container *wrap_candidate = NULL; 141 struct sway_container *wrap_candidate = NULL;
142 struct sway_container *current = container; 142 struct sway_container *current = container;
143 while (current) { 143 while (current) {
144 if (current->fullscreen_mode == FULLSCREEN_WORKSPACE) { 144 if (current->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
145 // Fullscreen container with a direction - go straight to outputs 145 // Fullscreen container with a direction - go straight to outputs
146 struct sway_output *output = current->workspace->output; 146 struct sway_output *output = current->pending.workspace->output;
147 struct sway_output *new_output = 147 struct sway_output *new_output =
148 output_get_in_direction(output, dir); 148 output_get_in_direction(output, dir);
149 if (!new_output) { 149 if (!new_output) {
@@ -151,7 +151,7 @@ static struct sway_node *node_get_in_direction_tiling(
151 } 151 }
152 return get_node_in_output_direction(new_output, dir); 152 return get_node_in_output_direction(new_output, dir);
153 } 153 }
154 if (current->fullscreen_mode == FULLSCREEN_GLOBAL) { 154 if (current->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
155 return NULL; 155 return NULL;
156 } 156 }
157 157
@@ -202,11 +202,11 @@ static struct sway_node *node_get_in_direction_tiling(
202 } 202 }
203 } 203 }
204 204
205 current = current->parent; 205 current = current->pending.parent;
206 } 206 }
207 207
208 // Check a different output 208 // Check a different output
209 struct sway_output *output = container->workspace->output; 209 struct sway_output *output = container->pending.workspace->output;
210 struct sway_output *new_output = output_get_in_direction(output, dir); 210 struct sway_output *new_output = output_get_in_direction(output, dir);
211 if ((config->focus_wrapping != WRAP_WORKSPACE || 211 if ((config->focus_wrapping != WRAP_WORKSPACE ||
212 container->node.type == N_WORKSPACE) && new_output) { 212 container->node.type == N_WORKSPACE) && new_output) {
@@ -226,23 +226,23 @@ static struct sway_node *node_get_in_direction_tiling(
226static struct sway_node *node_get_in_direction_floating( 226static struct sway_node *node_get_in_direction_floating(
227 struct sway_container *con, struct sway_seat *seat, 227 struct sway_container *con, struct sway_seat *seat,
228 enum wlr_direction dir) { 228 enum wlr_direction dir) {
229 double ref_lx = con->x + con->width / 2; 229 double ref_lx = con->pending.x + con->pending.width / 2;
230 double ref_ly = con->y + con->height / 2; 230 double ref_ly = con->pending.y + con->pending.height / 2;
231 double closest_distance = DBL_MAX; 231 double closest_distance = DBL_MAX;
232 struct sway_container *closest_con = NULL; 232 struct sway_container *closest_con = NULL;
233 233
234 if (!con->workspace) { 234 if (!con->pending.workspace) {
235 return NULL; 235 return NULL;
236 } 236 }
237 237
238 for (int i = 0; i < con->workspace->floating->length; i++) { 238 for (int i = 0; i < con->pending.workspace->floating->length; i++) {
239 struct sway_container *floater = con->workspace->floating->items[i]; 239 struct sway_container *floater = con->pending.workspace->floating->items[i];
240 if (floater == con) { 240 if (floater == con) {
241 continue; 241 continue;
242 } 242 }
243 float distance = dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT 243 float distance = dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_RIGHT
244 ? (floater->x + floater->width / 2) - ref_lx 244 ? (floater->pending.x + floater->pending.width / 2) - ref_lx
245 : (floater->y + floater->height / 2) - ref_ly; 245 : (floater->pending.y + floater->pending.height / 2) - ref_ly;
246 if (dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_UP) { 246 if (dir == WLR_DIRECTION_LEFT || dir == WLR_DIRECTION_UP) {
247 distance = -distance; 247 distance = -distance;
248 } 248 }
@@ -267,6 +267,11 @@ static struct cmd_results *focus_mode(struct sway_workspace *ws,
267 new_focus = seat_get_focus_inactive_tiling(seat, ws); 267 new_focus = seat_get_focus_inactive_tiling(seat, ws);
268 } 268 }
269 if (new_focus) { 269 if (new_focus) {
270 struct sway_container *new_focus_view =
271 seat_get_focus_inactive_view(seat, &new_focus->node);
272 if (new_focus_view) {
273 new_focus = new_focus_view;
274 }
270 seat_set_focus_container(seat, new_focus); 275 seat_set_focus_container(seat, new_focus);
271 276
272 // If we're on the floating layer and the floating container area 277 // If we're on the floating layer and the floating container area
@@ -280,7 +285,7 @@ static struct cmd_results *focus_mode(struct sway_workspace *ws,
280 } 285 }
281 } else { 286 } else {
282 return cmd_results_new(CMD_FAILURE, 287 return cmd_results_new(CMD_FAILURE,
283 "Failed to find a %s container in workspace", 288 "Failed to find a %s container in workspace.",
284 floating ? "floating" : "tiling"); 289 floating ? "floating" : "tiling");
285 } 290 }
286 return cmd_results_new(CMD_SUCCESS, NULL); 291 return cmd_results_new(CMD_SUCCESS, NULL);
@@ -290,7 +295,7 @@ static struct cmd_results *focus_output(struct sway_seat *seat,
290 int argc, char **argv) { 295 int argc, char **argv) {
291 if (!argc) { 296 if (!argc) {
292 return cmd_results_new(CMD_INVALID, 297 return cmd_results_new(CMD_INVALID,
293 "Expected 'focus output <direction|name>'"); 298 "Expected 'focus output <direction|name>'.");
294 } 299 }
295 char *identifier = join_args(argv, argc); 300 char *identifier = join_args(argv, argc);
296 struct sway_output *output = output_by_name_or_id(identifier); 301 struct sway_output *output = output_by_name_or_id(identifier);
@@ -300,13 +305,13 @@ static struct cmd_results *focus_output(struct sway_seat *seat,
300 if (!parse_direction(identifier, &direction)) { 305 if (!parse_direction(identifier, &direction)) {
301 free(identifier); 306 free(identifier);
302 return cmd_results_new(CMD_INVALID, 307 return cmd_results_new(CMD_INVALID,
303 "There is no output with that name"); 308 "There is no output with that name.");
304 } 309 }
305 struct sway_workspace *ws = seat_get_focused_workspace(seat); 310 struct sway_workspace *ws = seat_get_focused_workspace(seat);
306 if (!ws) { 311 if (!ws) {
307 free(identifier); 312 free(identifier);
308 return cmd_results_new(CMD_FAILURE, 313 return cmd_results_new(CMD_FAILURE,
309 "No focused workspace to base directions off of"); 314 "No focused workspace to base directions off of.");
310 } 315 }
311 output = output_get_in_direction(ws->output, direction); 316 output = output_get_in_direction(ws->output, direction);
312 317
@@ -334,7 +339,7 @@ static struct cmd_results *focus_output(struct sway_seat *seat,
334static struct cmd_results *focus_parent(void) { 339static struct cmd_results *focus_parent(void) {
335 struct sway_seat *seat = config->handler_context.seat; 340 struct sway_seat *seat = config->handler_context.seat;
336 struct sway_container *con = config->handler_context.container; 341 struct sway_container *con = config->handler_context.container;
337 if (!con || con->fullscreen_mode) { 342 if (!con || con->pending.fullscreen_mode) {
338 return cmd_results_new(CMD_SUCCESS, NULL); 343 return cmd_results_new(CMD_SUCCESS, NULL);
339 } 344 }
340 struct sway_node *parent = node_get_parent(&con->node); 345 struct sway_node *parent = node_get_parent(&con->node);
@@ -370,13 +375,24 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
370 struct sway_seat *seat = config->handler_context.seat; 375 struct sway_seat *seat = config->handler_context.seat;
371 if (node->type < N_WORKSPACE) { 376 if (node->type < N_WORKSPACE) {
372 return cmd_results_new(CMD_FAILURE, 377 return cmd_results_new(CMD_FAILURE,
373 "Command 'focus' cannot be used above the workspace level"); 378 "Command 'focus' cannot be used above the workspace level.");
374 } 379 }
375 380
376 if (argc == 0 && container) { 381 if (argc == 0) {
382 if (!container) {
383 return cmd_results_new(CMD_FAILURE, "No container to focus was specified.");
384 }
385
377 if (container_is_scratchpad_hidden_or_child(container)) { 386 if (container_is_scratchpad_hidden_or_child(container)) {
378 root_scratchpad_show(container); 387 root_scratchpad_show(container);
379 } 388 }
389 // if we are switching to a container under a fullscreen window, we first
390 // need to exit fullscreen so that the newly focused container becomes visible
391 struct sway_container *obstructing = container_obstructing_fullscreen_container(container);
392 if (obstructing) {
393 container_fullscreen_disable(obstructing);
394 arrange_root();
395 }
380 seat_set_focus_container(seat, container); 396 seat_set_focus_container(seat, container);
381 seat_consider_warp_to_focus(seat); 397 seat_consider_warp_to_focus(seat);
382 container_raise_floating(container); 398 container_raise_floating(container);
@@ -439,7 +455,8 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
439 return cmd_results_new(CMD_FAILURE, ""); 455 return cmd_results_new(CMD_FAILURE, "");
440 } 456 }
441 struct sway_node *next_focus = NULL; 457 struct sway_node *next_focus = NULL;
442 if (container_is_floating(container)) { 458 if (container_is_floating(container) &&
459 container->pending.fullscreen_mode == FULLSCREEN_NONE) {
443 next_focus = node_get_in_direction_floating(container, seat, direction); 460 next_focus = node_get_in_direction_floating(container, seat, direction);
444 } else { 461 } else {
445 next_focus = node_get_in_direction_tiling(container, seat, direction, descend); 462 next_focus = node_get_in_direction_tiling(container, seat, direction, descend);
diff --git a/sway/commands/font.c b/sway/commands/font.c
index c54365b5..74bb6b9f 100644
--- a/sway/commands/font.c
+++ b/sway/commands/font.c
@@ -4,6 +4,7 @@
4#include "sway/config.h" 4#include "sway/config.h"
5#include "log.h" 5#include "log.h"
6#include "stringop.h" 6#include "stringop.h"
7#include <pango/pangocairo.h>
7 8
8struct cmd_results *cmd_font(int argc, char **argv) { 9struct cmd_results *cmd_font(int argc, char **argv) {
9 struct cmd_results *error = NULL; 10 struct cmd_results *error = NULL;
@@ -16,12 +17,34 @@ struct cmd_results *cmd_font(int argc, char **argv) {
16 if (strncmp(font, "pango:", 6) == 0) { 17 if (strncmp(font, "pango:", 6) == 0) {
17 config->pango_markup = true; 18 config->pango_markup = true;
18 config->font = strdup(font + 6); 19 config->font = strdup(font + 6);
20 free(font);
19 } else { 21 } else {
20 config->pango_markup = false; 22 config->pango_markup = false;
21 config->font = strdup(font); 23 config->font = font;
22 } 24 }
23 25
24 free(font); 26 // Parse the font early so we can reject it if it's not valid for pango.
25 config_update_font_height(true); 27 // Also avoids re-parsing each time we render text.
28 PangoFontDescription *font_description = pango_font_description_from_string(config->font);
29
30 const char *family = pango_font_description_get_family(font_description);
31 if (family == NULL) {
32 pango_font_description_free(font_description);
33 return cmd_results_new(CMD_FAILURE, "Invalid font family.");
34 }
35
36 const gint size = pango_font_description_get_size(font_description);
37 if (size == 0) {
38 pango_font_description_free(font_description);
39 return cmd_results_new(CMD_FAILURE, "Invalid font size.");
40 }
41
42 if (config->font_description != NULL) {
43 pango_font_description_free(config->font_description);
44 }
45
46 config->font_description = font_description;
47 config_update_font_height();
48
26 return cmd_results_new(CMD_SUCCESS, NULL); 49 return cmd_results_new(CMD_SUCCESS, NULL);
27} 50}
diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c
index ee9f4647..905e6776 100644
--- a/sway/commands/for_window.c
+++ b/sway/commands/for_window.c
@@ -14,7 +14,7 @@ struct cmd_results *cmd_for_window(int argc, char **argv) {
14 char *err_str = NULL; 14 char *err_str = NULL;
15 struct criteria *criteria = criteria_parse(argv[0], &err_str); 15 struct criteria *criteria = criteria_parse(argv[0], &err_str);
16 if (!criteria) { 16 if (!criteria) {
17 error = cmd_results_new(CMD_INVALID, err_str); 17 error = cmd_results_new(CMD_INVALID, "%s", err_str);
18 free(err_str); 18 free(err_str);
19 return error; 19 return error;
20 } 20 }
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index 3392a7f7..21c1e9a0 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -18,30 +18,19 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
18 return cmd_results_new(CMD_FAILURE, 18 return cmd_results_new(CMD_FAILURE,
19 "Can't run this command while there's no outputs connected."); 19 "Can't run this command while there's no outputs connected.");
20 } 20 }
21 struct sway_node *node = config->handler_context.node;
22 struct sway_container *container = config->handler_context.container; 21 struct sway_container *container = config->handler_context.container;
23 struct sway_workspace *workspace = config->handler_context.workspace;
24 if (node->type == N_WORKSPACE && workspace->tiling->length == 0) {
25 return cmd_results_new(CMD_FAILURE,
26 "Can't fullscreen an empty workspace");
27 }
28 22
29 // If in the scratchpad, operate on the highest container 23 if (!container) {
30 if (container && !container->workspace) { 24 // If the focus is not a container, do nothing successfully
31 while (container->parent) { 25 return cmd_results_new(CMD_SUCCESS, NULL);
32 container = container->parent; 26 } else if (!container->pending.workspace) {
33 } 27 // If in the scratchpad, operate on the highest container
34 } 28 while (container->pending.parent) {
35 29 container = container->pending.parent;
36 bool is_fullscreen = false;
37 for (struct sway_container *curr = container; curr; curr = curr->parent) {
38 if (curr->fullscreen_mode != FULLSCREEN_NONE) {
39 container = curr;
40 is_fullscreen = true;
41 break;
42 } 30 }
43 } 31 }
44 32
33 bool is_fullscreen = container->pending.fullscreen_mode != FULLSCREEN_NONE;
45 bool global = false; 34 bool global = false;
46 bool enable = !is_fullscreen; 35 bool enable = !is_fullscreen;
47 36
@@ -57,13 +46,6 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
57 global = strcasecmp(argv[1], "global") == 0; 46 global = strcasecmp(argv[1], "global") == 0;
58 } 47 }
59 48
60 if (enable && node->type == N_WORKSPACE) {
61 // Wrap the workspace's children in a container so we can fullscreen it
62 container = workspace_wrap_children(workspace);
63 workspace->layout = L_HORIZ;
64 seat_set_focus_container(config->handler_context.seat, container);
65 }
66
67 enum sway_fullscreen_mode mode = FULLSCREEN_NONE; 49 enum sway_fullscreen_mode mode = FULLSCREEN_NONE;
68 if (enable) { 50 if (enable) {
69 mode = global ? FULLSCREEN_GLOBAL : FULLSCREEN_WORKSPACE; 51 mode = global ? FULLSCREEN_GLOBAL : FULLSCREEN_WORKSPACE;
diff --git a/sway/commands/gaps.c b/sway/commands/gaps.c
index 021df843..1deeb56e 100644
--- a/sway/commands/gaps.c
+++ b/sway/commands/gaps.c
@@ -11,7 +11,8 @@
11enum gaps_op { 11enum gaps_op {
12 GAPS_OP_SET, 12 GAPS_OP_SET,
13 GAPS_OP_ADD, 13 GAPS_OP_ADD,
14 GAPS_OP_SUBTRACT 14 GAPS_OP_SUBTRACT,
15 GAPS_OP_TOGGLE
15}; 16};
16 17
17struct gaps_data { 18struct gaps_data {
@@ -102,6 +103,9 @@ static void apply_gaps_op(int *prop, enum gaps_op op, int amount) {
102 case GAPS_OP_SUBTRACT: 103 case GAPS_OP_SUBTRACT:
103 *prop -= amount; 104 *prop -= amount;
104 break; 105 break;
106 case GAPS_OP_TOGGLE:
107 *prop = *prop ? 0 : amount;
108 break;
105 } 109 }
106} 110}
107 111
@@ -133,9 +137,9 @@ static void configure_gaps(struct sway_workspace *ws, void *_data) {
133} 137}
134 138
135// gaps inner|outer|horizontal|vertical|top|right|bottom|left current|all 139// gaps inner|outer|horizontal|vertical|top|right|bottom|left current|all
136// set|plus|minus <px> 140// set|plus|minus|toggle <px>
137static const char expected_runtime[] = "'gaps inner|outer|horizontal|vertical|" 141static const char expected_runtime[] = "'gaps inner|outer|horizontal|vertical|"
138 "top|right|bottom|left current|all set|plus|minus <px>'"; 142 "top|right|bottom|left current|all set|plus|minus|toggle <px>'";
139static struct cmd_results *gaps_set_runtime(int argc, char **argv) { 143static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
140 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4); 144 struct cmd_results *error = checkarg(argc, "gaps", EXPECTED_EQUAL_TO, 4);
141 if (error) { 145 if (error) {
@@ -180,6 +184,8 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
180 data.operation = GAPS_OP_ADD; 184 data.operation = GAPS_OP_ADD;
181 } else if (strcasecmp(argv[2], "minus") == 0) { 185 } else if (strcasecmp(argv[2], "minus") == 0) {
182 data.operation = GAPS_OP_SUBTRACT; 186 data.operation = GAPS_OP_SUBTRACT;
187 } else if (strcasecmp(argv[2], "toggle") == 0) {
188 data.operation = GAPS_OP_TOGGLE;
183 } else { 189 } else {
184 return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime); 190 return cmd_results_new(CMD_INVALID, "Expected %s", expected_runtime);
185 } 191 }
@@ -200,7 +206,7 @@ static struct cmd_results *gaps_set_runtime(int argc, char **argv) {
200} 206}
201 207
202// gaps inner|outer|<dir>|<side> <px> - sets defaults for workspaces 208// gaps inner|outer|<dir>|<side> <px> - sets defaults for workspaces
203// gaps inner|outer|<dir>|<side> current|all set|plus|minus <px> - runtime only 209// gaps inner|outer|<dir>|<side> current|all set|plus|minus|toggle <px> - runtime only
204// <dir> = horizontal|vertical 210// <dir> = horizontal|vertical
205// <side> = top|right|bottom|left 211// <side> = top|right|bottom|left
206struct cmd_results *cmd_gaps(int argc, char **argv) { 212struct cmd_results *cmd_gaps(int argc, char **argv) {
diff --git a/sway/commands/gesture.c b/sway/commands/gesture.c
new file mode 100644
index 00000000..d4442cc3
--- /dev/null
+++ b/sway/commands/gesture.c
@@ -0,0 +1,166 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h"
3
4#include "gesture.h"
5#include "log.h"
6#include "stringop.h"
7#include "sway/commands.h"
8
9void free_gesture_binding(struct sway_gesture_binding *binding) {
10 if (!binding) {
11 return;
12 }
13 free(binding->input);
14 free(binding->command);
15 free(binding);
16}
17
18/**
19 * Returns true if the bindings have the same gesture type, direction, etc
20 */
21static bool binding_gesture_equal(struct sway_gesture_binding *binding_a,
22 struct sway_gesture_binding *binding_b) {
23 if (strcmp(binding_a->input, binding_b->input) != 0) {
24 return false;
25 }
26
27 if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) {
28 return false;
29 }
30
31 if ((binding_a->flags & BINDING_EXACT) !=
32 (binding_b->flags & BINDING_EXACT)) {
33 return false;
34 }
35 return true;
36}
37
38/**
39 * Add gesture binding to config
40 */
41static struct cmd_results *gesture_binding_add(
42 struct sway_gesture_binding *binding,
43 const char *gesturecombo, bool warn) {
44 list_t *mode_bindings = config->current_mode->gesture_bindings;
45 // overwrite the binding if it already exists
46 bool overwritten = false;
47 for (int i = 0; i < mode_bindings->length; ++i) {
48 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
49 if (binding_gesture_equal(binding, config_binding)) {
50 sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`",
51 gesturecombo, binding->command, config_binding->command);
52 if (warn) {
53 config_add_swaynag_warning("Overwriting binding"
54 "'%s' to `%s` from `%s`",
55 gesturecombo, binding->command,
56 config_binding->command);
57 }
58 free_gesture_binding(config_binding);
59 mode_bindings->items[i] = binding;
60 overwritten = true;
61 }
62 }
63
64 if (!overwritten) {
65 list_add(mode_bindings, binding);
66 sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`",
67 gesturecombo, binding->command);
68 }
69
70 return cmd_results_new(CMD_SUCCESS, NULL);
71}
72
73/**
74 * Remove gesture binding from config
75 */
76static struct cmd_results *gesture_binding_remove(
77 struct sway_gesture_binding *binding, const char *gesturecombo) {
78 list_t *mode_bindings = config->current_mode->gesture_bindings;
79 for (int i = 0; i < mode_bindings->length; ++i) {
80 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
81 if (binding_gesture_equal(binding, config_binding)) {
82 free_gesture_binding(config_binding);
83 free_gesture_binding(binding);
84 list_del(mode_bindings, i);
85 sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture",
86 gesturecombo);
87 return cmd_results_new(CMD_SUCCESS, NULL);
88 }
89 }
90
91 free_gesture_binding(binding);
92 return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`",
93 gesturecombo);
94}
95
96/**
97 * Parse and execute bindgesture or unbindgesture command.
98 */
99static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) {
100 int minargs = 2;
101 char *bindtype = "bindgesture";
102 if (unbind) {
103 minargs--;
104 bindtype = "unbindgesture";
105 }
106
107 struct cmd_results *error = NULL;
108 if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) {
109 return error;
110 }
111 struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding));
112 if (!binding) {
113 return cmd_results_new(CMD_FAILURE, "Unable to allocate binding");
114 }
115 binding->input = strdup("*");
116
117 bool warn = true;
118
119 // Handle flags
120 while (argc > 0) {
121 if (strcmp("--exact", argv[0]) == 0) {
122 binding->flags |= BINDING_EXACT;
123 } else if (strcmp("--no-warn", argv[0]) == 0) {
124 warn = false;
125 } else if (strncmp("--input-device=", argv[0],
126 strlen("--input-device=")) == 0) {
127 free(binding->input);
128 binding->input = strdup(argv[0] + strlen("--input-device="));
129 } else {
130 break;
131 }
132 argv++;
133 argc--;
134 }
135
136 if (argc < minargs) {
137 free(binding);
138 return cmd_results_new(CMD_FAILURE,
139 "Invalid %s command (expected at least %d "
140 "non-option arguments, got %d)", bindtype, minargs, argc);
141 }
142
143 char* errmsg = NULL;
144 if ((errmsg = gesture_parse(argv[0], &binding->gesture))) {
145 free(binding);
146 struct cmd_results *final = cmd_results_new(CMD_FAILURE,
147 "Invalid %s command (%s)",
148 bindtype, errmsg);
149 free(errmsg);
150 return final;
151 }
152
153 if (unbind) {
154 return gesture_binding_remove(binding, argv[0]);
155 }
156 binding->command = join_args(argv + 1, argc - 1);
157 return gesture_binding_add(binding, argv[0], warn);
158}
159
160struct cmd_results *cmd_bindgesture(int argc, char **argv) {
161 return cmd_bind_or_unbind_gesture(argc, argv, false);
162}
163
164struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
165 return cmd_bind_or_unbind_gesture(argc, argv, true);
166}
diff --git a/sway/commands/hide_edge_borders.c b/sway/commands/hide_edge_borders.c
index 9a1d8445..43bd6dc8 100644
--- a/sway/commands/hide_edge_borders.c
+++ b/sway/commands/hide_edge_borders.c
@@ -20,7 +20,7 @@ struct cmd_results *cmd_hide_edge_borders(int argc, char **argv) {
20 } 20 }
21 21
22 if (!argc) { 22 if (!argc) {
23 return cmd_results_new(CMD_INVALID, expected_syntax); 23 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
24 } 24 }
25 25
26 if (strcmp(argv[0], "none") == 0) { 26 if (strcmp(argv[0], "none") == 0) {
@@ -38,7 +38,7 @@ struct cmd_results *cmd_hide_edge_borders(int argc, char **argv) {
38 config->hide_edge_borders = E_NONE; 38 config->hide_edge_borders = E_NONE;
39 config->hide_edge_borders_smart = ESMART_NO_GAPS; 39 config->hide_edge_borders_smart = ESMART_NO_GAPS;
40 } else { 40 } else {
41 return cmd_results_new(CMD_INVALID, expected_syntax); 41 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
42 } 42 }
43 config->hide_lone_tab = hide_lone_tab; 43 config->hide_lone_tab = hide_lone_tab;
44 44
diff --git a/sway/commands/inhibit_idle.c b/sway/commands/inhibit_idle.c
index aebc2bf9..6125736a 100644
--- a/sway/commands/inhibit_idle.c
+++ b/sway/commands/inhibit_idle.c
@@ -41,7 +41,7 @@ struct cmd_results *cmd_inhibit_idle(int argc, char **argv) {
41 sway_idle_inhibit_v1_user_inhibitor_destroy(inhibitor); 41 sway_idle_inhibit_v1_user_inhibitor_destroy(inhibitor);
42 } else { 42 } else {
43 inhibitor->mode = mode; 43 inhibitor->mode = mode;
44 sway_idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1); 44 sway_idle_inhibit_v1_check_active();
45 } 45 }
46 } else if (!clear) { 46 } else if (!clear) {
47 sway_idle_inhibit_v1_user_inhibitor_register(con->view, mode); 47 sway_idle_inhibit_v1_user_inhibitor_register(con->view, mode);
diff --git a/sway/commands/input.c b/sway/commands/input.c
index c9bb8e06..306c40f7 100644
--- a/sway/commands/input.c
+++ b/sway/commands/input.c
@@ -7,13 +7,14 @@
7#include "stringop.h" 7#include "stringop.h"
8 8
9// must be in order for the bsearch 9// must be in order for the bsearch
10static struct cmd_handler input_handlers[] = { 10static const struct cmd_handler input_handlers[] = {
11 { "accel_profile", input_cmd_accel_profile }, 11 { "accel_profile", input_cmd_accel_profile },
12 { "calibration_matrix", input_cmd_calibration_matrix }, 12 { "calibration_matrix", input_cmd_calibration_matrix },
13 { "click_method", input_cmd_click_method }, 13 { "click_method", input_cmd_click_method },
14 { "drag", input_cmd_drag }, 14 { "drag", input_cmd_drag },
15 { "drag_lock", input_cmd_drag_lock }, 15 { "drag_lock", input_cmd_drag_lock },
16 { "dwt", input_cmd_dwt }, 16 { "dwt", input_cmd_dwt },
17 { "dwtp", input_cmd_dwtp },
17 { "events", input_cmd_events }, 18 { "events", input_cmd_events },
18 { "left_handed", input_cmd_left_handed }, 19 { "left_handed", input_cmd_left_handed },
19 { "map_from_region", input_cmd_map_from_region }, 20 { "map_from_region", input_cmd_map_from_region },
@@ -24,7 +25,9 @@ static struct cmd_handler input_handlers[] = {
24 { "pointer_accel", input_cmd_pointer_accel }, 25 { "pointer_accel", input_cmd_pointer_accel },
25 { "repeat_delay", input_cmd_repeat_delay }, 26 { "repeat_delay", input_cmd_repeat_delay },
26 { "repeat_rate", input_cmd_repeat_rate }, 27 { "repeat_rate", input_cmd_repeat_rate },
28 { "rotation_angle", input_cmd_rotation_angle },
27 { "scroll_button", input_cmd_scroll_button }, 29 { "scroll_button", input_cmd_scroll_button },
30 { "scroll_button_lock", input_cmd_scroll_button_lock },
28 { "scroll_factor", input_cmd_scroll_factor }, 31 { "scroll_factor", input_cmd_scroll_factor },
29 { "scroll_method", input_cmd_scroll_method }, 32 { "scroll_method", input_cmd_scroll_method },
30 { "tap", input_cmd_tap }, 33 { "tap", input_cmd_tap },
@@ -40,7 +43,7 @@ static struct cmd_handler input_handlers[] = {
40}; 43};
41 44
42// must be in order for the bsearch 45// must be in order for the bsearch
43static struct cmd_handler input_config_handlers[] = { 46static const struct cmd_handler input_config_handlers[] = {
44 { "xkb_capslock", input_cmd_xkb_capslock }, 47 { "xkb_capslock", input_cmd_xkb_capslock },
45 { "xkb_numlock", input_cmd_xkb_numlock }, 48 { "xkb_numlock", input_cmd_xkb_numlock },
46}; 49};
diff --git a/sway/commands/input/dwtp.c b/sway/commands/input/dwtp.c
new file mode 100644
index 00000000..232e2b26
--- /dev/null
+++ b/sway/commands/input/dwtp.c
@@ -0,0 +1,25 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/config.h"
4#include "sway/commands.h"
5#include "sway/input/input-manager.h"
6#include "util.h"
7
8struct cmd_results *input_cmd_dwtp(int argc, char **argv) {
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "dwtp", EXPECTED_AT_LEAST, 1))) {
11 return error;
12 }
13 struct input_config *ic = config->handler_context.input_config;
14 if (!ic) {
15 return cmd_results_new(CMD_FAILURE, "No input device defined.");
16 }
17
18 if (parse_boolean(argv[0], true)) {
19 ic->dwtp = LIBINPUT_CONFIG_DWTP_ENABLED;
20 } else {
21 ic->dwtp = LIBINPUT_CONFIG_DWTP_DISABLED;
22 }
23
24 return cmd_results_new(CMD_SUCCESS, NULL);
25}
diff --git a/sway/commands/input/events.c b/sway/commands/input/events.c
index 9405181a..08d99bf0 100644
--- a/sway/commands/input/events.c
+++ b/sway/commands/input/events.c
@@ -1,14 +1,19 @@
1#include <limits.h> 1#include <limits.h>
2#include <string.h> 2#include <string.h>
3#include <strings.h> 3#include <strings.h>
4#include <wlr/backend/libinput.h> 4#include <wlr/config.h>
5#include "sway/config.h" 5#include "sway/config.h"
6#include "sway/commands.h" 6#include "sway/commands.h"
7#include "sway/input/input-manager.h" 7#include "sway/input/input-manager.h"
8#include "log.h" 8#include "log.h"
9 9
10#if WLR_HAS_LIBINPUT_BACKEND
11#include <wlr/backend/libinput.h>
12#endif
13
10static void toggle_supported_send_events_for_device(struct input_config *ic, 14static void toggle_supported_send_events_for_device(struct input_config *ic,
11 struct sway_input_device *input_device) { 15 struct sway_input_device *input_device) {
16#if WLR_HAS_LIBINPUT_BACKEND
12 struct wlr_input_device *wlr_device = input_device->wlr_device; 17 struct wlr_input_device *wlr_device = input_device->wlr_device;
13 if (!wlr_input_device_is_libinput(wlr_device)) { 18 if (!wlr_input_device_is_libinput(wlr_device)) {
14 return; 19 return;
@@ -41,6 +46,7 @@ static void toggle_supported_send_events_for_device(struct input_config *ic,
41 } 46 }
42 47
43 ic->send_events = mode; 48 ic->send_events = mode;
49#endif
44} 50}
45 51
46static int mode_for_name(const char *name) { 52static int mode_for_name(const char *name) {
@@ -56,6 +62,7 @@ static int mode_for_name(const char *name) {
56 62
57static void toggle_select_send_events_for_device(struct input_config *ic, 63static void toggle_select_send_events_for_device(struct input_config *ic,
58 struct sway_input_device *input_device, int argc, char **argv) { 64 struct sway_input_device *input_device, int argc, char **argv) {
65#if WLR_HAS_LIBINPUT_BACKEND
59 if (!wlr_input_device_is_libinput(input_device->wlr_device)) { 66 if (!wlr_input_device_is_libinput(input_device->wlr_device)) {
60 return; 67 return;
61 } 68 }
@@ -72,6 +79,7 @@ static void toggle_select_send_events_for_device(struct input_config *ic,
72 } 79 }
73 } 80 }
74 ic->send_events = mode_for_name(argv[index % argc]); 81 ic->send_events = mode_for_name(argv[index % argc]);
82#endif
75} 83}
76 84
77static void toggle_send_events(int argc, char **argv) { 85static void toggle_send_events(int argc, char **argv) {
diff --git a/sway/commands/input/map_from_region.c b/sway/commands/input/map_from_region.c
index de00b714..4400e111 100644
--- a/sway/commands/input/map_from_region.c
+++ b/sway/commands/input/map_from_region.c
@@ -11,11 +11,21 @@ static bool parse_coords(const char *str, double *x, double *y, bool *mm) {
11 *mm = false; 11 *mm = false;
12 12
13 char *end; 13 char *end;
14 *x = strtod(str, &end); 14
15 if (end[0] != 'x') { 15 // Check for "0x" prefix to avoid strtod treating the string as hex
16 return false; 16 if (str[0] == '0' && str[1] == 'x') {
17 if (strlen(str) < 3) {
18 return false;
19 }
20 *x = 0;
21 end = (char *)str + 2;
22 } else {
23 *x = strtod(str, &end);
24 if (end[0] != 'x') {
25 return false;
26 }
27 ++end;
17 } 28 }
18 ++end;
19 29
20 *y = strtod(end, &end); 30 *y = strtod(end, &end);
21 if (end[0] == 'm') { 31 if (end[0] == 'm') {
diff --git a/sway/commands/input/map_to_region.c b/sway/commands/input/map_to_region.c
index e85495e5..ad535db2 100644
--- a/sway/commands/input/map_to_region.c
+++ b/sway/commands/input/map_to_region.c
@@ -1,7 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 2#include <stdlib.h>
3#include <string.h> 3#include <string.h>
4#include <wlr/types/wlr_box.h>
5#include "sway/commands.h" 4#include "sway/commands.h"
6#include "sway/config.h" 5#include "sway/config.h"
7 6
@@ -50,5 +49,5 @@ struct cmd_results *input_cmd_map_to_region(int argc, char **argv) {
50error: 49error:
51 free(ic->mapped_to_region); 50 free(ic->mapped_to_region);
52 ic->mapped_to_region = NULL; 51 ic->mapped_to_region = NULL;
53 return cmd_results_new(CMD_FAILURE, errstr); 52 return cmd_results_new(CMD_FAILURE, "%s", errstr);
54} 53}
diff --git a/sway/commands/input/rotation_angle.c b/sway/commands/input/rotation_angle.c
new file mode 100644
index 00000000..5e278fff
--- /dev/null
+++ b/sway/commands/input/rotation_angle.c
@@ -0,0 +1,29 @@
1#include <math.h>
2#include <stdlib.h>
3#include <string.h>
4#include "sway/config.h"
5#include "sway/commands.h"
6#include "sway/input/input-manager.h"
7#include "util.h"
8
9struct cmd_results *input_cmd_rotation_angle(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "rotation_angle", EXPECTED_AT_LEAST, 1))) {
12 return error;
13 }
14 struct input_config *ic = config->handler_context.input_config;
15 if (!ic) {
16 return cmd_results_new(CMD_FAILURE, "No input device defined.");
17 }
18
19 float rotation_angle = parse_float(argv[0]);
20 if (isnan(rotation_angle)) {
21 return cmd_results_new(CMD_INVALID,
22 "Invalid rotation_angle; expected float.");
23 } if (rotation_angle < 0 || rotation_angle > 360) {
24 return cmd_results_new(CMD_INVALID, "Input out of range [0, 360)");
25 }
26 ic->rotation_angle = rotation_angle;
27
28 return cmd_results_new(CMD_SUCCESS, NULL);
29}
diff --git a/sway/commands/input/scroll_button.c b/sway/commands/input/scroll_button.c
index 6b331419..81f69a6d 100644
--- a/sway/commands/input/scroll_button.c
+++ b/sway/commands/input/scroll_button.c
@@ -21,7 +21,7 @@ struct cmd_results *input_cmd_scroll_button(int argc, char **argv) {
21 char *message = NULL; 21 char *message = NULL;
22 uint32_t button = get_mouse_button(*argv, &message); 22 uint32_t button = get_mouse_button(*argv, &message);
23 if (message) { 23 if (message) {
24 error = cmd_results_new(CMD_INVALID, message); 24 error = cmd_results_new(CMD_INVALID, "%s", message);
25 free(message); 25 free(message);
26 return error; 26 return error;
27 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN 27 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN
diff --git a/sway/commands/input/scroll_button_lock.c b/sway/commands/input/scroll_button_lock.c
new file mode 100644
index 00000000..f96b6514
--- /dev/null
+++ b/sway/commands/input/scroll_button_lock.c
@@ -0,0 +1,26 @@
1#include <libinput.h>
2#include <string.h>
3#include <strings.h>
4#include "sway/config.h"
5#include "sway/commands.h"
6#include "sway/input/input-manager.h"
7#include "util.h"
8
9struct cmd_results *input_cmd_scroll_button_lock(int argc, char **argv) {
10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "scroll_button_lock", EXPECTED_AT_LEAST, 1))) {
12 return error;
13 }
14 struct input_config *ic = config->handler_context.input_config;
15 if (!ic) {
16 return cmd_results_new(CMD_FAILURE, "No input device defined.");
17 }
18
19 if (parse_boolean(argv[0], true)) {
20 ic->scroll_button_lock = LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED;
21 } else {
22 ic->scroll_button_lock = LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED;
23 }
24
25 return cmd_results_new(CMD_SUCCESS, NULL);
26}
diff --git a/sway/commands/input/xkb_switch_layout.c b/sway/commands/input/xkb_switch_layout.c
index d6548a68..3cce4ec8 100644
--- a/sway/commands/input/xkb_switch_layout.c
+++ b/sway/commands/input/xkb_switch_layout.c
@@ -1,10 +1,16 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <wlr/interfaces/wlr_keyboard.h>
3#include "sway/config.h" 4#include "sway/config.h"
4#include "sway/commands.h" 5#include "sway/commands.h"
5#include "sway/input/input-manager.h" 6#include "sway/input/input-manager.h"
6#include "log.h" 7#include "log.h"
7 8
9struct xkb_switch_layout_action {
10 struct wlr_keyboard *keyboard;
11 xkb_layout_index_t layout;
12};
13
8static void switch_layout(struct wlr_keyboard *kbd, xkb_layout_index_t idx) { 14static void switch_layout(struct wlr_keyboard *kbd, xkb_layout_index_t idx) {
9 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap); 15 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap);
10 if (idx >= num_layouts) { 16 if (idx >= num_layouts) {
@@ -28,10 +34,10 @@ static xkb_layout_index_t get_current_layout_index(struct wlr_keyboard *kbd) {
28 return layout_idx; 34 return layout_idx;
29} 35}
30 36
31static void switch_layout_relative(struct wlr_keyboard *kbd, int dir) { 37static xkb_layout_index_t get_layout_relative(struct wlr_keyboard *kbd, int dir) {
32 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap); 38 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap);
33 xkb_layout_index_t idx = get_current_layout_index(kbd); 39 xkb_layout_index_t idx = get_current_layout_index(kbd);
34 switch_layout(kbd, (idx + num_layouts + dir) % num_layouts); 40 return (idx + num_layouts + dir) % num_layouts;
35} 41}
36 42
37struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) { 43struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
@@ -66,6 +72,18 @@ struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
66 relative = 0; 72 relative = 0;
67 } 73 }
68 74
75 struct xkb_switch_layout_action *actions = calloc(
76 wl_list_length(&server.input->devices),
77 sizeof(struct xkb_switch_layout_action));
78 size_t actions_len = 0;
79
80 if (!actions) {
81 return cmd_results_new(CMD_FAILURE, "Unable to allocate actions");
82 }
83
84 /* Calculate new indexes first because switching a layout in one
85 keyboard may result in a change on other keyboards as well because
86 of keyboard groups. */
69 struct sway_input_device *dev; 87 struct sway_input_device *dev;
70 wl_list_for_each(dev, &server.input->devices, link) { 88 wl_list_for_each(dev, &server.input->devices, link) {
71 if (strcmp(ic->identifier, "*") != 0 && 89 if (strcmp(ic->identifier, "*") != 0 &&
@@ -76,12 +94,22 @@ struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
76 if (dev->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { 94 if (dev->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) {
77 continue; 95 continue;
78 } 96 }
97
98 struct xkb_switch_layout_action *action =
99 &actions[actions_len++];
100
101 action->keyboard = wlr_keyboard_from_input_device(dev->wlr_device);
79 if (relative) { 102 if (relative) {
80 switch_layout_relative(dev->wlr_device->keyboard, relative); 103 action->layout = get_layout_relative(action->keyboard, relative);
81 } else { 104 } else {
82 switch_layout(dev->wlr_device->keyboard, layout); 105 action->layout = layout;
83 } 106 }
84 } 107 }
85 108
109 for (size_t i = 0; i < actions_len; i++) {
110 switch_layout(actions[i].keyboard, actions[i].layout);
111 }
112 free(actions);
113
86 return cmd_results_new(CMD_SUCCESS, NULL); 114 return cmd_results_new(CMD_SUCCESS, NULL);
87} 115}
diff --git a/sway/commands/layout.c b/sway/commands/layout.c
index f2af183b..12ce4839 100644
--- a/sway/commands/layout.c
+++ b/sway/commands/layout.c
@@ -133,7 +133,7 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
133 133
134 // Operate on parent container, like i3. 134 // Operate on parent container, like i3.
135 if (container) { 135 if (container) {
136 container = container->parent; 136 container = container->pending.parent;
137 } 137 }
138 138
139 // We could be working with a container OR a workspace. These are different 139 // We could be working with a container OR a workspace. These are different
@@ -142,10 +142,10 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
142 enum sway_container_layout new_layout = L_NONE; 142 enum sway_container_layout new_layout = L_NONE;
143 enum sway_container_layout old_layout = L_NONE; 143 enum sway_container_layout old_layout = L_NONE;
144 if (container) { 144 if (container) {
145 old_layout = container->layout; 145 old_layout = container->pending.layout;
146 new_layout = get_layout(argc, argv, 146 new_layout = get_layout(argc, argv,
147 container->layout, container->prev_split_layout, 147 container->pending.layout, container->prev_split_layout,
148 container->workspace->output); 148 container->pending.workspace->output);
149 } else { 149 } else {
150 old_layout = workspace->layout; 150 old_layout = workspace->layout;
151 new_layout = get_layout(argc, argv, 151 new_layout = get_layout(argc, argv,
@@ -153,20 +153,20 @@ struct cmd_results *cmd_layout(int argc, char **argv) {
153 workspace->output); 153 workspace->output);
154 } 154 }
155 if (new_layout == L_NONE) { 155 if (new_layout == L_NONE) {
156 return cmd_results_new(CMD_INVALID, expected_syntax); 156 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
157 } 157 }
158 if (new_layout != old_layout) { 158 if (new_layout != old_layout) {
159 if (container) { 159 if (container) {
160 if (old_layout != L_TABBED && old_layout != L_STACKED) { 160 if (old_layout != L_TABBED && old_layout != L_STACKED) {
161 container->prev_split_layout = old_layout; 161 container->prev_split_layout = old_layout;
162 } 162 }
163 container->layout = new_layout; 163 container->pending.layout = new_layout;
164 container_update_representation(container); 164 container_update_representation(container);
165 } else if (config->handler_context.container) { 165 } else if (config->handler_context.container) {
166 // i3 avoids changing workspace layouts with a new container 166 // i3 avoids changing workspace layouts with a new container
167 // https://github.com/i3/i3/blob/3cd1c45eba6de073bc4300eebb4e1cc1a0c4479a/src/con.c#L1817 167 // https://github.com/i3/i3/blob/3cd1c45eba6de073bc4300eebb4e1cc1a0c4479a/src/con.c#L1817
168 container = workspace_wrap_children(workspace); 168 container = workspace_wrap_children(workspace);
169 container->layout = new_layout; 169 container->pending.layout = new_layout;
170 container_update_representation(container); 170 container_update_representation(container);
171 } else { 171 } else {
172 if (old_layout != L_TABBED && old_layout != L_STACKED) { 172 if (old_layout != L_TABBED && old_layout != L_STACKED) {
diff --git a/sway/commands/mark.c b/sway/commands/mark.c
index aa5f185c..30cf458c 100644
--- a/sway/commands/mark.c
+++ b/sway/commands/mark.c
@@ -59,7 +59,7 @@ struct cmd_results *cmd_mark(int argc, char **argv) {
59 } 59 }
60 60
61 free(mark); 61 free(mark);
62 container_update_marks_textures(container); 62 container_update_marks(container);
63 if (container->view) { 63 if (container->view) {
64 view_execute_criteria(container->view); 64 view_execute_criteria(container->view);
65 } 65 }
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index a5871dab..7263efcb 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -9,12 +9,14 @@
9#include "stringop.h" 9#include "stringop.h"
10 10
11// Must be in order for the bsearch 11// Must be in order for the bsearch
12static struct cmd_handler mode_handlers[] = { 12static const struct cmd_handler mode_handlers[] = {
13 { "bindcode", cmd_bindcode }, 13 { "bindcode", cmd_bindcode },
14 { "bindgesture", cmd_bindgesture },
14 { "bindswitch", cmd_bindswitch }, 15 { "bindswitch", cmd_bindswitch },
15 { "bindsym", cmd_bindsym }, 16 { "bindsym", cmd_bindsym },
16 { "set", cmd_set }, 17 { "set", cmd_set },
17 { "unbindcode", cmd_unbindcode }, 18 { "unbindcode", cmd_unbindcode },
19 { "unbindgesture", cmd_unbindgesture },
18 { "unbindswitch", cmd_unbindswitch }, 20 { "unbindswitch", cmd_unbindswitch },
19 { "unbindsym", cmd_unbindsym }, 21 { "unbindsym", cmd_unbindsym },
20}; 22};
@@ -59,6 +61,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
59 mode->keycode_bindings = create_list(); 61 mode->keycode_bindings = create_list();
60 mode->mouse_bindings = create_list(); 62 mode->mouse_bindings = create_list();
61 mode->switch_bindings = create_list(); 63 mode->switch_bindings = create_list();
64 mode->gesture_bindings = create_list();
62 mode->pango = pango; 65 mode->pango = pango;
63 list_add(config->modes, mode); 66 list_add(config->modes, mode);
64 } 67 }
diff --git a/sway/commands/move.c b/sway/commands/move.c
index f8f89f18..69ed06c0 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -113,8 +113,8 @@ static void container_move_to_container_from_direction(
113 struct sway_container *container, struct sway_container *destination, 113 struct sway_container *container, struct sway_container *destination,
114 enum wlr_direction move_dir) { 114 enum wlr_direction move_dir) {
115 if (destination->view) { 115 if (destination->view) {
116 if (destination->parent == container->parent && 116 if (destination->pending.parent == container->pending.parent &&
117 destination->workspace == container->workspace) { 117 destination->pending.workspace == container->pending.workspace) {
118 sway_log(SWAY_DEBUG, "Swapping siblings"); 118 sway_log(SWAY_DEBUG, "Swapping siblings");
119 list_t *siblings = container_get_siblings(container); 119 list_t *siblings = container_get_siblings(container);
120 int container_index = list_find(siblings, container); 120 int container_index = list_find(siblings, container);
@@ -126,28 +126,28 @@ static void container_move_to_container_from_direction(
126 int offset = 126 int offset =
127 move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP; 127 move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP;
128 int index = container_sibling_index(destination) + offset; 128 int index = container_sibling_index(destination) + offset;
129 if (destination->parent) { 129 if (destination->pending.parent) {
130 container_insert_child(destination->parent, container, index); 130 container_insert_child(destination->pending.parent, container, index);
131 } else { 131 } else {
132 workspace_insert_tiling(destination->workspace, 132 workspace_insert_tiling(destination->pending.workspace,
133 container, index); 133 container, index);
134 } 134 }
135 container->width = container->height = 0; 135 container->pending.width = container->pending.height = 0;
136 container->width_fraction = container->height_fraction = 0; 136 container->width_fraction = container->height_fraction = 0;
137 workspace_squash(destination->workspace); 137 workspace_squash(destination->pending.workspace);
138 } 138 }
139 return; 139 return;
140 } 140 }
141 141
142 if (is_parallel(destination->layout, move_dir)) { 142 if (is_parallel(destination->pending.layout, move_dir)) {
143 sway_log(SWAY_DEBUG, "Reparenting container (parallel)"); 143 sway_log(SWAY_DEBUG, "Reparenting container (parallel)");
144 int index = 144 int index =
145 move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ? 145 move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ?
146 0 : destination->children->length; 146 0 : destination->pending.children->length;
147 container_insert_child(destination, container, index); 147 container_insert_child(destination, container, index);
148 container->width = container->height = 0; 148 container->pending.width = container->pending.height = 0;
149 container->width_fraction = container->height_fraction = 0; 149 container->width_fraction = container->height_fraction = 0;
150 workspace_squash(destination->workspace); 150 workspace_squash(destination->pending.workspace);
151 return; 151 return;
152 } 152 }
153 153
@@ -168,7 +168,7 @@ static void container_move_to_container_from_direction(
168static void container_move_to_workspace_from_direction( 168static void container_move_to_workspace_from_direction(
169 struct sway_container *container, struct sway_workspace *workspace, 169 struct sway_container *container, struct sway_workspace *workspace,
170 enum wlr_direction move_dir) { 170 enum wlr_direction move_dir) {
171 container->width = container->height = 0; 171 container->pending.width = container->pending.height = 0;
172 container->width_fraction = container->height_fraction = 0; 172 container->width_fraction = container->height_fraction = 0;
173 173
174 if (is_parallel(workspace->layout, move_dir)) { 174 if (is_parallel(workspace->layout, move_dir)) {
@@ -188,8 +188,8 @@ static void container_move_to_workspace_from_direction(
188 workspace_add_tiling(workspace, container); 188 workspace_add_tiling(workspace, container);
189 return; 189 return;
190 } 190 }
191 while (focus_inactive->parent) { 191 while (focus_inactive->pending.parent) {
192 focus_inactive = focus_inactive->parent; 192 focus_inactive = focus_inactive->pending.parent;
193 } 193 }
194 container_move_to_container_from_direction(container, focus_inactive, 194 container_move_to_container_from_direction(container, focus_inactive,
195 move_dir); 195 move_dir);
@@ -197,25 +197,33 @@ static void container_move_to_workspace_from_direction(
197 197
198static void container_move_to_workspace(struct sway_container *container, 198static void container_move_to_workspace(struct sway_container *container,
199 struct sway_workspace *workspace) { 199 struct sway_workspace *workspace) {
200 if (container->workspace == workspace) { 200 if (container->pending.workspace == workspace) {
201 return; 201 return;
202 } 202 }
203 struct sway_workspace *old_workspace = container->workspace; 203 struct sway_workspace *old_workspace = container->pending.workspace;
204 if (container_is_floating(container)) { 204 if (container_is_floating(container)) {
205 struct sway_output *old_output = container->workspace->output; 205 struct sway_output *old_output = container->pending.workspace->output;
206 container_detach(container); 206 container_detach(container);
207 workspace_add_floating(workspace, container); 207 workspace_add_floating(workspace, container);
208 container_handle_fullscreen_reparent(container); 208 container_handle_fullscreen_reparent(container);
209 // If changing output, center it within the workspace 209 // If changing output, adjust the coordinates of the window.
210 if (old_output != workspace->output && !container->fullscreen_mode) { 210 if (old_output != workspace->output && !container->pending.fullscreen_mode) {
211 container_floating_move_to_center(container); 211 struct wlr_box workspace_box, old_workspace_box;
212 workspace_get_box(workspace, &workspace_box);
213 workspace_get_box(old_workspace, &old_workspace_box);
214 floating_fix_coordinates(container, &old_workspace_box, &workspace_box);
215 if (container->scratchpad && workspace->output) {
216 struct wlr_box output_box;
217 output_get_box(workspace->output, &output_box);
218 container->transform = workspace_box;
219 }
212 } 220 }
213 } else { 221 } else {
214 container_detach(container); 222 container_detach(container);
215 if (workspace_is_empty(workspace) && container->children) { 223 if (workspace_is_empty(workspace) && container->pending.children) {
216 workspace_unwrap_children(workspace, container); 224 workspace_unwrap_children(workspace, container);
217 } else { 225 } else {
218 container->width = container->height = 0; 226 container->pending.width = container->pending.height = 0;
219 container->width_fraction = container->height_fraction = 0; 227 container->width_fraction = container->height_fraction = 0;
220 workspace_add_tiling(workspace, container); 228 workspace_add_tiling(workspace, container);
221 } 229 }
@@ -237,13 +245,13 @@ static void container_move_to_container(struct sway_container *container,
237 return; 245 return;
238 } 246 }
239 if (container_is_floating(container)) { 247 if (container_is_floating(container)) {
240 container_move_to_workspace(container, destination->workspace); 248 container_move_to_workspace(container, destination->pending.workspace);
241 return; 249 return;
242 } 250 }
243 struct sway_workspace *old_workspace = container->workspace; 251 struct sway_workspace *old_workspace = container->pending.workspace;
244 252
245 container_detach(container); 253 container_detach(container);
246 container->width = container->height = 0; 254 container->pending.width = container->pending.height = 0;
247 container->width_fraction = container->height_fraction = 0; 255 container->width_fraction = container->height_fraction = 0;
248 256
249 if (destination->view) { 257 if (destination->view) {
@@ -256,12 +264,12 @@ static void container_move_to_container(struct sway_container *container,
256 ipc_event_window(container, "move"); 264 ipc_event_window(container, "move");
257 } 265 }
258 266
259 if (destination->workspace) { 267 if (destination->pending.workspace) {
260 workspace_focus_fullscreen(destination->workspace); 268 workspace_focus_fullscreen(destination->pending.workspace);
261 workspace_detect_urgent(destination->workspace); 269 workspace_detect_urgent(destination->pending.workspace);
262 } 270 }
263 271
264 if (old_workspace && old_workspace != destination->workspace) { 272 if (old_workspace && old_workspace != destination->pending.workspace) {
265 workspace_detect_urgent(old_workspace); 273 workspace_detect_urgent(old_workspace);
266 } 274 }
267} 275}
@@ -275,7 +283,7 @@ static bool container_move_to_next_output(struct sway_container *container,
275 if (!sway_assert(ws, "Expected output to have a workspace")) { 283 if (!sway_assert(ws, "Expected output to have a workspace")) {
276 return false; 284 return false;
277 } 285 }
278 switch (container->fullscreen_mode) { 286 switch (container->pending.fullscreen_mode) {
279 case FULLSCREEN_NONE: 287 case FULLSCREEN_NONE:
280 container_move_to_workspace_from_direction(container, ws, move_dir); 288 container_move_to_workspace_from_direction(container, ws, move_dir);
281 return true; 289 return true;
@@ -293,12 +301,12 @@ static bool container_move_to_next_output(struct sway_container *container,
293static bool container_move_in_direction(struct sway_container *container, 301static bool container_move_in_direction(struct sway_container *container,
294 enum wlr_direction move_dir) { 302 enum wlr_direction move_dir) {
295 // If moving a fullscreen view, only consider outputs 303 // If moving a fullscreen view, only consider outputs
296 switch (container->fullscreen_mode) { 304 switch (container->pending.fullscreen_mode) {
297 case FULLSCREEN_NONE: 305 case FULLSCREEN_NONE:
298 break; 306 break;
299 case FULLSCREEN_WORKSPACE: 307 case FULLSCREEN_WORKSPACE:
300 return container_move_to_next_output(container, 308 return container_move_to_next_output(container,
301 container->workspace->output, move_dir); 309 container->pending.workspace->output, move_dir);
302 case FULLSCREEN_GLOBAL: 310 case FULLSCREEN_GLOBAL:
303 return false; 311 return false;
304 } 312 }
@@ -317,26 +325,26 @@ static bool container_move_in_direction(struct sway_container *container,
317 while (!ancestor) { 325 while (!ancestor) {
318 // Don't allow containers to move out of their 326 // Don't allow containers to move out of their
319 // fullscreen or floating parent 327 // fullscreen or floating parent
320 if (current->fullscreen_mode || container_is_floating(current)) { 328 if (current->pending.fullscreen_mode || container_is_floating(current)) {
321 return false; 329 return false;
322 } 330 }
323 331
324 enum sway_container_layout parent_layout = container_parent_layout(current); 332 enum sway_container_layout parent_layout = container_parent_layout(current);
325 if (!is_parallel(parent_layout, move_dir)) { 333 if (!is_parallel(parent_layout, move_dir)) {
326 if (!current->parent) { 334 if (!current->pending.parent) {
327 // No parallel parent, so we reorient the workspace 335 // No parallel parent, so we reorient the workspace
328 current = workspace_wrap_children(current->workspace); 336 current = workspace_wrap_children(current->pending.workspace);
329 current->workspace->layout = 337 current->pending.workspace->layout =
330 move_dir == WLR_DIRECTION_LEFT || 338 move_dir == WLR_DIRECTION_LEFT ||
331 move_dir == WLR_DIRECTION_RIGHT ? 339 move_dir == WLR_DIRECTION_RIGHT ?
332 L_HORIZ : L_VERT; 340 L_HORIZ : L_VERT;
333 container->height = container->width = 0; 341 container->pending.height = container->pending.width = 0;
334 container->height_fraction = container->width_fraction = 0; 342 container->height_fraction = container->width_fraction = 0;
335 workspace_update_representation(current->workspace); 343 workspace_update_representation(current->pending.workspace);
336 wrapped = true; 344 wrapped = true;
337 } else { 345 } else {
338 // Keep looking for a parallel parent 346 // Keep looking for a parallel parent
339 current = current->parent; 347 current = current->pending.parent;
340 } 348 }
341 continue; 349 continue;
342 } 350 }
@@ -356,14 +364,14 @@ static bool container_move_in_direction(struct sway_container *container,
356 container_move_to_container_from_direction(container, 364 container_move_to_container_from_direction(container,
357 target, move_dir); 365 target, move_dir);
358 return true; 366 return true;
359 } else if (!container->parent) { 367 } else if (!container->pending.parent) {
360 // Container is at workspace level so we move it to the 368 // Container is at workspace level so we move it to the
361 // next workspace if possible 369 // next workspace if possible
362 return container_move_to_next_output(container, 370 return container_move_to_next_output(container,
363 current->workspace->output, move_dir); 371 current->pending.workspace->output, move_dir);
364 } else { 372 } else {
365 // Container has escaped its immediate parallel parent 373 // Container has escaped its immediate parallel parent
366 current = current->parent; 374 current = current->pending.parent;
367 continue; 375 continue;
368 } 376 }
369 } 377 }
@@ -377,31 +385,31 @@ static bool container_move_in_direction(struct sway_container *container,
377 container_move_to_container_from_direction(container, 385 container_move_to_container_from_direction(container,
378 target, move_dir); 386 target, move_dir);
379 return true; 387 return true;
380 } else if (!wrapped && !container->parent->parent && 388 } else if (!wrapped && !container->pending.parent->pending.parent &&
381 container->parent->children->length == 1) { 389 container->pending.parent->pending.children->length == 1) {
382 // Treat singleton children as if they are at workspace level like i3 390 // Treat singleton children as if they are at workspace level like i3
383 // https://github.com/i3/i3/blob/1d9160f2d247dbaa83fb62f02fd7041dec767fc2/src/move.c#L367 391 // https://github.com/i3/i3/blob/1d9160f2d247dbaa83fb62f02fd7041dec767fc2/src/move.c#L367
384 return container_move_to_next_output(container, 392 return container_move_to_next_output(container,
385 ancestor->workspace->output, move_dir); 393 ancestor->pending.workspace->output, move_dir);
386 } else { 394 } else {
387 // Container will be promoted 395 // Container will be promoted
388 struct sway_container *old_parent = container->parent; 396 struct sway_container *old_parent = container->pending.parent;
389 if (ancestor->parent) { 397 if (ancestor->pending.parent) {
390 // Container will move in with its parent 398 // Container will move in with its parent
391 container_insert_child(ancestor->parent, container, 399 container_insert_child(ancestor->pending.parent, container,
392 index + (offs < 0 ? 0 : 1)); 400 index + (offs < 0 ? 0 : 1));
393 } else { 401 } else {
394 // Container will move to workspace level, 402 // Container will move to workspace level,
395 // may be re-split by workspace_layout 403 // may be re-split by workspace_layout
396 workspace_insert_tiling(ancestor->workspace, container, 404 workspace_insert_tiling(ancestor->pending.workspace, container,
397 index + (offs < 0 ? 0 : 1)); 405 index + (offs < 0 ? 0 : 1));
398 } 406 }
399 ancestor->height = ancestor->width = 0; 407 ancestor->pending.height = ancestor->pending.width = 0;
400 ancestor->height_fraction = ancestor->width_fraction = 0; 408 ancestor->height_fraction = ancestor->width_fraction = 0;
401 if (old_parent) { 409 if (old_parent) {
402 container_reap_empty(old_parent); 410 container_reap_empty(old_parent);
403 } 411 }
404 workspace_squash(container->workspace); 412 workspace_squash(container->pending.workspace);
405 return true; 413 return true;
406 } 414 }
407} 415}
@@ -427,14 +435,14 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
427 container = workspace_wrap_children(workspace); 435 container = workspace_wrap_children(workspace);
428 } 436 }
429 437
430 if (container->fullscreen_mode == FULLSCREEN_GLOBAL) { 438 if (container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
431 return cmd_results_new(CMD_FAILURE, 439 return cmd_results_new(CMD_FAILURE,
432 "Can't move fullscreen global container"); 440 "Can't move fullscreen global container");
433 } 441 }
434 442
435 struct sway_seat *seat = config->handler_context.seat; 443 struct sway_seat *seat = config->handler_context.seat;
436 struct sway_container *old_parent = container->parent; 444 struct sway_container *old_parent = container->pending.parent;
437 struct sway_workspace *old_ws = container->workspace; 445 struct sway_workspace *old_ws = container->pending.workspace;
438 struct sway_output *old_output = old_ws ? old_ws->output : NULL; 446 struct sway_output *old_output = old_ws ? old_ws->output : NULL;
439 struct sway_node *destination = NULL; 447 struct sway_node *destination = NULL;
440 448
@@ -462,7 +470,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
462 if (strcasecmp(argv[1], "number") == 0) { 470 if (strcasecmp(argv[1], "number") == 0) {
463 // move [window|container] [to] "workspace number x" 471 // move [window|container] [to] "workspace number x"
464 if (argc < 3) { 472 if (argc < 3) {
465 return cmd_results_new(CMD_INVALID, expected_syntax); 473 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
466 } 474 }
467 if (!isdigit(argv[2][0])) { 475 if (!isdigit(argv[2][0])) {
468 return cmd_results_new(CMD_INVALID, 476 return cmd_results_new(CMD_INVALID,
@@ -508,7 +516,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
508 destination = dst ? &dst->node : &ws->node; 516 destination = dst ? &dst->node : &ws->node;
509 } else if (strcasecmp(argv[0], "output") == 0) { 517 } else if (strcasecmp(argv[0], "output") == 0) {
510 struct sway_output *new_output = output_in_direction(argv[1], 518 struct sway_output *new_output = output_in_direction(argv[1],
511 old_output, container->x, container->y); 519 old_output, container->pending.x, container->pending.y);
512 if (!new_output) { 520 if (!new_output) {
513 return cmd_results_new(CMD_FAILURE, 521 return cmd_results_new(CMD_FAILURE,
514 "Can't find output with name/direction '%s'", argv[1]); 522 "Can't find output with name/direction '%s'", argv[1]);
@@ -522,7 +530,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
522 } 530 }
523 destination = &dest_con->node; 531 destination = &dest_con->node;
524 } else { 532 } else {
525 return cmd_results_new(CMD_INVALID, expected_syntax); 533 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
526 } 534 }
527 535
528 if (destination->type == N_CONTAINER && 536 if (destination->type == N_CONTAINER &&
@@ -686,6 +694,9 @@ static struct cmd_results *cmd_move_workspace(int argc, char **argv) {
686 arrange_output(old_output); 694 arrange_output(old_output);
687 arrange_output(new_output); 695 arrange_output(new_output);
688 696
697 struct sway_seat *seat = config->handler_context.seat;
698 seat_consider_warp_to_focus(seat);
699
689 return cmd_results_new(CMD_SUCCESS, NULL); 700 return cmd_results_new(CMD_SUCCESS, NULL);
690} 701}
691 702
@@ -706,12 +717,12 @@ static struct cmd_results *cmd_move_in_direction(
706 "Cannot move workspaces in a direction"); 717 "Cannot move workspaces in a direction");
707 } 718 }
708 if (container_is_floating(container)) { 719 if (container_is_floating(container)) {
709 if (container->fullscreen_mode) { 720 if (container->pending.fullscreen_mode) {
710 return cmd_results_new(CMD_FAILURE, 721 return cmd_results_new(CMD_FAILURE,
711 "Cannot move fullscreen floating container"); 722 "Cannot move fullscreen floating container");
712 } 723 }
713 double lx = container->x; 724 double lx = container->pending.x;
714 double ly = container->y; 725 double ly = container->pending.y;
715 switch (direction) { 726 switch (direction) {
716 case WLR_DIRECTION_LEFT: 727 case WLR_DIRECTION_LEFT:
717 lx -= move_amt; 728 lx -= move_amt;
@@ -729,8 +740,8 @@ static struct cmd_results *cmd_move_in_direction(
729 container_floating_move_to(container, lx, ly); 740 container_floating_move_to(container, lx, ly);
730 return cmd_results_new(CMD_SUCCESS, NULL); 741 return cmd_results_new(CMD_SUCCESS, NULL);
731 } 742 }
732 struct sway_workspace *old_ws = container->workspace; 743 struct sway_workspace *old_ws = container->pending.workspace;
733 struct sway_container *old_parent = container->parent; 744 struct sway_container *old_parent = container->pending.parent;
734 745
735 if (!container_move_in_direction(container, direction)) { 746 if (!container_move_in_direction(container, direction)) {
736 // Container didn't move 747 // Container didn't move
@@ -744,7 +755,7 @@ static struct cmd_results *cmd_move_in_direction(
744 workspace_consider_destroy(old_ws); 755 workspace_consider_destroy(old_ws);
745 } 756 }
746 757
747 struct sway_workspace *new_ws = container->workspace; 758 struct sway_workspace *new_ws = container->pending.workspace;
748 759
749 if (root->fullscreen_global) { 760 if (root->fullscreen_global) {
750 arrange_root(); 761 arrange_root();
@@ -781,22 +792,22 @@ static struct cmd_results *cmd_move_to_position_pointer(
781 } 792 }
782 struct wlr_cursor *cursor = seat->cursor->cursor; 793 struct wlr_cursor *cursor = seat->cursor->cursor;
783 /* Determine where to put the window. */ 794 /* Determine where to put the window. */
784 double lx = cursor->x - container->width / 2; 795 double lx = cursor->x - container->pending.width / 2;
785 double ly = cursor->y - container->height / 2; 796 double ly = cursor->y - container->pending.height / 2;
786 797
787 /* Correct target coordinates to be in bounds (on screen). */ 798 /* Correct target coordinates to be in bounds (on screen). */
788 struct wlr_output *output = wlr_output_layout_output_at( 799 struct wlr_output *output = wlr_output_layout_output_at(
789 root->output_layout, cursor->x, cursor->y); 800 root->output_layout, cursor->x, cursor->y);
790 if (output) { 801 if (output) {
791 struct wlr_box *box = 802 struct wlr_box box;
792 wlr_output_layout_get_box(root->output_layout, output); 803 wlr_output_layout_get_box(root->output_layout, output, &box);
793 lx = fmax(lx, box->x); 804 lx = fmax(lx, box.x);
794 ly = fmax(ly, box->y); 805 ly = fmax(ly, box.y);
795 if (lx + container->width > box->x + box->width) { 806 if (lx + container->pending.width > box.x + box.width) {
796 lx = box->x + box->width - container->width; 807 lx = box.x + box.width - container->pending.width;
797 } 808 }
798 if (ly + container->height > box->y + box->height) { 809 if (ly + container->pending.height > box.y + box.height) {
799 ly = box->y + box->height - container->height; 810 ly = box.y + box.height - container->pending.height;
800 } 811 }
801 } 812 }
802 813
@@ -818,7 +829,7 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
818 } 829 }
819 830
820 if (!argc) { 831 if (!argc) {
821 return cmd_results_new(CMD_INVALID, expected_position_syntax); 832 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
822 } 833 }
823 834
824 bool absolute = false; 835 bool absolute = false;
@@ -828,41 +839,41 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
828 ++argv; 839 ++argv;
829 } 840 }
830 if (!argc) { 841 if (!argc) {
831 return cmd_results_new(CMD_INVALID, expected_position_syntax); 842 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
832 } 843 }
833 if (strcmp(argv[0], "position") == 0) { 844 if (strcmp(argv[0], "position") == 0) {
834 --argc; 845 --argc;
835 ++argv; 846 ++argv;
836 } 847 }
837 if (!argc) { 848 if (!argc) {
838 return cmd_results_new(CMD_INVALID, expected_position_syntax); 849 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
839 } 850 }
840 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 || 851 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 ||
841 strcmp(argv[0], "pointer") == 0) { 852 strcmp(argv[0], "pointer") == 0) {
842 if (absolute) { 853 if (absolute) {
843 return cmd_results_new(CMD_INVALID, expected_position_syntax); 854 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
844 } 855 }
845 return cmd_move_to_position_pointer(container); 856 return cmd_move_to_position_pointer(container);
846 } else if (strcmp(argv[0], "center") == 0) { 857 } else if (strcmp(argv[0], "center") == 0) {
847 double lx, ly; 858 double lx, ly;
848 if (absolute) { 859 if (absolute) {
849 lx = root->x + (root->width - container->width) / 2; 860 lx = root->x + (root->width - container->pending.width) / 2;
850 ly = root->y + (root->height - container->height) / 2; 861 ly = root->y + (root->height - container->pending.height) / 2;
851 } else { 862 } else {
852 struct sway_workspace *ws = container->workspace; 863 struct sway_workspace *ws = container->pending.workspace;
853 if (!ws) { 864 if (!ws) {
854 struct sway_seat *seat = config->handler_context.seat; 865 struct sway_seat *seat = config->handler_context.seat;
855 ws = seat_get_focused_workspace(seat); 866 ws = seat_get_focused_workspace(seat);
856 } 867 }
857 lx = ws->x + (ws->width - container->width) / 2; 868 lx = ws->x + (ws->width - container->pending.width) / 2;
858 ly = ws->y + (ws->height - container->height) / 2; 869 ly = ws->y + (ws->height - container->pending.height) / 2;
859 } 870 }
860 container_floating_move_to(container, lx, ly); 871 container_floating_move_to(container, lx, ly);
861 return cmd_results_new(CMD_SUCCESS, NULL); 872 return cmd_results_new(CMD_SUCCESS, NULL);
862 } 873 }
863 874
864 if (argc < 2) { 875 if (argc < 2) {
865 return cmd_results_new(CMD_FAILURE, expected_position_syntax); 876 return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax);
866 } 877 }
867 878
868 struct movement_amount lx = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; 879 struct movement_amount lx = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID };
@@ -874,19 +885,23 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
874 return cmd_results_new(CMD_INVALID, "Invalid x position specified"); 885 return cmd_results_new(CMD_INVALID, "Invalid x position specified");
875 } 886 }
876 887
888 if (argc < 1) {
889 return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax);
890 }
891
877 struct movement_amount ly = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; 892 struct movement_amount ly = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID };
878 // Y direction 893 // Y direction
879 num_consumed_args = parse_movement_amount(argc, argv, &ly); 894 num_consumed_args = parse_movement_amount(argc, argv, &ly);
880 argc -= num_consumed_args; 895 argc -= num_consumed_args;
881 argv += num_consumed_args; 896 argv += num_consumed_args;
882 if (argc > 0) { 897 if (argc > 0) {
883 return cmd_results_new(CMD_INVALID, expected_position_syntax); 898 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
884 } 899 }
885 if (ly.unit == MOVEMENT_UNIT_INVALID) { 900 if (ly.unit == MOVEMENT_UNIT_INVALID) {
886 return cmd_results_new(CMD_INVALID, "Invalid y position specified"); 901 return cmd_results_new(CMD_INVALID, "Invalid y position specified");
887 } 902 }
888 903
889 struct sway_workspace *ws = container->workspace; 904 struct sway_workspace *ws = container->pending.workspace;
890 if (!ws) { 905 if (!ws) {
891 struct sway_seat *seat = config->handler_context.seat; 906 struct sway_seat *seat = config->handler_context.seat;
892 ws = seat_get_focused_workspace(seat); 907 ws = seat_get_focused_workspace(seat);
@@ -960,14 +975,14 @@ static struct cmd_results *cmd_move_to_scratchpad(void) {
960 // If the container is in a floating split container, 975 // If the container is in a floating split container,
961 // operate on the split container instead of the child. 976 // operate on the split container instead of the child.
962 if (container_is_floating_or_child(con)) { 977 if (container_is_floating_or_child(con)) {
963 while (con->parent) { 978 while (con->pending.parent) {
964 con = con->parent; 979 con = con->pending.parent;
965 } 980 }
966 } 981 }
967 982
968 if (!con->scratchpad) { 983 if (!con->scratchpad) {
969 root_scratchpad_add_container(con, NULL); 984 root_scratchpad_add_container(con, NULL);
970 } else if (con->workspace) { 985 } else if (con->pending.workspace) {
971 root_scratchpad_hide(con); 986 root_scratchpad_hide(con);
972 } 987 }
973 return cmd_results_new(CMD_SUCCESS, NULL); 988 return cmd_results_new(CMD_SUCCESS, NULL);
@@ -1026,13 +1041,13 @@ struct cmd_results *cmd_move(int argc, char **argv) {
1026 } 1041 }
1027 1042
1028 if (!argc) { 1043 if (!argc) {
1029 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1044 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1030 } 1045 }
1031 1046
1032 // Only `move [window|container] [to] workspace` supports 1047 // Only `move [window|container] [to] workspace` supports
1033 // `--no-auto-back-and-forth` so treat others as invalid syntax 1048 // `--no-auto-back-and-forth` so treat others as invalid syntax
1034 if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) { 1049 if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) {
1035 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1050 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1036 } 1051 }
1037 1052
1038 if (strcasecmp(argv[0], "workspace") == 0 || 1053 if (strcasecmp(argv[0], "workspace") == 0 ||
@@ -1046,5 +1061,5 @@ struct cmd_results *cmd_move(int argc, char **argv) {
1046 strcasecmp(argv[1], "position") == 0)) { 1061 strcasecmp(argv[1], "position") == 0)) {
1047 return cmd_move_to_position(argc, argv); 1062 return cmd_move_to_position(argc, argv);
1048 } 1063 }
1049 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1064 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1050} 1065}
diff --git a/sway/commands/no_focus.c b/sway/commands/no_focus.c
index 2001e04f..ccfdec82 100644
--- a/sway/commands/no_focus.c
+++ b/sway/commands/no_focus.c
@@ -13,7 +13,7 @@ struct cmd_results *cmd_no_focus(int argc, char **argv) {
13 char *err_str = NULL; 13 char *err_str = NULL;
14 struct criteria *criteria = criteria_parse(argv[0], &err_str); 14 struct criteria *criteria = criteria_parse(argv[0], &err_str);
15 if (!criteria) { 15 if (!criteria) {
16 error = cmd_results_new(CMD_INVALID, err_str); 16 error = cmd_results_new(CMD_INVALID, "%s", err_str);
17 free(err_str); 17 free(err_str);
18 return error; 18 return error;
19 } 19 }
diff --git a/sway/commands/opacity.c b/sway/commands/opacity.c
index 96e6228e..610cecc6 100644
--- a/sway/commands/opacity.c
+++ b/sway/commands/opacity.c
@@ -37,6 +37,7 @@ struct cmd_results *cmd_opacity(int argc, char **argv) {
37 } 37 }
38 38
39 con->alpha = val; 39 con->alpha = val;
40 container_damage_whole(con); 40 container_update(con);
41
41 return cmd_results_new(CMD_SUCCESS, NULL); 42 return cmd_results_new(CMD_SUCCESS, NULL);
42} 43}
diff --git a/sway/commands/output.c b/sway/commands/output.c
index 5186a2ba..df32c673 100644
--- a/sway/commands/output.c
+++ b/sway/commands/output.c
@@ -6,7 +6,7 @@
6#include "log.h" 6#include "log.h"
7 7
8// must be in order for the bsearch 8// must be in order for the bsearch
9static struct cmd_handler output_handlers[] = { 9static const struct cmd_handler output_handlers[] = {
10 { "adaptive_sync", output_cmd_adaptive_sync }, 10 { "adaptive_sync", output_cmd_adaptive_sync },
11 { "background", output_cmd_background }, 11 { "background", output_cmd_background },
12 { "bg", output_cmd_background }, 12 { "bg", output_cmd_background },
@@ -15,8 +15,11 @@ static struct cmd_handler output_handlers[] = {
15 { "enable", output_cmd_enable }, 15 { "enable", output_cmd_enable },
16 { "max_render_time", output_cmd_max_render_time }, 16 { "max_render_time", output_cmd_max_render_time },
17 { "mode", output_cmd_mode }, 17 { "mode", output_cmd_mode },
18 { "modeline", output_cmd_modeline },
18 { "pos", output_cmd_position }, 19 { "pos", output_cmd_position },
19 { "position", output_cmd_position }, 20 { "position", output_cmd_position },
21 { "power", output_cmd_power },
22 { "render_bit_depth", output_cmd_render_bit_depth },
20 { "res", output_cmd_mode }, 23 { "res", output_cmd_mode },
21 { "resolution", output_cmd_mode }, 24 { "resolution", output_cmd_mode },
22 { "scale", output_cmd_scale }, 25 { "scale", output_cmd_scale },
@@ -24,6 +27,7 @@ static struct cmd_handler output_handlers[] = {
24 { "subpixel", output_cmd_subpixel }, 27 { "subpixel", output_cmd_subpixel },
25 { "toggle", output_cmd_toggle }, 28 { "toggle", output_cmd_toggle },
26 { "transform", output_cmd_transform }, 29 { "transform", output_cmd_transform },
30 { "unplug", output_cmd_unplug },
27}; 31};
28 32
29struct cmd_results *cmd_output(int argc, char **argv) { 33struct cmd_results *cmd_output(int argc, char **argv) {
@@ -32,9 +36,9 @@ struct cmd_results *cmd_output(int argc, char **argv) {
32 return error; 36 return error;
33 } 37 }
34 38
35 // The NOOP-1 output is a dummy output used when there's no outputs 39 // The HEADLESS-1 output is a dummy output used when there's no outputs
36 // connected. It should never be configured. 40 // connected. It should never be configured.
37 if (strcasecmp(argv[0], root->noop_output->wlr_output->name) == 0) { 41 if (strcasecmp(argv[0], root->fallback_output->wlr_output->name) == 0) {
38 return cmd_results_new(CMD_FAILURE, 42 return cmd_results_new(CMD_FAILURE,
39 "Refusing to configure the no op output"); 43 "Refusing to configure the no op output");
40 } 44 }
@@ -51,7 +55,7 @@ struct cmd_results *cmd_output(int argc, char **argv) {
51 if (!sway_output) { 55 if (!sway_output) {
52 return cmd_results_new(CMD_FAILURE, "Unknown output"); 56 return cmd_results_new(CMD_FAILURE, "Unknown output");
53 } 57 }
54 if (sway_output == root->noop_output) { 58 if (sway_output == root->fallback_output) {
55 return cmd_results_new(CMD_FAILURE, 59 return cmd_results_new(CMD_FAILURE,
56 "Refusing to configure the no op output"); 60 "Refusing to configure the no op output");
57 } 61 }
diff --git a/sway/commands/output/background.c b/sway/commands/output/background.c
index 68ee9fe1..d691295f 100644
--- a/sway/commands/output/background.c
+++ b/sway/commands/output/background.c
@@ -102,19 +102,19 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
102 } 102 }
103 103
104 char *conf_path = dirname(conf); 104 char *conf_path = dirname(conf);
105 char *rel_path = src; 105 char *real_src = malloc(strlen(conf_path) + strlen(src) + 2);
106 src = malloc(strlen(conf_path) + strlen(src) + 2); 106 if (!real_src) {
107 if (!src) { 107 free(src);
108 free(rel_path);
109 free(conf); 108 free(conf);
110 sway_log(SWAY_ERROR, "Unable to allocate memory"); 109 sway_log(SWAY_ERROR, "Unable to allocate memory");
111 return cmd_results_new(CMD_FAILURE, 110 return cmd_results_new(CMD_FAILURE,
112 "Unable to allocate resources"); 111 "Unable to allocate resources");
113 } 112 }
114 113
115 sprintf(src, "%s/%s", conf_path, rel_path); 114 snprintf(real_src, strlen(conf_path) + strlen(src) + 2, "%s/%s", conf_path, src);
116 free(rel_path); 115 free(src);
117 free(conf); 116 free(conf);
117 src = real_src;
118 } 118 }
119 119
120 bool can_access = access(src, F_OK) != -1; 120 bool can_access = access(src, F_OK) != -1;
@@ -123,7 +123,10 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
123 src); 123 src);
124 config_add_swaynag_warning("Unable to access background file '%s'", 124 config_add_swaynag_warning("Unable to access background file '%s'",
125 src); 125 src);
126 struct cmd_results *result = cmd_results_new(CMD_FAILURE,
127 "unable to access background file '%s'", src);
126 free(src); 128 free(src);
129 return result;
127 } else { 130 } else {
128 output->background = src; 131 output->background = src;
129 output->background_option = strdup(mode); 132 output->background_option = strdup(mode);
diff --git a/sway/commands/output/dpms.c b/sway/commands/output/dpms.c
index 9d75a80e..c7adbd58 100644
--- a/sway/commands/output/dpms.c
+++ b/sway/commands/output/dpms.c
@@ -1,22 +1,8 @@
1#include "log.h"
1#include "sway/commands.h" 2#include "sway/commands.h"
2#include "sway/config.h"
3#include "util.h"
4 3
5struct cmd_results *output_cmd_dpms(int argc, char **argv) { 4struct cmd_results *output_cmd_dpms(int argc, char **argv) {
6 if (!config->handler_context.output_config) { 5 sway_log(SWAY_INFO, "The \"output dpms\" command is deprecated, "
7 return cmd_results_new(CMD_FAILURE, "Missing output config"); 6 "use \"output power\" instead");
8 } 7 return output_cmd_power(argc, argv);
9 if (!argc) {
10 return cmd_results_new(CMD_INVALID, "Missing dpms argument.");
11 }
12
13 if (parse_boolean(argv[0], true)) {
14 config->handler_context.output_config->dpms_state = DPMS_ON;
15 } else {
16 config->handler_context.output_config->dpms_state = DPMS_OFF;
17 }
18
19 config->handler_context.leftovers.argc = argc - 1;
20 config->handler_context.leftovers.argv = argv + 1;
21 return NULL;
22} 8}
diff --git a/sway/commands/output/mode.c b/sway/commands/output/mode.c
index 5b710713..019d625a 100644
--- a/sway/commands/output/mode.c
+++ b/sway/commands/output/mode.c
@@ -20,6 +20,9 @@ struct cmd_results *output_cmd_mode(int argc, char **argv) {
20 output->custom_mode = 0; 20 output->custom_mode = 0;
21 } 21 }
22 22
23 // Reset custom modeline, if any
24 output->drm_mode.type = 0;
25
23 char *end; 26 char *end;
24 output->width = strtol(*argv, &end, 10); 27 output->width = strtol(*argv, &end, 10);
25 if (*end) { 28 if (*end) {
@@ -58,3 +61,58 @@ struct cmd_results *output_cmd_mode(int argc, char **argv) {
58 return NULL; 61 return NULL;
59} 62}
60 63
64static bool parse_modeline(char **argv, drmModeModeInfo *mode) {
65 mode->type = DRM_MODE_TYPE_USERDEF;
66 mode->clock = strtof(argv[0], NULL) * 1000;
67 mode->hdisplay = strtol(argv[1], NULL, 10);
68 mode->hsync_start = strtol(argv[2], NULL, 10);
69 mode->hsync_end = strtol(argv[3], NULL, 10);
70 mode->htotal = strtol(argv[4], NULL, 10);
71 mode->vdisplay = strtol(argv[5], NULL, 10);
72 mode->vsync_start = strtol(argv[6], NULL, 10);
73 mode->vsync_end = strtol(argv[7], NULL, 10);
74 mode->vtotal = strtol(argv[8], NULL, 10);
75
76 mode->vrefresh = mode->clock * 1000.0 * 1000.0
77 / mode->htotal / mode->vtotal;
78 if (strcasecmp(argv[9], "+hsync") == 0) {
79 mode->flags |= DRM_MODE_FLAG_PHSYNC;
80 } else if (strcasecmp(argv[9], "-hsync") == 0) {
81 mode->flags |= DRM_MODE_FLAG_NHSYNC;
82 } else {
83 return false;
84 }
85
86 if (strcasecmp(argv[10], "+vsync") == 0) {
87 mode->flags |= DRM_MODE_FLAG_PVSYNC;
88 } else if (strcasecmp(argv[10], "-vsync") == 0) {
89 mode->flags |= DRM_MODE_FLAG_NVSYNC;
90 } else {
91 return false;
92 }
93
94 snprintf(mode->name, sizeof(mode->name), "%dx%d@%d",
95 mode->hdisplay, mode->vdisplay, mode->vrefresh / 1000);
96
97 return true;
98}
99
100struct cmd_results *output_cmd_modeline(int argc, char **argv) {
101 if (!config->handler_context.output_config) {
102 return cmd_results_new(CMD_FAILURE, "Missing output config");
103 }
104 if (!argc) {
105 return cmd_results_new(CMD_INVALID, "Missing modeline argument.");
106 }
107
108 struct output_config *output = config->handler_context.output_config;
109
110 if (argc != 11 || !parse_modeline(argv, &output->drm_mode)) {
111 return cmd_results_new(CMD_INVALID, "Invalid modeline");
112 }
113
114 config->handler_context.leftovers.argc = argc - 12;
115 config->handler_context.leftovers.argv = argv + 12;
116 return NULL;
117}
118
diff --git a/sway/commands/output/power.c b/sway/commands/output/power.c
new file mode 100644
index 00000000..e6ae2852
--- /dev/null
+++ b/sway/commands/output/power.c
@@ -0,0 +1,43 @@
1#include <strings.h>
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/output.h"
5#include "util.h"
6
7struct cmd_results *output_cmd_power(int argc, char **argv) {
8 if (!config->handler_context.output_config) {
9 return cmd_results_new(CMD_FAILURE, "Missing output config");
10 }
11 if (argc == 0) {
12 return cmd_results_new(CMD_INVALID, "Missing power argument");
13 }
14
15 bool current = true;
16 if (strcasecmp(argv[0], "toggle") == 0) {
17 const char *oc_name = config->handler_context.output_config->name;
18 if (strcmp(oc_name, "*") == 0) {
19 return cmd_results_new(CMD_INVALID,
20 "Cannot apply toggle to all outputs");
21 }
22
23 struct sway_output *sway_output = all_output_by_name_or_id(oc_name);
24 if (!sway_output || !sway_output->wlr_output) {
25 return cmd_results_new(CMD_FAILURE,
26 "Cannot apply toggle to unknown output %s", oc_name);
27 }
28
29 if (sway_output->enabled && !sway_output->wlr_output->enabled) {
30 current = false;
31 }
32 }
33
34 if (parse_boolean(argv[0], current)) {
35 config->handler_context.output_config->power = 1;
36 } else {
37 config->handler_context.output_config->power = 0;
38 }
39
40 config->handler_context.leftovers.argc = argc - 1;
41 config->handler_context.leftovers.argv = argv + 1;
42 return NULL;
43}
diff --git a/sway/commands/output/render_bit_depth.c b/sway/commands/output/render_bit_depth.c
new file mode 100644
index 00000000..c419321e
--- /dev/null
+++ b/sway/commands/output/render_bit_depth.c
@@ -0,0 +1,29 @@
1#include <drm_fourcc.h>
2#include <strings.h>
3#include "sway/commands.h"
4#include "sway/config.h"
5
6struct cmd_results *output_cmd_render_bit_depth(int argc, char **argv) {
7 if (!config->handler_context.output_config) {
8 return cmd_results_new(CMD_FAILURE, "Missing output config");
9 }
10 if (!argc) {
11 return cmd_results_new(CMD_INVALID, "Missing bit depth argument.");
12 }
13
14 if (strcmp(*argv, "8") == 0) {
15 config->handler_context.output_config->render_bit_depth =
16 RENDER_BIT_DEPTH_8;
17 } else if (strcmp(*argv, "10") == 0) {
18 config->handler_context.output_config->render_bit_depth =
19 RENDER_BIT_DEPTH_10;
20 } else {
21 return cmd_results_new(CMD_INVALID,
22 "Invalid bit depth. Must be a value in (8|10).");
23 }
24
25 config->handler_context.leftovers.argc = argc - 1;
26 config->handler_context.leftovers.argv = argv + 1;
27 return NULL;
28}
29
diff --git a/sway/commands/output/transform.c b/sway/commands/output/transform.c
index f4fcc8c9..8db71bb3 100644
--- a/sway/commands/output/transform.c
+++ b/sway/commands/output/transform.c
@@ -1,4 +1,5 @@
1#include <string.h> 1#include <string.h>
2#include <wlr/util/transform.h>
2#include "sway/commands.h" 3#include "sway/commands.h"
3#include "sway/config.h" 4#include "sway/config.h"
4#include "log.h" 5#include "log.h"
diff --git a/sway/commands/output/unplug.c b/sway/commands/output/unplug.c
new file mode 100644
index 00000000..dfef626f
--- /dev/null
+++ b/sway/commands/output/unplug.c
@@ -0,0 +1,54 @@
1#include <strings.h>
2#include <wlr/config.h>
3#include <wlr/backend/headless.h>
4#include <wlr/backend/wayland.h>
5#if WLR_HAS_X11_BACKEND
6#include <wlr/backend/x11.h>
7#endif
8#include "sway/commands.h"
9#include "sway/config.h"
10#include "sway/output.h"
11
12static bool is_backend_allowed(struct wlr_backend *backend) {
13 if (wlr_backend_is_headless(backend)) {
14 return true;
15 }
16 if (wlr_backend_is_wl(backend)) {
17 return true;
18 }
19#if WLR_HAS_X11_BACKEND
20 if (wlr_backend_is_x11(backend)) {
21 return true;
22 }
23#endif
24 return false;
25}
26
27/**
28 * This command is intended for developer use only.
29 */
30struct cmd_results *output_cmd_unplug(int argc, char **argv) {
31 if (!config->handler_context.output_config) {
32 return cmd_results_new(CMD_FAILURE, "Missing output config");
33 }
34
35 const char *oc_name = config->handler_context.output_config->name;
36 if (strcmp(oc_name, "*") == 0) {
37 return cmd_results_new(CMD_INVALID, "Won't unplug all outputs");
38 }
39
40 struct sway_output *sway_output = all_output_by_name_or_id(oc_name);
41 if (!sway_output) {
42 return cmd_results_new(CMD_INVALID,
43 "Cannot unplug unknown output %s", oc_name);
44 }
45
46 if (!is_backend_allowed(sway_output->wlr_output->backend)) {
47 return cmd_results_new(CMD_INVALID,
48 "Can only unplug outputs with headless, wayland or x11 backend");
49 }
50
51 wlr_output_destroy(sway_output->wlr_output);
52
53 return cmd_results_new(CMD_SUCCESS, NULL);
54}
diff --git a/sway/commands/primary_selection.c b/sway/commands/primary_selection.c
new file mode 100644
index 00000000..585b079d
--- /dev/null
+++ b/sway/commands/primary_selection.c
@@ -0,0 +1,23 @@
1#include <string.h>
2#include <strings.h>
3#include "sway/config.h"
4#include "sway/commands.h"
5#include "util.h"
6
7struct cmd_results *cmd_primary_selection(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "primary_selection", EXPECTED_EQUAL_TO, 1))) {
10 return error;
11 }
12
13 bool primary_selection = parse_boolean(argv[0], true);
14
15 if (config->reloading && config->primary_selection != primary_selection) {
16 return cmd_results_new(CMD_FAILURE,
17 "primary_selection can only be enabled/disabled at launch");
18 }
19
20 config->primary_selection = parse_boolean(argv[0], true);
21
22 return cmd_results_new(CMD_SUCCESS, NULL);
23}
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index 3c994d54..82967ca7 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -9,9 +9,8 @@
9#include "list.h" 9#include "list.h"
10#include "log.h" 10#include "log.h"
11 11
12static void rebuild_textures_iterator(struct sway_container *con, void *data) { 12static void title_bar_update_iterator(struct sway_container *con, void *data) {
13 container_update_marks_textures(con); 13 container_update_title_bar(con);
14 container_update_title_textures(con);
15} 14}
16 15
17static void do_reload(void *data) { 16static void do_reload(void *data) {
@@ -48,8 +47,7 @@ static void do_reload(void *data) {
48 } 47 }
49 list_free_items_and_destroy(bar_ids); 48 list_free_items_and_destroy(bar_ids);
50 49
51 config_update_font_height(true); 50 root_for_each_container(title_bar_update_iterator, NULL);
52 root_for_each_container(rebuild_textures_iterator, NULL);
53 51
54 arrange_root(); 52 arrange_root();
55} 53}
diff --git a/sway/commands/rename.c b/sway/commands/rename.c
index 3b855fdf..0d36cc21 100644
--- a/sway/commands/rename.c
+++ b/sway/commands/rename.c
@@ -7,6 +7,7 @@
7#include "sway/config.h" 7#include "sway/config.h"
8#include "sway/ipc-server.h" 8#include "sway/ipc-server.h"
9#include "sway/output.h" 9#include "sway/output.h"
10#include "sway/desktop/launcher.h"
10#include "sway/tree/container.h" 11#include "sway/tree/container.h"
11#include "sway/tree/workspace.h" 12#include "sway/tree/workspace.h"
12#include "sway/tree/root.h" 13#include "sway/tree/root.h"
@@ -25,7 +26,7 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
25 "Can't run this command while there's no outputs connected."); 26 "Can't run this command while there's no outputs connected.");
26 } 27 }
27 if (strcasecmp(argv[0], "workspace") != 0) { 28 if (strcasecmp(argv[0], "workspace") != 0) {
28 return cmd_results_new(CMD_INVALID, expected_syntax); 29 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
29 } 30 }
30 31
31 int argn = 1; 32 int argn = 1;
@@ -64,7 +65,7 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
64 ++argn; // move past "to" 65 ++argn; // move past "to"
65 66
66 if (argn >= argc) { 67 if (argn >= argc) {
67 return cmd_results_new(CMD_INVALID, expected_syntax); 68 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
68 } 69 }
69 70
70 char *new_name = join_args(argv + argn, argc - argn); 71 char *new_name = join_args(argv + argn, argc - argn);
@@ -91,8 +92,6 @@ struct cmd_results *cmd_rename(int argc, char **argv) {
91 92
92 sway_log(SWAY_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name); 93 sway_log(SWAY_DEBUG, "renaming workspace '%s' to '%s'", workspace->name, new_name);
93 94
94 root_rename_pid_workspaces(workspace->name, new_name);
95
96 free(workspace->name); 95 free(workspace->name);
97 workspace->name = new_name; 96 workspace->name = new_name;
98 97
diff --git a/sway/commands/resize.c b/sway/commands/resize.c
index ca36e858..32b746ea 100644
--- a/sway/commands/resize.c
+++ b/sway/commands/resize.c
@@ -57,7 +57,7 @@ struct sway_container *container_find_resize_parent(struct sway_container *con,
57 (allow_last || index < siblings->length - 1)) { 57 (allow_last || index < siblings->length - 1)) {
58 return con; 58 return con;
59 } 59 }
60 con = con->parent; 60 con = con->pending.parent;
61 } 61 }
62 62
63 return NULL; 63 return NULL;
@@ -75,6 +75,10 @@ void container_resize_tiled(struct sway_container *con,
75 return; 75 return;
76 } 76 }
77 77
78 if (container_is_scratchpad_hidden_or_child(con)) {
79 return;
80 }
81
78 // For HORIZONTAL or VERTICAL, we are growing in two directions so select 82 // For HORIZONTAL or VERTICAL, we are growing in two directions so select
79 // both adjacent siblings. For RIGHT or DOWN, just select the next sibling. 83 // both adjacent siblings. For RIGHT or DOWN, just select the next sibling.
80 // For LEFT or UP, convert it to a RIGHT or DOWN resize and reassign con to 84 // For LEFT or UP, convert it to a RIGHT or DOWN resize and reassign con to
@@ -115,13 +119,13 @@ void container_resize_tiled(struct sway_container *con,
115 int sibling_amount = prev ? ceil((double)amount / 2.0) : amount; 119 int sibling_amount = prev ? ceil((double)amount / 2.0) : amount;
116 120
117 if (is_horizontal(axis)) { 121 if (is_horizontal(axis)) {
118 if (con->width + amount < MIN_SANE_W) { 122 if (con->pending.width + amount < MIN_SANE_W) {
119 return; 123 return;
120 } 124 }
121 if (next->width - sibling_amount < MIN_SANE_W) { 125 if (next->pending.width - sibling_amount < MIN_SANE_W) {
122 return; 126 return;
123 } 127 }
124 if (prev && prev->width - sibling_amount < MIN_SANE_W) { 128 if (prev && prev->pending.width - sibling_amount < MIN_SANE_W) {
125 return; 129 return;
126 } 130 }
127 if (con->child_total_width <= 0) { 131 if (con->child_total_width <= 0) {
@@ -133,7 +137,7 @@ void container_resize_tiled(struct sway_container *con,
133 list_t *siblings = container_get_siblings(con); 137 list_t *siblings = container_get_siblings(con);
134 for (int i = 0; i < siblings->length; ++i) { 138 for (int i = 0; i < siblings->length; ++i) {
135 struct sway_container *con = siblings->items[i]; 139 struct sway_container *con = siblings->items[i];
136 con->width_fraction = con->width / con->child_total_width; 140 con->width_fraction = con->pending.width / con->child_total_width;
137 } 141 }
138 142
139 double amount_fraction = (double)amount / con->child_total_width; 143 double amount_fraction = (double)amount / con->child_total_width;
@@ -146,13 +150,13 @@ void container_resize_tiled(struct sway_container *con,
146 prev->width_fraction -= sibling_amount_fraction; 150 prev->width_fraction -= sibling_amount_fraction;
147 } 151 }
148 } else { 152 } else {
149 if (con->height + amount < MIN_SANE_H) { 153 if (con->pending.height + amount < MIN_SANE_H) {
150 return; 154 return;
151 } 155 }
152 if (next->height - sibling_amount < MIN_SANE_H) { 156 if (next->pending.height - sibling_amount < MIN_SANE_H) {
153 return; 157 return;
154 } 158 }
155 if (prev && prev->height - sibling_amount < MIN_SANE_H) { 159 if (prev && prev->pending.height - sibling_amount < MIN_SANE_H) {
156 return; 160 return;
157 } 161 }
158 if (con->child_total_height <= 0) { 162 if (con->child_total_height <= 0) {
@@ -164,7 +168,7 @@ void container_resize_tiled(struct sway_container *con,
164 list_t *siblings = container_get_siblings(con); 168 list_t *siblings = container_get_siblings(con);
165 for (int i = 0; i < siblings->length; ++i) { 169 for (int i = 0; i < siblings->length; ++i) {
166 struct sway_container *con = siblings->items[i]; 170 struct sway_container *con = siblings->items[i];
167 con->height_fraction = con->height / con->child_total_height; 171 con->height_fraction = con->pending.height / con->child_total_height;
168 } 172 }
169 173
170 double amount_fraction = (double)amount / con->child_total_height; 174 double amount_fraction = (double)amount / con->child_total_height;
@@ -178,10 +182,10 @@ void container_resize_tiled(struct sway_container *con,
178 } 182 }
179 } 183 }
180 184
181 if (con->parent) { 185 if (con->pending.parent) {
182 arrange_container(con->parent); 186 arrange_container(con->pending.parent);
183 } else { 187 } else {
184 arrange_workspace(con->workspace); 188 arrange_workspace(con->pending.workspace);
185 } 189 }
186} 190}
187 191
@@ -203,15 +207,15 @@ static struct cmd_results *resize_adjust_floating(uint32_t axis,
203 int min_width, max_width, min_height, max_height; 207 int min_width, max_width, min_height, max_height;
204 floating_calculate_constraints(&min_width, &max_width, 208 floating_calculate_constraints(&min_width, &max_width,
205 &min_height, &max_height); 209 &min_height, &max_height);
206 if (con->width + grow_width < min_width) { 210 if (con->pending.width + grow_width < min_width) {
207 grow_width = min_width - con->width; 211 grow_width = min_width - con->pending.width;
208 } else if (con->width + grow_width > max_width) { 212 } else if (con->pending.width + grow_width > max_width) {
209 grow_width = max_width - con->width; 213 grow_width = max_width - con->pending.width;
210 } 214 }
211 if (con->height + grow_height < min_height) { 215 if (con->pending.height + grow_height < min_height) {
212 grow_height = min_height - con->height; 216 grow_height = min_height - con->pending.height;
213 } else if (con->height + grow_height > max_height) { 217 } else if (con->pending.height + grow_height > max_height) {
214 grow_height = max_height - con->height; 218 grow_height = max_height - con->pending.height;
215 } 219 }
216 int grow_x = 0, grow_y = 0; 220 int grow_x = 0, grow_y = 0;
217 221
@@ -227,15 +231,15 @@ static struct cmd_results *resize_adjust_floating(uint32_t axis,
227 if (grow_width == 0 && grow_height == 0) { 231 if (grow_width == 0 && grow_height == 0) {
228 return cmd_results_new(CMD_INVALID, "Cannot resize any further"); 232 return cmd_results_new(CMD_INVALID, "Cannot resize any further");
229 } 233 }
230 con->x += grow_x; 234 con->pending.x += grow_x;
231 con->y += grow_y; 235 con->pending.y += grow_y;
232 con->width += grow_width; 236 con->pending.width += grow_width;
233 con->height += grow_height; 237 con->pending.height += grow_height;
234 238
235 con->content_x += grow_x; 239 con->pending.content_x += grow_x;
236 con->content_y += grow_y; 240 con->pending.content_y += grow_y;
237 con->content_width += grow_width; 241 con->pending.content_width += grow_width;
238 con->content_height += grow_height; 242 con->pending.content_height += grow_height;
239 243
240 arrange_container(con); 244 arrange_container(con);
241 245
@@ -249,16 +253,35 @@ static struct cmd_results *resize_adjust_tiled(uint32_t axis,
249 struct movement_amount *amount) { 253 struct movement_amount *amount) {
250 struct sway_container *current = config->handler_context.container; 254 struct sway_container *current = config->handler_context.container;
251 255
256 if (container_is_scratchpad_hidden_or_child(current)) {
257 return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container");
258 }
259
252 if (amount->unit == MOVEMENT_UNIT_DEFAULT) { 260 if (amount->unit == MOVEMENT_UNIT_DEFAULT) {
253 amount->unit = MOVEMENT_UNIT_PPT; 261 amount->unit = MOVEMENT_UNIT_PPT;
254 } 262 }
255 if (amount->unit == MOVEMENT_UNIT_PPT) { 263 if (amount->unit == MOVEMENT_UNIT_PPT) {
264 struct sway_container *parent = current->pending.parent;
256 float pct = amount->amount / 100.0f; 265 float pct = amount->amount / 100.0f;
257 266
258 if (is_horizontal(axis)) { 267 if (is_horizontal(axis)) {
259 amount->amount = (float)current->width * pct; 268 while (parent && parent->pending.layout != L_HORIZ) {
269 parent = parent->pending.parent;
270 }
271 if (parent) {
272 amount->amount = (float)parent->pending.width * pct;
273 } else {
274 amount->amount = (float)current->pending.workspace->width * pct;
275 }
260 } else { 276 } else {
261 amount->amount = (float)current->height * pct; 277 while (parent && parent->pending.layout != L_VERT) {
278 parent = parent->pending.parent;
279 }
280 if (parent) {
281 amount->amount = (float)parent->pending.height * pct;
282 } else {
283 amount->amount = (float)current->pending.workspace->height * pct;
284 }
262 } 285 }
263 } 286 }
264 287
@@ -277,24 +300,29 @@ static struct cmd_results *resize_adjust_tiled(uint32_t axis,
277 */ 300 */
278static struct cmd_results *resize_set_tiled(struct sway_container *con, 301static struct cmd_results *resize_set_tiled(struct sway_container *con,
279 struct movement_amount *width, struct movement_amount *height) { 302 struct movement_amount *width, struct movement_amount *height) {
303
304 if (container_is_scratchpad_hidden_or_child(con)) {
305 return cmd_results_new(CMD_FAILURE, "Cannot resize a hidden scratchpad container");
306 }
307
280 if (width->amount) { 308 if (width->amount) {
281 if (width->unit == MOVEMENT_UNIT_PPT || 309 if (width->unit == MOVEMENT_UNIT_PPT ||
282 width->unit == MOVEMENT_UNIT_DEFAULT) { 310 width->unit == MOVEMENT_UNIT_DEFAULT) {
283 // Convert to px 311 // Convert to px
284 struct sway_container *parent = con->parent; 312 struct sway_container *parent = con->pending.parent;
285 while (parent && parent->layout != L_HORIZ) { 313 while (parent && parent->pending.layout != L_HORIZ) {
286 parent = parent->parent; 314 parent = parent->pending.parent;
287 } 315 }
288 if (parent) { 316 if (parent) {
289 width->amount = parent->width * width->amount / 100; 317 width->amount = parent->pending.width * width->amount / 100;
290 } else { 318 } else {
291 width->amount = con->workspace->width * width->amount / 100; 319 width->amount = con->pending.workspace->width * width->amount / 100;
292 } 320 }
293 width->unit = MOVEMENT_UNIT_PX; 321 width->unit = MOVEMENT_UNIT_PX;
294 } 322 }
295 if (width->unit == MOVEMENT_UNIT_PX) { 323 if (width->unit == MOVEMENT_UNIT_PX) {
296 container_resize_tiled(con, AXIS_HORIZONTAL, 324 container_resize_tiled(con, AXIS_HORIZONTAL,
297 width->amount - con->width); 325 width->amount - con->pending.width);
298 } 326 }
299 } 327 }
300 328
@@ -302,20 +330,20 @@ static struct cmd_results *resize_set_tiled(struct sway_container *con,
302 if (height->unit == MOVEMENT_UNIT_PPT || 330 if (height->unit == MOVEMENT_UNIT_PPT ||
303 height->unit == MOVEMENT_UNIT_DEFAULT) { 331 height->unit == MOVEMENT_UNIT_DEFAULT) {
304 // Convert to px 332 // Convert to px
305 struct sway_container *parent = con->parent; 333 struct sway_container *parent = con->pending.parent;
306 while (parent && parent->layout != L_VERT) { 334 while (parent && parent->pending.layout != L_VERT) {
307 parent = parent->parent; 335 parent = parent->pending.parent;
308 } 336 }
309 if (parent) { 337 if (parent) {
310 height->amount = parent->height * height->amount / 100; 338 height->amount = parent->pending.height * height->amount / 100;
311 } else { 339 } else {
312 height->amount = con->workspace->height * height->amount / 100; 340 height->amount = con->pending.workspace->height * height->amount / 100;
313 } 341 }
314 height->unit = MOVEMENT_UNIT_PX; 342 height->unit = MOVEMENT_UNIT_PX;
315 } 343 }
316 if (height->unit == MOVEMENT_UNIT_PX) { 344 if (height->unit == MOVEMENT_UNIT_PX) {
317 container_resize_tiled(con, AXIS_VERTICAL, 345 container_resize_tiled(con, AXIS_VERTICAL,
318 height->amount - con->height); 346 height->amount - con->pending.height);
319 } 347 }
320 } 348 }
321 349
@@ -339,15 +367,15 @@ static struct cmd_results *resize_set_floating(struct sway_container *con,
339 "Cannot resize a hidden scratchpad container by ppt"); 367 "Cannot resize a hidden scratchpad container by ppt");
340 } 368 }
341 // Convert to px 369 // Convert to px
342 width->amount = con->workspace->width * width->amount / 100; 370 width->amount = con->pending.workspace->width * width->amount / 100;
343 width->unit = MOVEMENT_UNIT_PX; 371 width->unit = MOVEMENT_UNIT_PX;
344 // Falls through 372 // Falls through
345 case MOVEMENT_UNIT_PX: 373 case MOVEMENT_UNIT_PX:
346 case MOVEMENT_UNIT_DEFAULT: 374 case MOVEMENT_UNIT_DEFAULT:
347 width->amount = fmax(min_width, fmin(width->amount, max_width)); 375 width->amount = fmax(min_width, fmin(width->amount, max_width));
348 grow_width = width->amount - con->width; 376 grow_width = width->amount - con->pending.width;
349 con->x -= grow_width / 2; 377 con->pending.x -= grow_width / 2;
350 con->width = width->amount; 378 con->pending.width = width->amount;
351 break; 379 break;
352 case MOVEMENT_UNIT_INVALID: 380 case MOVEMENT_UNIT_INVALID:
353 sway_assert(false, "invalid width unit"); 381 sway_assert(false, "invalid width unit");
@@ -363,15 +391,15 @@ static struct cmd_results *resize_set_floating(struct sway_container *con,
363 "Cannot resize a hidden scratchpad container by ppt"); 391 "Cannot resize a hidden scratchpad container by ppt");
364 } 392 }
365 // Convert to px 393 // Convert to px
366 height->amount = con->workspace->height * height->amount / 100; 394 height->amount = con->pending.workspace->height * height->amount / 100;
367 height->unit = MOVEMENT_UNIT_PX; 395 height->unit = MOVEMENT_UNIT_PX;
368 // Falls through 396 // Falls through
369 case MOVEMENT_UNIT_PX: 397 case MOVEMENT_UNIT_PX:
370 case MOVEMENT_UNIT_DEFAULT: 398 case MOVEMENT_UNIT_DEFAULT:
371 height->amount = fmax(min_height, fmin(height->amount, max_height)); 399 height->amount = fmax(min_height, fmin(height->amount, max_height));
372 grow_height = height->amount - con->height; 400 grow_height = height->amount - con->pending.height;
373 con->y -= grow_height / 2; 401 con->pending.y -= grow_height / 2;
374 con->height = height->amount; 402 con->pending.height = height->amount;
375 break; 403 break;
376 case MOVEMENT_UNIT_INVALID: 404 case MOVEMENT_UNIT_INVALID:
377 sway_assert(false, "invalid height unit"); 405 sway_assert(false, "invalid height unit");
@@ -379,10 +407,10 @@ static struct cmd_results *resize_set_floating(struct sway_container *con,
379 } 407 }
380 } 408 }
381 409
382 con->content_x -= grow_width / 2; 410 con->pending.content_x -= grow_width / 2;
383 con->content_y -= grow_height / 2; 411 con->pending.content_y -= grow_height / 2;
384 con->content_width += grow_width; 412 con->pending.content_width += grow_width;
385 con->content_height += grow_height; 413 con->pending.content_height += grow_height;
386 414
387 arrange_container(con); 415 arrange_container(con);
388 416
@@ -415,7 +443,7 @@ static struct cmd_results *cmd_resize_set(int argc, char **argv) {
415 argc -= num_consumed_args; 443 argc -= num_consumed_args;
416 argv += num_consumed_args; 444 argv += num_consumed_args;
417 if (width.unit == MOVEMENT_UNIT_INVALID) { 445 if (width.unit == MOVEMENT_UNIT_INVALID) {
418 return cmd_results_new(CMD_INVALID, usage); 446 return cmd_results_new(CMD_INVALID, "%s", usage);
419 } 447 }
420 } 448 }
421 449
@@ -427,20 +455,20 @@ static struct cmd_results *cmd_resize_set(int argc, char **argv) {
427 } 455 }
428 int num_consumed_args = parse_movement_amount(argc, argv, &height); 456 int num_consumed_args = parse_movement_amount(argc, argv, &height);
429 if (argc > num_consumed_args) { 457 if (argc > num_consumed_args) {
430 return cmd_results_new(CMD_INVALID, usage); 458 return cmd_results_new(CMD_INVALID, "%s", usage);
431 } 459 }
432 if (width.unit == MOVEMENT_UNIT_INVALID) { 460 if (width.unit == MOVEMENT_UNIT_INVALID) {
433 return cmd_results_new(CMD_INVALID, usage); 461 return cmd_results_new(CMD_INVALID, "%s", usage);
434 } 462 }
435 } 463 }
436 464
437 // If 0, don't resize that dimension 465 // If 0, don't resize that dimension
438 struct sway_container *con = config->handler_context.container; 466 struct sway_container *con = config->handler_context.container;
439 if (width.amount <= 0) { 467 if (width.amount <= 0) {
440 width.amount = con->width; 468 width.amount = con->pending.width;
441 } 469 }
442 if (height.amount <= 0) { 470 if (height.amount <= 0) {
443 height.amount = con->height; 471 height.amount = con->pending.height;
444 } 472 }
445 473
446 if (container_is_floating(con)) { 474 if (container_is_floating(con)) {
@@ -462,7 +490,7 @@ static struct cmd_results *cmd_resize_adjust(int argc, char **argv,
462 "[<amount> px|ppt [or <amount> px|ppt]]'"; 490 "[<amount> px|ppt [or <amount> px|ppt]]'";
463 uint32_t axis = parse_resize_axis(*argv); 491 uint32_t axis = parse_resize_axis(*argv);
464 if (axis == WLR_EDGE_NONE) { 492 if (axis == WLR_EDGE_NONE) {
465 return cmd_results_new(CMD_INVALID, usage); 493 return cmd_results_new(CMD_INVALID, "%s", usage);
466 } 494 }
467 --argc; ++argv; 495 --argc; ++argv;
468 496
@@ -473,7 +501,7 @@ static struct cmd_results *cmd_resize_adjust(int argc, char **argv,
473 argc -= num_consumed_args; 501 argc -= num_consumed_args;
474 argv += num_consumed_args; 502 argv += num_consumed_args;
475 if (first_amount.unit == MOVEMENT_UNIT_INVALID) { 503 if (first_amount.unit == MOVEMENT_UNIT_INVALID) {
476 return cmd_results_new(CMD_INVALID, usage); 504 return cmd_results_new(CMD_INVALID, "%s", usage);
477 } 505 }
478 } else { 506 } else {
479 first_amount.amount = 10; 507 first_amount.amount = 10;
@@ -483,7 +511,7 @@ static struct cmd_results *cmd_resize_adjust(int argc, char **argv,
483 // "or" 511 // "or"
484 if (argc) { 512 if (argc) {
485 if (strcmp(*argv, "or") != 0) { 513 if (strcmp(*argv, "or") != 0) {
486 return cmd_results_new(CMD_INVALID, usage); 514 return cmd_results_new(CMD_INVALID, "%s", usage);
487 } 515 }
488 --argc; ++argv; 516 --argc; ++argv;
489 } 517 }
@@ -493,10 +521,10 @@ static struct cmd_results *cmd_resize_adjust(int argc, char **argv,
493 if (argc) { 521 if (argc) {
494 int num_consumed_args = parse_movement_amount(argc, argv, &second_amount); 522 int num_consumed_args = parse_movement_amount(argc, argv, &second_amount);
495 if (argc > num_consumed_args) { 523 if (argc > num_consumed_args) {
496 return cmd_results_new(CMD_INVALID, usage); 524 return cmd_results_new(CMD_INVALID, "%s", usage);
497 } 525 }
498 if (second_amount.unit == MOVEMENT_UNIT_INVALID) { 526 if (second_amount.unit == MOVEMENT_UNIT_INVALID) {
499 return cmd_results_new(CMD_INVALID, usage); 527 return cmd_results_new(CMD_INVALID, "%s", usage);
500 } 528 }
501 } else { 529 } else {
502 second_amount.amount = 0; 530 second_amount.amount = 0;
@@ -566,5 +594,5 @@ struct cmd_results *cmd_resize(int argc, char **argv) {
566 const char usage[] = "Expected 'resize <shrink|grow> " 594 const char usage[] = "Expected 'resize <shrink|grow> "
567 "<width|height|up|down|left|right> [<amount>] [px|ppt]'"; 595 "<width|height|up|down|left|right> [<amount>] [px|ppt]'";
568 596
569 return cmd_results_new(CMD_INVALID, usage); 597 return cmd_results_new(CMD_INVALID, "%s", usage);
570} 598}
diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c
index 34871bc6..c995f2f0 100644
--- a/sway/commands/scratchpad.c
+++ b/sway/commands/scratchpad.c
@@ -21,8 +21,8 @@ static void scratchpad_toggle_auto(void) {
21 // If the focus is in a floating split container, 21 // If the focus is in a floating split container,
22 // operate on the split container instead of the child. 22 // operate on the split container instead of the child.
23 if (focus && container_is_floating_or_child(focus)) { 23 if (focus && container_is_floating_or_child(focus)) {
24 while (focus->parent) { 24 while (focus->pending.parent) {
25 focus = focus->parent; 25 focus = focus->pending.parent;
26 } 26 }
27 } 27 }
28 28
@@ -52,7 +52,7 @@ static void scratchpad_toggle_auto(void) {
52 // In this case we move it to the current workspace. 52 // In this case we move it to the current workspace.
53 for (int i = 0; i < root->scratchpad->length; ++i) { 53 for (int i = 0; i < root->scratchpad->length; ++i) {
54 struct sway_container *con = root->scratchpad->items[i]; 54 struct sway_container *con = root->scratchpad->items[i];
55 if (con->parent) { 55 if (con->pending.parent) {
56 sway_log(SWAY_DEBUG, 56 sway_log(SWAY_DEBUG,
57 "Moving a visible scratchpad window (%s) to this workspace", 57 "Moving a visible scratchpad window (%s) to this workspace",
58 con->title); 58 con->title);
@@ -80,7 +80,7 @@ static void scratchpad_toggle_container(struct sway_container *con) {
80 struct sway_seat *seat = input_manager_current_seat(); 80 struct sway_seat *seat = input_manager_current_seat();
81 struct sway_workspace *ws = seat_get_focused_workspace(seat); 81 struct sway_workspace *ws = seat_get_focused_workspace(seat);
82 // Check if it matches a currently visible scratchpad window and hide it. 82 // Check if it matches a currently visible scratchpad window and hide it.
83 if (con->workspace && ws == con->workspace) { 83 if (con->pending.workspace && ws == con->pending.workspace) {
84 root_scratchpad_hide(con); 84 root_scratchpad_hide(con);
85 return; 85 return;
86 } 86 }
@@ -105,21 +105,22 @@ struct cmd_results *cmd_scratchpad(int argc, char **argv) {
105 return cmd_results_new(CMD_INVALID, "Scratchpad is empty"); 105 return cmd_results_new(CMD_INVALID, "Scratchpad is empty");
106 } 106 }
107 107
108 if (config->handler_context.using_criteria) { 108 if (config->handler_context.node_overridden) {
109 struct sway_container *con = config->handler_context.container; 109 struct sway_container *con = config->handler_context.container;
110 110
111 // If the container is in a floating split container, 111 // If the container is in a floating split container,
112 // operate on the split container instead of the child. 112 // operate on the split container instead of the child.
113 if (container_is_floating_or_child(con)) { 113 if (con && container_is_floating_or_child(con)) {
114 while (con->parent) { 114 while (con->pending.parent) {
115 con = con->parent; 115 con = con->pending.parent;
116 } 116 }
117 } 117 }
118 118
119 // If using criteria, this command is executed for every container which 119 // If using criteria, this command is executed for every container which
120 // matches the criteria. If this container isn't in the scratchpad, 120 // matches the criteria. If this container isn't in the scratchpad,
121 // we'll just silently return a success. 121 // we'll just silently return a success. The same is true if the
122 if (!con->scratchpad) { 122 // overridden node is not a container.
123 if (!con || !con->scratchpad) {
123 return cmd_results_new(CMD_SUCCESS, NULL); 124 return cmd_results_new(CMD_SUCCESS, NULL);
124 } 125 }
125 scratchpad_toggle_container(con); 126 scratchpad_toggle_container(con);
diff --git a/sway/commands/seat.c b/sway/commands/seat.c
index 84c6ba53..2d197b69 100644
--- a/sway/commands/seat.c
+++ b/sway/commands/seat.c
@@ -8,13 +8,13 @@
8 8
9// must be in order for the bsearch 9// must be in order for the bsearch
10// these handlers perform actions on the seat 10// these handlers perform actions on the seat
11static struct cmd_handler seat_action_handlers[] = { 11static const struct cmd_handler seat_action_handlers[] = {
12 { "cursor", seat_cmd_cursor }, 12 { "cursor", seat_cmd_cursor },
13}; 13};
14 14
15// must be in order for the bsearch 15// must be in order for the bsearch
16// these handlers alter the seat config 16// these handlers alter the seat config
17static struct cmd_handler seat_handlers[] = { 17static const struct cmd_handler seat_handlers[] = {
18 { "attach", seat_cmd_attach }, 18 { "attach", seat_cmd_attach },
19 { "fallback", seat_cmd_fallback }, 19 { "fallback", seat_cmd_fallback },
20 { "hide_cursor", seat_cmd_hide_cursor }, 20 { "hide_cursor", seat_cmd_hide_cursor },
diff --git a/sway/commands/seat/attach.c b/sway/commands/seat/attach.c
index 7615eef9..00bfdab6 100644
--- a/sway/commands/seat/attach.c
+++ b/sway/commands/seat/attach.c
@@ -12,7 +12,7 @@ struct cmd_results *seat_cmd_attach(int argc, char **argv) {
12 if (!config->handler_context.seat_config) { 12 if (!config->handler_context.seat_config) {
13 return cmd_results_new(CMD_FAILURE, "No seat defined"); 13 return cmd_results_new(CMD_FAILURE, "No seat defined");
14 } 14 }
15 if (config->reading) { 15 if (!config->active) {
16 return cmd_results_new(CMD_DEFER, NULL); 16 return cmd_results_new(CMD_DEFER, NULL);
17 } 17 }
18 18
diff --git a/sway/commands/seat/cursor.c b/sway/commands/seat/cursor.c
index 749235eb..5a8a3bc8 100644
--- a/sway/commands/seat/cursor.c
+++ b/sway/commands/seat/cursor.c
@@ -18,7 +18,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
18 int argc, char **argv) { 18 int argc, char **argv) {
19 if (strcasecmp(argv[0], "move") == 0) { 19 if (strcasecmp(argv[0], "move") == 0) {
20 if (argc < 3) { 20 if (argc < 3) {
21 return cmd_results_new(CMD_INVALID, expected_syntax); 21 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
22 } 22 }
23 int delta_x = strtol(argv[1], NULL, 10); 23 int delta_x = strtol(argv[1], NULL, 10);
24 int delta_y = strtol(argv[2], NULL, 10); 24 int delta_y = strtol(argv[2], NULL, 10);
@@ -27,7 +27,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
27 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 27 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
28 } else if (strcasecmp(argv[0], "set") == 0) { 28 } else if (strcasecmp(argv[0], "set") == 0) {
29 if (argc < 3) { 29 if (argc < 3) {
30 return cmd_results_new(CMD_INVALID, expected_syntax); 30 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
31 } 31 }
32 // map absolute coords (0..1,0..1) to root container coords 32 // map absolute coords (0..1,0..1) to root container coords
33 float x = strtof(argv[1], NULL) / root->width; 33 float x = strtof(argv[1], NULL) / root->width;
@@ -37,7 +37,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
37 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 37 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
38 } else { 38 } else {
39 if (argc < 2) { 39 if (argc < 2) {
40 return cmd_results_new(CMD_INVALID, expected_syntax); 40 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
41 } 41 }
42 struct cmd_results *error = NULL; 42 struct cmd_results *error = NULL;
43 if ((error = press_or_release(cursor, argv[0], argv[1]))) { 43 if ((error = press_or_release(cursor, argv[0], argv[1]))) {
@@ -92,14 +92,14 @@ static struct cmd_results *press_or_release(struct sway_cursor *cursor,
92 } else if (strcasecmp(action, "release") == 0) { 92 } else if (strcasecmp(action, "release") == 0) {
93 state = WLR_BUTTON_RELEASED; 93 state = WLR_BUTTON_RELEASED;
94 } else { 94 } else {
95 return cmd_results_new(CMD_INVALID, expected_syntax); 95 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
96 } 96 }
97 97
98 char *message = NULL; 98 char *message = NULL;
99 button = get_mouse_button(button_str, &message); 99 button = get_mouse_button(button_str, &message);
100 if (message) { 100 if (message) {
101 struct cmd_results *error = 101 struct cmd_results *error =
102 cmd_results_new(CMD_INVALID, message); 102 cmd_results_new(CMD_INVALID, "%s", message);
103 free(message); 103 free(message);
104 return error; 104 return error;
105 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN 105 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN
@@ -111,8 +111,8 @@ static struct cmd_results *press_or_release(struct sway_cursor *cursor,
111 : WLR_AXIS_ORIENTATION_HORIZONTAL; 111 : WLR_AXIS_ORIENTATION_HORIZONTAL;
112 double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT) 112 double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT)
113 ? -1 : 1; 113 ? -1 : 1;
114 struct wlr_event_pointer_axis event = { 114 struct wlr_pointer_axis_event event = {
115 .device = NULL, 115 .pointer = NULL,
116 .time_msec = 0, 116 .time_msec = 0,
117 .source = WLR_AXIS_SOURCE_WHEEL, 117 .source = WLR_AXIS_SOURCE_WHEEL,
118 .orientation = orientation, 118 .orientation = orientation,
diff --git a/sway/commands/seat/idle.c b/sway/commands/seat/idle.c
index 82428f2c..62b94db2 100644
--- a/sway/commands/seat/idle.c
+++ b/sway/commands/seat/idle.c
@@ -3,6 +3,7 @@
3#include <string.h> 3#include <string.h>
4#include <strings.h> 4#include <strings.h>
5#include <stdint.h> 5#include <stdint.h>
6#include "log.h"
6#include "sway/commands.h" 7#include "sway/commands.h"
7#include "sway/config.h" 8#include "sway/config.h"
8#include "sway/input/seat.h" 9#include "sway/input/seat.h"
@@ -69,5 +70,10 @@ struct cmd_results *seat_cmd_idle_wake(int argc, char **argv) {
69 return cmd_results_new(CMD_FAILURE, "Invalid idle source"); 70 return cmd_results_new(CMD_FAILURE, "Invalid idle source");
70 } 71 }
71 config->handler_context.seat_config->idle_wake_sources = sources; 72 config->handler_context.seat_config->idle_wake_sources = sources;
73 sway_log(SWAY_INFO, "Warning: seat idle_wake is deprecated");
74 if (config->reading) {
75 config_add_swaynag_warning("seat idle_wake is deprecated. "
76 "Only seat idle_inhibit is supported.");
77 }
72 return cmd_results_new(CMD_SUCCESS, NULL); 78 return cmd_results_new(CMD_SUCCESS, NULL);
73} 79}
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
index 0d373b80..fecb5ade 100644
--- a/sway/commands/show_marks.c
+++ b/sway/commands/show_marks.c
@@ -10,8 +10,8 @@
10#include "stringop.h" 10#include "stringop.h"
11#include "util.h" 11#include "util.h"
12 12
13static void rebuild_marks_iterator(struct sway_container *con, void *data) { 13static void title_bar_update_iterator(struct sway_container *con, void *data) {
14 container_update_marks_textures(con); 14 container_update_marks(con);
15} 15}
16 16
17struct cmd_results *cmd_show_marks(int argc, char **argv) { 17struct cmd_results *cmd_show_marks(int argc, char **argv) {
@@ -23,12 +23,7 @@ struct cmd_results *cmd_show_marks(int argc, char **argv) {
23 config->show_marks = parse_boolean(argv[0], config->show_marks); 23 config->show_marks = parse_boolean(argv[0], config->show_marks);
24 24
25 if (config->show_marks) { 25 if (config->show_marks) {
26 root_for_each_container(rebuild_marks_iterator, NULL); 26 root_for_each_container(title_bar_update_iterator, NULL);
27 }
28
29 for (int i = 0; i < root->outputs->length; ++i) {
30 struct sway_output *output = root->outputs->items[i];
31 output_damage_whole(output);
32 } 27 }
33 28
34 return cmd_results_new(CMD_SUCCESS, NULL); 29 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/smart_gaps.c b/sway/commands/smart_gaps.c
index b27f9ccd..a6d165dc 100644
--- a/sway/commands/smart_gaps.c
+++ b/sway/commands/smart_gaps.c
@@ -15,7 +15,12 @@ struct cmd_results *cmd_smart_gaps(int argc, char **argv) {
15 return error; 15 return error;
16 } 16 }
17 17
18 config->smart_gaps = parse_boolean(argv[0], config->smart_gaps); 18 if (strcmp(argv[0], "inverse_outer") == 0) {
19 config->smart_gaps = SMART_GAPS_INVERSE_OUTER;
20 } else {
21 config->smart_gaps = parse_boolean(argv[0], config->smart_gaps)
22 ? SMART_GAPS_ON : SMART_GAPS_OFF;
23 }
19 24
20 arrange_root(); 25 arrange_root();
21 26
diff --git a/sway/commands/split.c b/sway/commands/split.c
index 782bab02..500a497d 100644
--- a/sway/commands/split.c
+++ b/sway/commands/split.c
@@ -14,7 +14,7 @@ static struct cmd_results *do_split(int layout) {
14 struct sway_workspace *ws = config->handler_context.workspace; 14 struct sway_workspace *ws = config->handler_context.workspace;
15 if (con) { 15 if (con) {
16 if (container_is_scratchpad_hidden_or_child(con) && 16 if (container_is_scratchpad_hidden_or_child(con) &&
17 con->fullscreen_mode != FULLSCREEN_GLOBAL) { 17 con->pending.fullscreen_mode != FULLSCREEN_GLOBAL) {
18 return cmd_results_new(CMD_FAILURE, 18 return cmd_results_new(CMD_FAILURE,
19 "Cannot split a hidden scratchpad container"); 19 "Cannot split a hidden scratchpad container");
20 } 20 }
@@ -32,6 +32,24 @@ static struct cmd_results *do_split(int layout) {
32 return cmd_results_new(CMD_SUCCESS, NULL); 32 return cmd_results_new(CMD_SUCCESS, NULL);
33} 33}
34 34
35static struct cmd_results *do_unsplit(void) {
36 struct sway_container *con = config->handler_context.container;
37 struct sway_workspace *ws = config->handler_context.workspace;
38
39 if (con && con->pending.parent && con->pending.parent->pending.children->length == 1) {
40 container_flatten(con->pending.parent);
41 } else {
42 return cmd_results_new(CMD_FAILURE, "Can only flatten a child container with no siblings");
43 }
44
45 if (root->fullscreen_global) {
46 arrange_root();
47 } else {
48 arrange_workspace(ws);
49 }
50 return cmd_results_new(CMD_SUCCESS, NULL);
51}
52
35struct cmd_results *cmd_split(int argc, char **argv) { 53struct cmd_results *cmd_split(int argc, char **argv) {
36 struct cmd_results *error = NULL; 54 struct cmd_results *error = NULL;
37 if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) { 55 if ((error = checkarg(argc, "split", EXPECTED_EQUAL_TO, 1))) {
@@ -55,6 +73,9 @@ struct cmd_results *cmd_split(int argc, char **argv) {
55 } else { 73 } else {
56 return do_split(L_VERT); 74 return do_split(L_VERT);
57 } 75 }
76 } else if (strcasecmp(argv[0], "n") == 0 ||
77 strcasecmp(argv[0], "none") == 0) {
78 return do_unsplit();
58 } else { 79 } else {
59 return cmd_results_new(CMD_FAILURE, 80 return cmd_results_new(CMD_FAILURE,
60 "Invalid split command (expected either horizontal or vertical)."); 81 "Invalid split command (expected either horizontal or vertical).");
diff --git a/sway/commands/sticky.c b/sway/commands/sticky.c
index 3c93a276..9b09a0f9 100644
--- a/sway/commands/sticky.c
+++ b/sway/commands/sticky.c
@@ -29,14 +29,14 @@ struct cmd_results *cmd_sticky(int argc, char **argv) {
29 !container_is_scratchpad_hidden(container)) { 29 !container_is_scratchpad_hidden(container)) {
30 // move container to active workspace 30 // move container to active workspace
31 struct sway_workspace *active_workspace = 31 struct sway_workspace *active_workspace =
32 output_get_active_workspace(container->workspace->output); 32 output_get_active_workspace(container->pending.workspace->output);
33 if (!sway_assert(active_workspace, 33 if (!sway_assert(active_workspace,
34 "Expected output to have a workspace")) { 34 "Expected output to have a workspace")) {
35 return cmd_results_new(CMD_FAILURE, 35 return cmd_results_new(CMD_FAILURE,
36 "Expected output to have a workspace"); 36 "Expected output to have a workspace");
37 } 37 }
38 if (container->workspace != active_workspace) { 38 if (container->pending.workspace != active_workspace) {
39 struct sway_workspace *old_workspace = container->workspace; 39 struct sway_workspace *old_workspace = container->pending.workspace;
40 container_detach(container); 40 container_detach(container);
41 workspace_add_floating(active_workspace, container); 41 workspace_add_floating(active_workspace, container);
42 container_handle_fullscreen_reparent(container); 42 container_handle_fullscreen_reparent(container);
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index a7f9691b..d44eb006 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -5,6 +5,7 @@
5#include "sway/commands.h" 5#include "sway/commands.h"
6#include "sway/output.h" 6#include "sway/output.h"
7#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
8#include "sway/tree/container.h"
8#include "sway/tree/root.h" 9#include "sway/tree/root.h"
9#include "sway/tree/view.h" 10#include "sway/tree/view.h"
10#include "sway/tree/workspace.h" 11#include "sway/tree/workspace.h"
@@ -13,180 +14,6 @@
13static const char expected_syntax[] = 14static const char expected_syntax[] =
14 "Expected 'swap container with id|con_id|mark <arg>'"; 15 "Expected 'swap container with id|con_id|mark <arg>'";
15 16
16static void swap_places(struct sway_container *con1,
17 struct sway_container *con2) {
18 struct sway_container *temp = malloc(sizeof(struct sway_container));
19 temp->x = con1->x;
20 temp->y = con1->y;
21 temp->width = con1->width;
22 temp->height = con1->height;
23 temp->width_fraction = con1->width_fraction;
24 temp->height_fraction = con1->height_fraction;
25 temp->parent = con1->parent;
26 temp->workspace = con1->workspace;
27 bool temp_floating = container_is_floating(con1);
28
29 con1->x = con2->x;
30 con1->y = con2->y;
31 con1->width = con2->width;
32 con1->height = con2->height;
33 con1->width_fraction = con2->width_fraction;
34 con1->height_fraction = con2->height_fraction;
35
36 con2->x = temp->x;
37 con2->y = temp->y;
38 con2->width = temp->width;
39 con2->height = temp->height;
40 con2->width_fraction = temp->width_fraction;
41 con2->height_fraction = temp->height_fraction;
42
43 int temp_index = container_sibling_index(con1);
44 if (con2->parent) {
45 container_insert_child(con2->parent, con1,
46 container_sibling_index(con2));
47 } else if (container_is_floating(con2)) {
48 workspace_add_floating(con2->workspace, con1);
49 } else {
50 workspace_insert_tiling(con2->workspace, con1,
51 container_sibling_index(con2));
52 }
53 if (temp->parent) {
54 container_insert_child(temp->parent, con2, temp_index);
55 } else if (temp_floating) {
56 workspace_add_floating(temp->workspace, con2);
57 } else {
58 workspace_insert_tiling(temp->workspace, con2, temp_index);
59 }
60
61 free(temp);
62}
63
64static void swap_focus(struct sway_container *con1,
65 struct sway_container *con2, struct sway_seat *seat,
66 struct sway_container *focus) {
67 if (focus == con1 || focus == con2) {
68 struct sway_workspace *ws1 = con1->workspace;
69 struct sway_workspace *ws2 = con2->workspace;
70 enum sway_container_layout layout1 = container_parent_layout(con1);
71 enum sway_container_layout layout2 = container_parent_layout(con2);
72 if (focus == con1 && (layout2 == L_TABBED || layout2 == L_STACKED)) {
73 if (workspace_is_visible(ws2)) {
74 seat_set_focus(seat, &con2->node);
75 }
76 seat_set_focus_container(seat, ws1 != ws2 ? con2 : con1);
77 } else if (focus == con2 && (layout1 == L_TABBED
78 || layout1 == L_STACKED)) {
79 if (workspace_is_visible(ws1)) {
80 seat_set_focus(seat, &con1->node);
81 }
82 seat_set_focus_container(seat, ws1 != ws2 ? con1 : con2);
83 } else if (ws1 != ws2) {
84 seat_set_focus_container(seat, focus == con1 ? con2 : con1);
85 } else {
86 seat_set_focus_container(seat, focus);
87 }
88 } else {
89 seat_set_focus_container(seat, focus);
90 }
91
92 if (root->fullscreen_global) {
93 seat_set_focus(seat,
94 seat_get_focus_inactive(seat, &root->fullscreen_global->node));
95 }
96}
97
98void container_swap(struct sway_container *con1, struct sway_container *con2) {
99 if (!sway_assert(con1 && con2, "Cannot swap with nothing")) {
100 return;
101 }
102 if (!sway_assert(!container_has_ancestor(con1, con2)
103 && !container_has_ancestor(con2, con1),
104 "Cannot swap ancestor and descendant")) {
105 return;
106 }
107
108 sway_log(SWAY_DEBUG, "Swapping containers %zu and %zu",
109 con1->node.id, con2->node.id);
110
111 bool scratch1 = con1->scratchpad;
112 bool hidden1 = container_is_scratchpad_hidden(con1);
113 bool scratch2 = con2->scratchpad;
114 bool hidden2 = container_is_scratchpad_hidden(con2);
115 if (scratch1) {
116 if (hidden1) {
117 root_scratchpad_show(con1);
118 }
119 root_scratchpad_remove_container(con1);
120 }
121 if (scratch2) {
122 if (hidden2) {
123 root_scratchpad_show(con2);
124 }
125 root_scratchpad_remove_container(con2);
126 }
127
128 enum sway_fullscreen_mode fs1 = con1->fullscreen_mode;
129 enum sway_fullscreen_mode fs2 = con2->fullscreen_mode;
130 if (fs1) {
131 container_fullscreen_disable(con1);
132 }
133 if (fs2) {
134 container_fullscreen_disable(con2);
135 }
136
137 struct sway_seat *seat = config->handler_context.seat;
138 struct sway_container *focus = seat_get_focused_container(seat);
139 struct sway_workspace *vis1 =
140 output_get_active_workspace(con1->workspace->output);
141 struct sway_workspace *vis2 =
142 output_get_active_workspace(con2->workspace->output);
143 if (!sway_assert(vis1 && vis2, "con1 or con2 are on an output without a"
144 "workspace. This should not happen")) {
145 return;
146 }
147
148 char *stored_prev_name = NULL;
149 if (seat->prev_workspace_name) {
150 stored_prev_name = strdup(seat->prev_workspace_name);
151 }
152
153 swap_places(con1, con2);
154
155 if (!workspace_is_visible(vis1)) {
156 seat_set_focus(seat, seat_get_focus_inactive(seat, &vis1->node));
157 }
158 if (!workspace_is_visible(vis2)) {
159 seat_set_focus(seat, seat_get_focus_inactive(seat, &vis2->node));
160 }
161
162 swap_focus(con1, con2, seat, focus);
163
164 if (stored_prev_name) {
165 free(seat->prev_workspace_name);
166 seat->prev_workspace_name = stored_prev_name;
167 }
168
169 if (scratch1) {
170 root_scratchpad_add_container(con2, NULL);
171 if (!hidden1) {
172 root_scratchpad_show(con2);
173 }
174 }
175 if (scratch2) {
176 root_scratchpad_add_container(con1, NULL);
177 if (!hidden2) {
178 root_scratchpad_show(con1);
179 }
180 }
181
182 if (fs1) {
183 container_set_fullscreen(con2, fs1);
184 }
185 if (fs2) {
186 container_set_fullscreen(con1, fs2);
187 }
188}
189
190static bool test_con_id(struct sway_container *container, void *data) { 17static bool test_con_id(struct sway_container *container, void *data) {
191 size_t *con_id = data; 18 size_t *con_id = data;
192 return container->node.id == *con_id; 19 return container->node.id == *con_id;
@@ -219,7 +46,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
219 } 46 }
220 47
221 if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) { 48 if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) {
222 return cmd_results_new(CMD_INVALID, expected_syntax); 49 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
223 } 50 }
224 51
225 struct sway_container *current = config->handler_context.container; 52 struct sway_container *current = config->handler_context.container;
@@ -238,7 +65,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
238 other = root_find_container(test_mark, value); 65 other = root_find_container(test_mark, value);
239 } else { 66 } else {
240 free(value); 67 free(value);
241 return cmd_results_new(CMD_INVALID, expected_syntax); 68 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
242 } 69 }
243 70
244 if (!other) { 71 if (!other) {
@@ -247,6 +74,9 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
247 } else if (!current) { 74 } else if (!current) {
248 error = cmd_results_new(CMD_FAILURE, 75 error = cmd_results_new(CMD_FAILURE,
249 "Can only swap with containers and views"); 76 "Can only swap with containers and views");
77 } else if (current == other) {
78 error = cmd_results_new(CMD_FAILURE,
79 "Cannot swap a container with itself");
250 } else if (container_has_ancestor(current, other) 80 } else if (container_has_ancestor(current, other)
251 || container_has_ancestor(other, current)) { 81 || container_has_ancestor(other, current)) {
252 error = cmd_results_new(CMD_FAILURE, 82 error = cmd_results_new(CMD_FAILURE,
diff --git a/sway/commands/title_align.c b/sway/commands/title_align.c
index c30355de..be298a29 100644
--- a/sway/commands/title_align.c
+++ b/sway/commands/title_align.c
@@ -4,6 +4,10 @@
4#include "sway/tree/container.h" 4#include "sway/tree/container.h"
5#include "sway/tree/root.h" 5#include "sway/tree/root.h"
6 6
7static void arrange_title_bar_iterator(struct sway_container *con, void *data) {
8 container_arrange_title_bar(con);
9}
10
7struct cmd_results *cmd_title_align(int argc, char **argv) { 11struct cmd_results *cmd_title_align(int argc, char **argv) {
8 struct cmd_results *error = NULL; 12 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "title_align", EXPECTED_AT_LEAST, 1))) { 13 if ((error = checkarg(argc, "title_align", EXPECTED_AT_LEAST, 1))) {
@@ -21,10 +25,7 @@ struct cmd_results *cmd_title_align(int argc, char **argv) {
21 "Expected 'title_align left|center|right'"); 25 "Expected 'title_align left|center|right'");
22 } 26 }
23 27
24 for (int i = 0; i < root->outputs->length; ++i) { 28 root_for_each_container(arrange_title_bar_iterator, NULL);
25 struct sway_output *output = root->outputs->items[i];
26 output_damage_whole(output);
27 }
28 29
29 return cmd_results_new(CMD_SUCCESS, NULL); 30 return cmd_results_new(CMD_SUCCESS, NULL);
30} 31}
diff --git a/sway/commands/title_format.c b/sway/commands/title_format.c
index 9d312470..a2446b7e 100644
--- a/sway/commands/title_format.c
+++ b/sway/commands/title_format.c
@@ -23,6 +23,5 @@ struct cmd_results *cmd_title_format(int argc, char **argv) {
23 } 23 }
24 view->title_format = format; 24 view->title_format = format;
25 view_update_title(view, true); 25 view_update_title(view, true);
26 config_update_font_height(true);
27 return cmd_results_new(CMD_SUCCESS, NULL); 26 return cmd_results_new(CMD_SUCCESS, NULL);
28} 27}
diff --git a/sway/commands/titlebar_border_thickness.c b/sway/commands/titlebar_border_thickness.c
index 7c27c163..fa3db3c5 100644
--- a/sway/commands/titlebar_border_thickness.c
+++ b/sway/commands/titlebar_border_thickness.c
@@ -27,7 +27,6 @@ struct cmd_results *cmd_titlebar_border_thickness(int argc, char **argv) {
27 "Expected output to have a workspace"); 27 "Expected output to have a workspace");
28 } 28 }
29 arrange_workspace(ws); 29 arrange_workspace(ws);
30 output_damage_whole(output);
31 } 30 }
32 31
33 return cmd_results_new(CMD_SUCCESS, NULL); 32 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/titlebar_padding.c b/sway/commands/titlebar_padding.c
index 29ce59ff..6999f7a2 100644
--- a/sway/commands/titlebar_padding.c
+++ b/sway/commands/titlebar_padding.c
@@ -33,7 +33,6 @@ struct cmd_results *cmd_titlebar_padding(int argc, char **argv) {
33 for (int i = 0; i < root->outputs->length; ++i) { 33 for (int i = 0; i < root->outputs->length; ++i) {
34 struct sway_output *output = root->outputs->items[i]; 34 struct sway_output *output = root->outputs->items[i];
35 arrange_workspace(output_get_active_workspace(output)); 35 arrange_workspace(output_get_active_workspace(output));
36 output_damage_whole(output);
37 } 36 }
38 37
39 return cmd_results_new(CMD_SUCCESS, NULL); 38 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/unmark.c b/sway/commands/unmark.c
index cedfcfb2..c3a6ac4b 100644
--- a/sway/commands/unmark.c
+++ b/sway/commands/unmark.c
@@ -8,9 +8,13 @@
8#include "log.h" 8#include "log.h"
9#include "stringop.h" 9#include "stringop.h"
10 10
11static void remove_all_marks_iterator(struct sway_container *con, void *data) { 11static void remove_mark(struct sway_container *con) {
12 container_clear_marks(con); 12 container_clear_marks(con);
13 container_update_marks_textures(con); 13 container_update_marks(con);
14}
15
16static void remove_all_marks_iterator(struct sway_container *con, void *data) {
17 remove_mark(con);
14} 18}
15 19
16// unmark Remove all marks from all views 20// unmark Remove all marks from all views
@@ -21,7 +25,7 @@ static void remove_all_marks_iterator(struct sway_container *con, void *data) {
21struct cmd_results *cmd_unmark(int argc, char **argv) { 25struct cmd_results *cmd_unmark(int argc, char **argv) {
22 // Determine the container 26 // Determine the container
23 struct sway_container *con = NULL; 27 struct sway_container *con = NULL;
24 if (config->handler_context.using_criteria) { 28 if (config->handler_context.node_overridden) {
25 con = config->handler_context.container; 29 con = config->handler_context.container;
26 } 30 }
27 31
@@ -38,8 +42,7 @@ struct cmd_results *cmd_unmark(int argc, char **argv) {
38 } 42 }
39 } else if (con && !mark) { 43 } else if (con && !mark) {
40 // Clear all marks from the given container 44 // Clear all marks from the given container
41 container_clear_marks(con); 45 remove_mark(con);
42 container_update_marks_textures(con);
43 } else if (!con && mark) { 46 } else if (!con && mark) {
44 // Remove mark from whichever container has it 47 // Remove mark from whichever container has it
45 container_find_and_unmark(mark); 48 container_find_and_unmark(mark);
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c
index 2858a284..8536929e 100644
--- a/sway/commands/workspace.c
+++ b/sway/commands/workspace.c
@@ -61,7 +61,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
61 const char expected[] = "Expected 'workspace <name> gaps " 61 const char expected[] = "Expected 'workspace <name> gaps "
62 "inner|outer|horizontal|vertical|top|right|bottom|left <px>'"; 62 "inner|outer|horizontal|vertical|top|right|bottom|left <px>'";
63 if (gaps_location == 0) { 63 if (gaps_location == 0) {
64 return cmd_results_new(CMD_INVALID, expected); 64 return cmd_results_new(CMD_INVALID, "%s", expected);
65 } 65 }
66 struct cmd_results *error = NULL; 66 struct cmd_results *error = NULL;
67 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, 67 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO,
@@ -79,7 +79,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
79 char *end; 79 char *end;
80 int amount = strtol(argv[gaps_location + 2], &end, 10); 80 int amount = strtol(argv[gaps_location + 2], &end, 10);
81 if (strlen(end)) { 81 if (strlen(end)) {
82 return cmd_results_new(CMD_FAILURE, expected); 82 return cmd_results_new(CMD_FAILURE, "%s", expected);
83 } 83 }
84 84
85 bool valid = false; 85 bool valid = false;
@@ -110,7 +110,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
110 } 110 }
111 } 111 }
112 if (!valid) { 112 if (!valid) {
113 return cmd_results_new(CMD_INVALID, expected); 113 return cmd_results_new(CMD_INVALID, "%s", expected);
114 } 114 }
115 115
116 // Prevent invalid gaps configurations. 116 // Prevent invalid gaps configurations.
@@ -158,6 +158,10 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
158 return cmd_results_new(CMD_FAILURE, 158 return cmd_results_new(CMD_FAILURE,
159 "Unable to allocate workspace output"); 159 "Unable to allocate workspace output");
160 } 160 }
161 if (output_location + 1 < argc) {
162 list_free_items_and_destroy(wsc->outputs);
163 wsc->outputs = create_list();
164 }
161 for (int i = output_location + 1; i < argc; ++i) { 165 for (int i = output_location + 1; i < argc; ++i) {
162 list_add(wsc->outputs, strdup(argv[i])); 166 list_add(wsc->outputs, strdup(argv[i]));
163 } 167 }
@@ -174,25 +178,20 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
174 } 178 }
175 179
176 if (root->fullscreen_global) { 180 if (root->fullscreen_global) {
177 return cmd_results_new(CMD_FAILURE, "workspace", 181 return cmd_results_new(CMD_FAILURE,
178 "Can't switch workspaces while fullscreen global"); 182 "Can't switch workspaces while fullscreen global");
179 } 183 }
180 184
181 bool no_auto_back_and_forth = false; 185 bool auto_back_and_forth = true;
182 while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) { 186 while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) {
183 no_auto_back_and_forth = true; 187 auto_back_and_forth = false;
184 if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) { 188 if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) {
185 return error; 189 return error;
186 } 190 }
187 ++argv; 191 ++argv;
188 } 192 }
189 193
190 bool create = argc > 1 && strcasecmp(argv[1], "--create") == 0;
191 struct sway_seat *seat = config->handler_context.seat; 194 struct sway_seat *seat = config->handler_context.seat;
192 struct sway_workspace *current = seat_get_focused_workspace(seat);
193 if (!current) {
194 return cmd_results_new(CMD_FAILURE, "No workspace to switch from");
195 }
196 195
197 struct sway_workspace *ws = NULL; 196 struct sway_workspace *ws = NULL;
198 if (strcasecmp(argv[0], "number") == 0) { 197 if (strcasecmp(argv[0], "number") == 0) {
@@ -209,14 +208,15 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
209 ws = workspace_create(NULL, name); 208 ws = workspace_create(NULL, name);
210 free(name); 209 free(name);
211 } 210 }
211 if (ws && auto_back_and_forth) {
212 ws = workspace_auto_back_and_forth(ws);
213 }
212 } else if (strcasecmp(argv[0], "next") == 0 || 214 } else if (strcasecmp(argv[0], "next") == 0 ||
213 strcasecmp(argv[0], "prev") == 0 || 215 strcasecmp(argv[0], "prev") == 0 ||
216 strcasecmp(argv[0], "next_on_output") == 0 ||
217 strcasecmp(argv[0], "prev_on_output") == 0 ||
214 strcasecmp(argv[0], "current") == 0) { 218 strcasecmp(argv[0], "current") == 0) {
215 ws = workspace_by_name(argv[0]); 219 ws = workspace_by_name(argv[0]);
216 } else if (strcasecmp(argv[0], "next_on_output") == 0) {
217 ws = workspace_output_next(current, create);
218 } else if (strcasecmp(argv[0], "prev_on_output") == 0) {
219 ws = workspace_output_prev(current, create);
220 } else if (strcasecmp(argv[0], "back_and_forth") == 0) { 220 } else if (strcasecmp(argv[0], "back_and_forth") == 0) {
221 if (!seat->prev_workspace_name) { 221 if (!seat->prev_workspace_name) {
222 return cmd_results_new(CMD_INVALID, 222 return cmd_results_new(CMD_INVALID,
@@ -231,11 +231,14 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
231 ws = workspace_create(NULL, name); 231 ws = workspace_create(NULL, name);
232 } 232 }
233 free(name); 233 free(name);
234 if (ws && auto_back_and_forth) {
235 ws = workspace_auto_back_and_forth(ws);
236 }
234 } 237 }
235 if (!ws) { 238 if (!ws) {
236 return cmd_results_new(CMD_FAILURE, "No workspace to switch to"); 239 return cmd_results_new(CMD_FAILURE, "No workspace to switch to");
237 } 240 }
238 workspace_switch(ws, no_auto_back_and_forth); 241 workspace_switch(ws);
239 seat_consider_warp_to_focus(seat); 242 seat_consider_warp_to_focus(seat);
240 } 243 }
241 return cmd_results_new(CMD_SUCCESS, NULL); 244 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/config.c b/sway/config.c
index 6e665434..4b51dc73 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -26,7 +26,7 @@
26#include "sway/tree/arrange.h" 26#include "sway/tree/arrange.h"
27#include "sway/tree/root.h" 27#include "sway/tree/root.h"
28#include "sway/tree/workspace.h" 28#include "sway/tree/workspace.h"
29#include "cairo.h" 29#include "cairo_util.h"
30#include "pango.h" 30#include "pango.h"
31#include "stringop.h" 31#include "stringop.h"
32#include "list.h" 32#include "list.h"
@@ -37,7 +37,7 @@ struct sway_config *config = NULL;
37 37
38static struct xkb_state *keysym_translation_state_create( 38static struct xkb_state *keysym_translation_state_create(
39 struct xkb_rule_names rules) { 39 struct xkb_rule_names rules) {
40 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 40 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_SECURE_GETENV);
41 struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names( 41 struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(
42 context, 42 context,
43 &rules, 43 &rules,
@@ -82,6 +82,12 @@ static void free_mode(struct sway_mode *mode) {
82 } 82 }
83 list_free(mode->switch_bindings); 83 list_free(mode->switch_bindings);
84 } 84 }
85 if (mode->gesture_bindings) {
86 for (int i = 0; i < mode->gesture_bindings->length; i++) {
87 free_gesture_binding(mode->gesture_bindings->items[i]);
88 }
89 list_free(mode->gesture_bindings);
90 }
85 free(mode); 91 free(mode);
86} 92}
87 93
@@ -222,6 +228,7 @@ static void config_defaults(struct sway_config *config) {
222 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; 228 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
223 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup; 229 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
224 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup; 230 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup;
231 if (!(config->current_mode->gesture_bindings = create_list())) goto cleanup;
225 list_add(config->modes, config->current_mode); 232 list_add(config->modes, config->current_mode);
226 233
227 config->floating_mod = 0; 234 config->floating_mod = 0;
@@ -236,7 +243,7 @@ static void config_defaults(struct sway_config *config) {
236 config->default_layout = L_NONE; 243 config->default_layout = L_NONE;
237 config->default_orientation = L_NONE; 244 config->default_orientation = L_NONE;
238 if (!(config->font = strdup("monospace 10"))) goto cleanup; 245 if (!(config->font = strdup("monospace 10"))) goto cleanup;
239 config->font_height = 17; // height of monospace 10 246 config->font_description = pango_font_description_from_string(config->font);
240 config->urgent_timeout = 500; 247 config->urgent_timeout = 500;
241 config->focus_on_window_activation = FOWA_URGENT; 248 config->focus_on_window_activation = FOWA_URGENT;
242 config->popup_during_fullscreen = POPUP_SMART; 249 config->popup_during_fullscreen = POPUP_SMART;
@@ -266,8 +273,9 @@ static void config_defaults(struct sway_config *config) {
266 config->title_align = ALIGN_LEFT; 273 config->title_align = ALIGN_LEFT;
267 config->tiling_drag = true; 274 config->tiling_drag = true;
268 config->tiling_drag_threshold = 9; 275 config->tiling_drag_threshold = 9;
276 config->primary_selection = true;
269 277
270 config->smart_gaps = false; 278 config->smart_gaps = SMART_GAPS_OFF;
271 config->gaps_inner = 0; 279 config->gaps_inner = 0;
272 config->gaps_outer.top = 0; 280 config->gaps_outer.top = 0;
273 config->gaps_outer.right = 0; 281 config->gaps_outer.right = 0;
@@ -291,6 +299,8 @@ static void config_defaults(struct sway_config *config) {
291 config->hide_edge_borders_smart = ESMART_OFF; 299 config->hide_edge_borders_smart = ESMART_OFF;
292 config->hide_lone_tab = false; 300 config->hide_lone_tab = false;
293 301
302 config->has_focused_tab_title = false;
303
294 // border colors 304 // border colors
295 color_to_rgba(config->border_colors.focused.border, 0x4C7899FF); 305 color_to_rgba(config->border_colors.focused.border, 0x4C7899FF);
296 color_to_rgba(config->border_colors.focused.background, 0x285577FF); 306 color_to_rgba(config->border_colors.focused.background, 0x285577FF);
@@ -338,35 +348,62 @@ static bool file_exists(const char *path) {
338 return path && access(path, R_OK) != -1; 348 return path && access(path, R_OK) != -1;
339} 349}
340 350
351static char *config_path(const char *prefix, const char *config_folder) {
352 if (!prefix || !prefix[0] || !config_folder || !config_folder[0]) {
353 return NULL;
354 }
355
356 const char *filename = "config";
357
358 size_t size = 3 + strlen(prefix) + strlen(config_folder) + strlen(filename);
359 char *path = calloc(size, sizeof(char));
360 snprintf(path, size, "%s/%s/%s", prefix, config_folder, filename);
361 return path;
362}
363
341static char *get_config_path(void) { 364static char *get_config_path(void) {
342 static const char *config_paths[] = { 365 char *path = NULL;
343 "$HOME/.sway/config", 366 const char *home = getenv("HOME");
344 "$XDG_CONFIG_HOME/sway/config", 367 char *config_home_fallback = NULL;
345 "$HOME/.i3/config", 368
346 "$XDG_CONFIG_HOME/i3/config", 369 const char *config_home = getenv("XDG_CONFIG_HOME");
347 SYSCONFDIR "/sway/config", 370 if ((config_home == NULL || config_home[0] == '\0') && home != NULL) {
348 SYSCONFDIR "/i3/config", 371 size_t size_fallback = 1 + strlen(home) + strlen("/.config");
372 config_home_fallback = calloc(size_fallback, sizeof(char));
373 if (config_home_fallback != NULL)
374 snprintf(config_home_fallback, size_fallback, "%s/.config", home);
375 config_home = config_home_fallback;
376 }
377
378 struct config_path {
379 const char *prefix;
380 const char *config_folder;
349 }; 381 };
350 382
351 char *config_home = getenv("XDG_CONFIG_HOME"); 383 struct config_path config_paths[] = {
352 if (!config_home || !*config_home) { 384 { .prefix = home, .config_folder = ".sway"},
353 config_paths[1] = "$HOME/.config/sway/config"; 385 { .prefix = config_home, .config_folder = "sway"},
354 config_paths[3] = "$HOME/.config/i3/config"; 386 { .prefix = home, .config_folder = ".i3"},
355 } 387 { .prefix = config_home, .config_folder = "i3"},
388 { .prefix = SYSCONFDIR, .config_folder = "sway"},
389 { .prefix = SYSCONFDIR, .config_folder = "i3"}
390 };
356 391
357 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { 392 size_t num_config_paths = sizeof(config_paths)/sizeof(config_paths[0]);
358 wordexp_t p; 393 for (size_t i = 0; i < num_config_paths; i++) {
359 if (wordexp(config_paths[i], &p, WRDE_UNDEF) == 0) { 394 path = config_path(config_paths[i].prefix, config_paths[i].config_folder);
360 char *path = strdup(p.we_wordv[0]); 395 if (!path) {
361 wordfree(&p); 396 continue;
362 if (file_exists(path)) {
363 return path;
364 }
365 free(path);
366 } 397 }
398 if (file_exists(path)) {
399 break;
400 }
401 free(path);
402 path = NULL;
367 } 403 }
368 404
369 return NULL; 405 free(config_home_fallback);
406 return path;
370} 407}
371 408
372static bool load_config(const char *path, struct sway_config *config, 409static bool load_config(const char *path, struct sway_config *config,
@@ -514,6 +551,9 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
514 return success; 551 return success;
515 } 552 }
516 553
554 // Only really necessary if not explicitly `font` is set in the config.
555 config_update_font_height();
556
517 if (is_active && !validating) { 557 if (is_active && !validating) {
518 input_manager_verify_fallback_seat(); 558 input_manager_verify_fallback_seat();
519 559
@@ -884,23 +924,18 @@ void config_add_swaynag_warning(char *fmt, ...) {
884 if (config->reading && !config->validating) { 924 if (config->reading && !config->validating) {
885 va_list args; 925 va_list args;
886 va_start(args, fmt); 926 va_start(args, fmt);
887 size_t length = vsnprintf(NULL, 0, fmt, args) + 1; 927 char *str = vformat_str(fmt, args);
888 va_end(args); 928 va_end(args);
889 929 if (str == NULL) {
890 char *temp = malloc(length + 1);
891 if (!temp) {
892 sway_log(SWAY_ERROR, "Failed to allocate buffer for warning.");
893 return; 930 return;
894 } 931 }
895 932
896 va_start(args, fmt);
897 vsnprintf(temp, length, fmt, args);
898 va_end(args);
899
900 swaynag_log(config->swaynag_command, &config->swaynag_config_errors, 933 swaynag_log(config->swaynag_command, &config->swaynag_config_errors,
901 "Warning on line %i (%s) '%s': %s", 934 "Warning on line %i (%s) '%s': %s",
902 config->current_config_line_number, config->current_config_path, 935 config->current_config_line_number, config->current_config_path,
903 config->current_config_line, temp); 936 config->current_config_line, str);
937
938 free(str);
904 } 939 }
905} 940}
906 941
@@ -940,7 +975,7 @@ char *do_var_replacement(char *str) {
940 int offset = find - str; 975 int offset = find - str;
941 strncpy(newptr, str, offset); 976 strncpy(newptr, str, offset);
942 newptr += offset; 977 newptr += offset;
943 strncpy(newptr, var->value, vvlen); 978 memcpy(newptr, var->value, vvlen);
944 newptr += vvlen; 979 newptr += vvlen;
945 strcpy(newptr, find + vnlen); 980 strcpy(newptr, find + vnlen);
946 free(str); 981 free(str);
@@ -964,31 +999,11 @@ int workspace_output_cmp_workspace(const void *a, const void *b) {
964 return lenient_strcmp(wsa->workspace, wsb->workspace); 999 return lenient_strcmp(wsa->workspace, wsb->workspace);
965} 1000}
966 1001
967static void find_font_height_iterator(struct sway_container *con, void *data) {
968 size_t amount_below_baseline = con->title_height - con->title_baseline;
969 size_t extended_height = config->font_baseline + amount_below_baseline;
970 if (extended_height > config->font_height) {
971 config->font_height = extended_height;
972 }
973}
974
975static void find_baseline_iterator(struct sway_container *con, void *data) {
976 bool *recalculate = data;
977 if (*recalculate) {
978 container_calculate_title_height(con);
979 }
980 if (con->title_baseline > config->font_baseline) {
981 config->font_baseline = con->title_baseline;
982 }
983}
984 1002
985void config_update_font_height(bool recalculate) { 1003void config_update_font_height(void) {
986 size_t prev_max_height = config->font_height; 1004 int prev_max_height = config->font_height;
987 config->font_height = 0;
988 config->font_baseline = 0;
989 1005
990 root_for_each_container(find_baseline_iterator, &recalculate); 1006 get_text_metrics(config->font_description, &config->font_height, &config->font_baseline);
991 root_for_each_container(find_font_height_iterator, NULL);
992 1007
993 if (config->font_height != prev_max_height) { 1008 if (config->font_height != prev_max_height) {
994 arrange_root(); 1009 arrange_root();
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 767534a6..a8389244 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -91,7 +91,7 @@ struct bar_config *default_bar_config(void) {
91 } 91 }
92 bar->outputs = NULL; 92 bar->outputs = NULL;
93 bar->position = strdup("bottom"); 93 bar->position = strdup("bottom");
94 bar->pango_markup = false; 94 bar->pango_markup = PANGO_MARKUP_DEFAULT;
95 bar->swaybar_command = NULL; 95 bar->swaybar_command = NULL;
96 bar->font = NULL; 96 bar->font = NULL;
97 bar->height = 0; 97 bar->height = 0;
@@ -217,6 +217,9 @@ static void invoke_swaybar(struct bar_config *bar) {
217 sigset_t set; 217 sigset_t set;
218 sigemptyset(&set); 218 sigemptyset(&set);
219 sigprocmask(SIG_SETMASK, &set, NULL); 219 sigprocmask(SIG_SETMASK, &set, NULL);
220 signal(SIGPIPE, SIG_DFL);
221
222 restore_nofile_limit();
220 223
221 pid = fork(); 224 pid = fork();
222 if (pid < 0) { 225 if (pid < 0) {
@@ -253,7 +256,6 @@ static void invoke_swaybar(struct bar_config *bar) {
253 } 256 }
254 257
255 sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id); 258 sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id);
256 return;
257} 259}
258 260
259void load_swaybar(struct bar_config *bar) { 261void load_swaybar(struct bar_config *bar) {
diff --git a/sway/config/input.c b/sway/config/input.c
index a998e170..44c2be28 100644
--- a/sway/config/input.c
+++ b/sway/config/input.c
@@ -25,14 +25,17 @@ struct input_config *new_input_config(const char* identifier) {
25 input->drag = INT_MIN; 25 input->drag = INT_MIN;
26 input->drag_lock = INT_MIN; 26 input->drag_lock = INT_MIN;
27 input->dwt = INT_MIN; 27 input->dwt = INT_MIN;
28 input->dwtp = INT_MIN;
28 input->send_events = INT_MIN; 29 input->send_events = INT_MIN;
29 input->click_method = INT_MIN; 30 input->click_method = INT_MIN;
30 input->middle_emulation = INT_MIN; 31 input->middle_emulation = INT_MIN;
31 input->natural_scroll = INT_MIN; 32 input->natural_scroll = INT_MIN;
32 input->accel_profile = INT_MIN; 33 input->accel_profile = INT_MIN;
34 input->rotation_angle = FLT_MIN;
33 input->pointer_accel = FLT_MIN; 35 input->pointer_accel = FLT_MIN;
34 input->scroll_factor = FLT_MIN; 36 input->scroll_factor = FLT_MIN;
35 input->scroll_button = INT_MIN; 37 input->scroll_button = INT_MIN;
38 input->scroll_button_lock = INT_MIN;
36 input->scroll_method = INT_MIN; 39 input->scroll_method = INT_MIN;
37 input->left_handed = INT_MIN; 40 input->left_handed = INT_MIN;
38 input->repeat_delay = INT_MIN; 41 input->repeat_delay = INT_MIN;
@@ -61,6 +64,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
61 if (src->dwt != INT_MIN) { 64 if (src->dwt != INT_MIN) {
62 dst->dwt = src->dwt; 65 dst->dwt = src->dwt;
63 } 66 }
67 if (src->dwtp != INT_MIN) {
68 dst->dwtp = src->dwtp;
69 }
64 if (src->left_handed != INT_MIN) { 70 if (src->left_handed != INT_MIN) {
65 dst->left_handed = src->left_handed; 71 dst->left_handed = src->left_handed;
66 } 72 }
@@ -70,6 +76,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
70 if (src->natural_scroll != INT_MIN) { 76 if (src->natural_scroll != INT_MIN) {
71 dst->natural_scroll = src->natural_scroll; 77 dst->natural_scroll = src->natural_scroll;
72 } 78 }
79 if (src->rotation_angle != FLT_MIN) {
80 dst->rotation_angle = src->rotation_angle;
81 }
73 if (src->pointer_accel != FLT_MIN) { 82 if (src->pointer_accel != FLT_MIN) {
74 dst->pointer_accel = src->pointer_accel; 83 dst->pointer_accel = src->pointer_accel;
75 } 84 }
@@ -88,6 +97,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
88 if (src->scroll_button != INT_MIN) { 97 if (src->scroll_button != INT_MIN) {
89 dst->scroll_button = src->scroll_button; 98 dst->scroll_button = src->scroll_button;
90 } 99 }
100 if (src->scroll_button_lock != INT_MIN) {
101 dst->scroll_button_lock = src->scroll_button_lock;
102 }
91 if (src->send_events != INT_MIN) { 103 if (src->send_events != INT_MIN) {
92 dst->send_events = src->send_events; 104 dst->send_events = src->send_events;
93 } 105 }
diff --git a/sway/config/output.c b/sway/config/output.c
index c9ec6745..1a5215fe 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -1,10 +1,12 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <drm_fourcc.h>
3#include <stdbool.h> 4#include <stdbool.h>
4#include <string.h> 5#include <string.h>
5#include <sys/socket.h> 6#include <sys/socket.h>
6#include <sys/wait.h> 7#include <sys/wait.h>
7#include <unistd.h> 8#include <unistd.h>
9#include <wlr/config.h>
8#include <wlr/types/wlr_cursor.h> 10#include <wlr/types/wlr_cursor.h>
9#include <wlr/types/wlr_output_layout.h> 11#include <wlr/types/wlr_output_layout.h>
10#include <wlr/types/wlr_output.h> 12#include <wlr/types/wlr_output.h>
@@ -15,6 +17,10 @@
15#include "log.h" 17#include "log.h"
16#include "util.h" 18#include "util.h"
17 19
20#if WLR_HAS_DRM_BACKEND
21#include <wlr/backend/drm.h>
22#endif
23
18int output_name_cmp(const void *item, const void *data) { 24int output_name_cmp(const void *item, const void *data) {
19 const struct output_config *output = item; 25 const struct output_config *output = item;
20 const char *name = data; 26 const char *name = data;
@@ -25,8 +31,10 @@ int output_name_cmp(const void *item, const void *data) {
25void output_get_identifier(char *identifier, size_t len, 31void output_get_identifier(char *identifier, size_t len,
26 struct sway_output *output) { 32 struct sway_output *output) {
27 struct wlr_output *wlr_output = output->wlr_output; 33 struct wlr_output *wlr_output = output->wlr_output;
28 snprintf(identifier, len, "%s %s %s", wlr_output->make, wlr_output->model, 34 snprintf(identifier, len, "%s %s %s",
29 wlr_output->serial); 35 wlr_output->make ? wlr_output->make : "Unknown",
36 wlr_output->model ? wlr_output->model : "Unknown",
37 wlr_output->serial ? wlr_output->serial : "Unknown");
30} 38}
31 39
32const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) { 40const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filter) {
@@ -58,6 +66,7 @@ struct output_config *new_output_config(const char *name) {
58 oc->width = oc->height = -1; 66 oc->width = oc->height = -1;
59 oc->refresh_rate = -1; 67 oc->refresh_rate = -1;
60 oc->custom_mode = -1; 68 oc->custom_mode = -1;
69 oc->drm_mode.type = -1;
61 oc->x = oc->y = -1; 70 oc->x = oc->y = -1;
62 oc->scale = -1; 71 oc->scale = -1;
63 oc->scale_filter = SCALE_FILTER_DEFAULT; 72 oc->scale_filter = SCALE_FILTER_DEFAULT;
@@ -65,6 +74,8 @@ struct output_config *new_output_config(const char *name) {
65 oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; 74 oc->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
66 oc->max_render_time = -1; 75 oc->max_render_time = -1;
67 oc->adaptive_sync = -1; 76 oc->adaptive_sync = -1;
77 oc->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT;
78 oc->power = -1;
68 return oc; 79 return oc;
69} 80}
70 81
@@ -99,6 +110,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
99 if (src->custom_mode != -1) { 110 if (src->custom_mode != -1) {
100 dst->custom_mode = src->custom_mode; 111 dst->custom_mode = src->custom_mode;
101 } 112 }
113 if (src->drm_mode.type != (uint32_t) -1) {
114 memcpy(&dst->drm_mode, &src->drm_mode, sizeof(src->drm_mode));
115 }
102 if (src->transform != -1) { 116 if (src->transform != -1) {
103 dst->transform = src->transform; 117 dst->transform = src->transform;
104 } 118 }
@@ -108,6 +122,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
108 if (src->adaptive_sync != -1) { 122 if (src->adaptive_sync != -1) {
109 dst->adaptive_sync = src->adaptive_sync; 123 dst->adaptive_sync = src->adaptive_sync;
110 } 124 }
125 if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
126 dst->render_bit_depth = src->render_bit_depth;
127 }
111 if (src->background) { 128 if (src->background) {
112 free(dst->background); 129 free(dst->background);
113 dst->background = strdup(src->background); 130 dst->background = strdup(src->background);
@@ -120,8 +137,8 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
120 free(dst->background_fallback); 137 free(dst->background_fallback);
121 dst->background_fallback = strdup(src->background_fallback); 138 dst->background_fallback = strdup(src->background_fallback);
122 } 139 }
123 if (src->dpms_state != 0) { 140 if (src->power != -1) {
124 dst->dpms_state = src->dpms_state; 141 dst->power = src->power;
125 } 142 }
126} 143}
127 144
@@ -136,25 +153,16 @@ static void merge_wildcard_on_all(struct output_config *wildcard) {
136} 153}
137 154
138static void merge_id_on_name(struct output_config *oc) { 155static void merge_id_on_name(struct output_config *oc) {
139 char *id_on_name = NULL; 156 struct sway_output *output = all_output_by_name_or_id(oc->name);
140 char id[128]; 157 if (output == NULL) {
141 char *name = NULL; 158 return;
142 struct sway_output *output;
143 wl_list_for_each(output, &root->all_outputs, link) {
144 name = output->wlr_output->name;
145 output_get_identifier(id, sizeof(id), output);
146 if (strcmp(name, oc->name) == 0 || strcmp(id, oc->name) == 0) {
147 size_t length = snprintf(NULL, 0, "%s on %s", id, name) + 1;
148 id_on_name = malloc(length);
149 if (!id_on_name) {
150 sway_log(SWAY_ERROR, "Failed to allocate id on name string");
151 return;
152 }
153 snprintf(id_on_name, length, "%s on %s", id, name);
154 break;
155 }
156 } 159 }
157 160
161 const char *name = output->wlr_output->name;
162 char id[128];
163 output_get_identifier(id, sizeof(id), output);
164
165 char *id_on_name = format_str("%s on %s", id, name);
158 if (!id_on_name) { 166 if (!id_on_name) {
159 return; 167 return;
160 } 168 }
@@ -180,11 +188,11 @@ static void merge_id_on_name(struct output_config *oc) {
180 list_add(config->output_configs, ion_oc); 188 list_add(config->output_configs, ion_oc);
181 sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\"" 189 sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\""
182 " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f " 190 " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f "
183 "transform %d) (bg %s %s) (dpms %d) (max render time: %d)", 191 "transform %d) (bg %s %s) (power %d) (max render time: %d)",
184 ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height, 192 ion_oc->name, ion_oc->enabled, ion_oc->width, ion_oc->height,
185 ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale, 193 ion_oc->refresh_rate, ion_oc->x, ion_oc->y, ion_oc->scale,
186 ion_oc->transform, ion_oc->background, 194 ion_oc->transform, ion_oc->background,
187 ion_oc->background_option, ion_oc->dpms_state, 195 ion_oc->background_option, ion_oc->power,
188 ion_oc->max_render_time); 196 ion_oc->max_render_time);
189 } 197 }
190 } 198 }
@@ -225,50 +233,74 @@ struct output_config *store_output_config(struct output_config *oc) {
225 } 233 }
226 234
227 sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " 235 sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz "
228 "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (dpms %d) " 236 "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) "
229 "(max render time: %d)", 237 "(max render time: %d)",
230 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, 238 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate,
231 oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel), 239 oc->x, oc->y, oc->scale, sway_wl_output_subpixel_to_string(oc->subpixel),
232 oc->transform, oc->background, oc->background_option, oc->dpms_state, 240 oc->transform, oc->background, oc->background_option, oc->power,
233 oc->max_render_time); 241 oc->max_render_time);
234 242
235 return oc; 243 return oc;
236} 244}
237 245
238static void set_mode(struct wlr_output *output, int width, int height, 246static void set_mode(struct wlr_output *output, struct wlr_output_state *pending,
239 float refresh_rate, bool custom) { 247 int width, int height, float refresh_rate, bool custom) {
240 // Not all floating point integers can be represented exactly 248 // Not all floating point integers can be represented exactly
241 // as (int)(1000 * mHz / 1000.f) 249 // as (int)(1000 * mHz / 1000.f)
242 // round() the result to avoid any error 250 // round() the result to avoid any error
243 int mhz = (int)round(refresh_rate * 1000); 251 int mhz = (int)roundf(refresh_rate * 1000);
252 // If no target refresh rate is given, match highest available
253 mhz = mhz <= 0 ? INT_MAX : mhz;
244 254
245 if (wl_list_empty(&output->modes) || custom) { 255 if (wl_list_empty(&output->modes) || custom) {
246 sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name); 256 sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name);
247 wlr_output_set_custom_mode(output, width, height, 257 wlr_output_state_set_custom_mode(pending, width, height,
248 refresh_rate > 0 ? mhz : 0); 258 refresh_rate > 0 ? mhz : 0);
249 return; 259 return;
250 } 260 }
251 261
252 struct wlr_output_mode *mode, *best = NULL; 262 struct wlr_output_mode *mode, *best = NULL;
263 int best_diff_mhz = INT_MAX;
253 wl_list_for_each(mode, &output->modes, link) { 264 wl_list_for_each(mode, &output->modes, link) {
254 if (mode->width == width && mode->height == height) { 265 if (mode->width == width && mode->height == height) {
255 if (mode->refresh == mhz) { 266 int diff_mhz = abs(mode->refresh - mhz);
256 best = mode; 267 if (diff_mhz < best_diff_mhz) {
257 break; 268 best_diff_mhz = diff_mhz;
258 }
259 if (best == NULL || mode->refresh > best->refresh) {
260 best = mode; 269 best = mode;
270 if (best_diff_mhz == 0) {
271 break;
272 }
261 } 273 }
262 } 274 }
263 } 275 }
264 if (!best) { 276 if (best) {
265 sway_log(SWAY_ERROR, "Configured mode for %s not available", output->name); 277 sway_log(SWAY_INFO, "Assigning configured mode (%dx%d@%.3fHz) to %s",
266 sway_log(SWAY_INFO, "Picking preferred mode instead"); 278 best->width, best->height, best->refresh / 1000.f, output->name);
267 best = wlr_output_preferred_mode(output);
268 } else { 279 } else {
269 sway_log(SWAY_DEBUG, "Assigning configured mode to %s", output->name); 280 best = wlr_output_preferred_mode(output);
281 sway_log(SWAY_INFO, "Configured mode (%dx%d@%.3fHz) not available, "
282 "applying preferred mode (%dx%d@%.3fHz)",
283 width, height, refresh_rate,
284 best->width, best->height, best->refresh / 1000.f);
270 } 285 }
271 wlr_output_set_mode(output, best); 286 wlr_output_state_set_mode(pending, best);
287}
288
289static void set_modeline(struct wlr_output *output,
290 struct wlr_output_state *pending, drmModeModeInfo *drm_mode) {
291#if WLR_HAS_DRM_BACKEND
292 if (!wlr_output_is_drm(output)) {
293 sway_log(SWAY_ERROR, "Modeline can only be set to DRM output");
294 return;
295 }
296 sway_log(SWAY_DEBUG, "Assigning custom modeline to %s", output->name);
297 struct wlr_output_mode *mode = wlr_drm_connector_add_mode(output, drm_mode);
298 if (mode) {
299 wlr_output_state_set_mode(pending, mode);
300 }
301#else
302 sway_log(SWAY_ERROR, "Modeline can only be set to DRM output");
303#endif
272} 304}
273 305
274/* Some manufacturers hardcode the aspect-ratio of the output in the physical 306/* Some manufacturers hardcode the aspect-ratio of the output in the physical
@@ -289,23 +321,24 @@ static bool phys_size_is_aspect_ratio(struct wlr_output *output) {
289// 1 inch = 25.4 mm 321// 1 inch = 25.4 mm
290#define MM_PER_INCH 25.4 322#define MM_PER_INCH 25.4
291 323
292static int compute_default_scale(struct wlr_output *output) { 324static int compute_default_scale(struct wlr_output *output,
325 struct wlr_output_state *pending) {
293 struct wlr_box box = { .width = output->width, .height = output->height }; 326 struct wlr_box box = { .width = output->width, .height = output->height };
294 if (output->pending.committed & WLR_OUTPUT_STATE_MODE) { 327 if (pending->committed & WLR_OUTPUT_STATE_MODE) {
295 switch (output->pending.mode_type) { 328 switch (pending->mode_type) {
296 case WLR_OUTPUT_STATE_MODE_FIXED: 329 case WLR_OUTPUT_STATE_MODE_FIXED:
297 box.width = output->pending.mode->width; 330 box.width = pending->mode->width;
298 box.height = output->pending.mode->height; 331 box.height = pending->mode->height;
299 break; 332 break;
300 case WLR_OUTPUT_STATE_MODE_CUSTOM: 333 case WLR_OUTPUT_STATE_MODE_CUSTOM:
301 box.width = output->pending.custom_mode.width; 334 box.width = pending->custom_mode.width;
302 box.height = output->pending.custom_mode.height; 335 box.height = pending->custom_mode.height;
303 break; 336 break;
304 } 337 }
305 } 338 }
306 enum wl_output_transform transform = output->transform; 339 enum wl_output_transform transform = output->transform;
307 if (output->pending.committed & WLR_OUTPUT_STATE_TRANSFORM) { 340 if (pending->committed & WLR_OUTPUT_STATE_TRANSFORM) {
308 transform = output->pending.transform; 341 transform = pending->transform;
309 } 342 }
310 wlr_box_transform(&box, &box, transform, box.width, box.height); 343 wlr_box_transform(&box, &box, transform, box.width, box.height);
311 344
@@ -334,42 +367,90 @@ static int compute_default_scale(struct wlr_output *output) {
334 return 2; 367 return 2;
335} 368}
336 369
370/* Lists of formats to try, in order, when a specific render bit depth has
371 * been asked for. The second to last format in each list should always
372 * be XRGB8888, as a reliable backup in case the others are not available;
373 * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */
374static const uint32_t *bit_depth_preferences[] = {
375 [RENDER_BIT_DEPTH_8] = (const uint32_t []){
376 DRM_FORMAT_XRGB8888,
377 DRM_FORMAT_INVALID,
378 },
379 [RENDER_BIT_DEPTH_10] = (const uint32_t []){
380 DRM_FORMAT_XRGB2101010,
381 DRM_FORMAT_XBGR2101010,
382 DRM_FORMAT_XRGB8888,
383 DRM_FORMAT_INVALID,
384 },
385};
386
337static void queue_output_config(struct output_config *oc, 387static void queue_output_config(struct output_config *oc,
338 struct sway_output *output) { 388 struct sway_output *output, struct wlr_output_state *pending) {
339 if (output == root->noop_output) { 389 if (output == root->fallback_output) {
340 return; 390 return;
341 } 391 }
342 392
343 struct wlr_output *wlr_output = output->wlr_output; 393 struct wlr_output *wlr_output = output->wlr_output;
344 394
345 if (oc && (!oc->enabled || oc->dpms_state == DPMS_OFF)) { 395 if (oc && (!oc->enabled || oc->power == 0)) {
346 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); 396 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name);
347 wlr_output_enable(wlr_output, false); 397 wlr_output_state_set_enabled(pending, false);
348 return; 398 return;
349 } 399 }
350 400
351 sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name); 401 sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name);
352 wlr_output_enable(wlr_output, true); 402 wlr_output_state_set_enabled(pending, true);
353 403
354 if (oc && oc->width > 0 && oc->height > 0) { 404 if (oc && oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t) -1) {
405 sway_log(SWAY_DEBUG, "Set %s modeline",
406 wlr_output->name);
407 set_modeline(wlr_output, pending, &oc->drm_mode);
408 } else if (oc && oc->width > 0 && oc->height > 0) {
355 sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)", 409 sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)",
356 wlr_output->name, oc->width, oc->height, oc->refresh_rate); 410 wlr_output->name, oc->width, oc->height, oc->refresh_rate);
357 set_mode(wlr_output, oc->width, oc->height, 411 set_mode(wlr_output, pending, oc->width, oc->height,
358 oc->refresh_rate, oc->custom_mode == 1); 412 oc->refresh_rate, oc->custom_mode == 1);
359 } else if (!wl_list_empty(&wlr_output->modes)) { 413 } else if (!wl_list_empty(&wlr_output->modes)) {
360 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); 414 sway_log(SWAY_DEBUG, "Set preferred mode");
361 wlr_output_set_mode(wlr_output, mode); 415 struct wlr_output_mode *preferred_mode =
416 wlr_output_preferred_mode(wlr_output);
417 wlr_output_state_set_mode(pending, preferred_mode);
418
419 if (!wlr_output_test_state(wlr_output, pending)) {
420 sway_log(SWAY_DEBUG, "Preferred mode rejected, "
421 "falling back to another mode");
422 struct wlr_output_mode *mode;
423 wl_list_for_each(mode, &wlr_output->modes, link) {
424 if (mode == preferred_mode) {
425 continue;
426 }
427
428 wlr_output_state_set_mode(pending, mode);
429 if (wlr_output_test_state(wlr_output, pending)) {
430 break;
431 }
432 }
433 }
362 } 434 }
363 435
364 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { 436 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) {
365 sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name, 437 sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name,
366 sway_wl_output_subpixel_to_string(oc->subpixel)); 438 sway_wl_output_subpixel_to_string(oc->subpixel));
367 wlr_output_set_subpixel(wlr_output, oc->subpixel); 439 wlr_output_state_set_subpixel(pending, oc->subpixel);
368 } 440 }
369 441
442 enum wl_output_transform tr = WL_OUTPUT_TRANSFORM_NORMAL;
370 if (oc && oc->transform >= 0) { 443 if (oc && oc->transform >= 0) {
371 sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, oc->transform); 444 tr = oc->transform;
372 wlr_output_set_transform(wlr_output, oc->transform); 445#if WLR_HAS_DRM_BACKEND
446 } else if (wlr_output_is_drm(wlr_output)) {
447 tr = wlr_drm_connector_get_panel_orientation(wlr_output);
448 sway_log(SWAY_DEBUG, "Auto-detected output transform: %d", tr);
449#endif
450 }
451 if (wlr_output->transform != tr) {
452 sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, tr);
453 wlr_output_state_set_transform(pending, tr);
373 } 454 }
374 455
375 // Apply the scale last before the commit, because the scale auto-detection 456 // Apply the scale last before the commit, because the scale auto-detection
@@ -377,24 +458,54 @@ static void queue_output_config(struct output_config *oc,
377 float scale; 458 float scale;
378 if (oc && oc->scale > 0) { 459 if (oc && oc->scale > 0) {
379 scale = oc->scale; 460 scale = oc->scale;
461
462 // The factional-scale-v1 protocol uses increments of 120ths to send
463 // the scale factor to the client. Adjust the scale so that we use the
464 // same value as the clients'.
465 float adjusted_scale = round(scale * 120) / 120;
466 if (scale != adjusted_scale) {
467 sway_log(SWAY_INFO, "Adjusting output scale from %f to %f",
468 scale, adjusted_scale);
469 scale = adjusted_scale;
470 }
380 } else { 471 } else {
381 scale = compute_default_scale(wlr_output); 472 scale = compute_default_scale(wlr_output, pending);
382 sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale); 473 sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale);
383 } 474 }
384 if (scale != wlr_output->scale) { 475 if (scale != wlr_output->scale) {
385 sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale); 476 sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale);
386 wlr_output_set_scale(wlr_output, scale); 477 wlr_output_state_set_scale(pending, scale);
387 } 478 }
388 479
389 if (oc && oc->adaptive_sync != -1) { 480 if (oc && oc->adaptive_sync != -1) {
390 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, 481 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name,
391 oc->adaptive_sync); 482 oc->adaptive_sync);
392 wlr_output_enable_adaptive_sync(wlr_output, oc->adaptive_sync == 1); 483 wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1);
484 if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) {
485 sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring");
486 wlr_output_state_set_adaptive_sync_enabled(pending, false);
487 }
488 }
489
490 if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
491 const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth];
492 assert(fmts);
493
494 for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) {
495 wlr_output_state_set_render_format(pending, fmts[i]);
496 if (wlr_output_test_state(wlr_output, pending)) {
497 break;
498 }
499
500 sway_log(SWAY_DEBUG, "Preferred output format 0x%08x "
501 "failed to work, falling back to next in "
502 "list, 0x%08x", fmts[i], fmts[i + 1]);
503 }
393 } 504 }
394} 505}
395 506
396bool apply_output_config(struct output_config *oc, struct sway_output *output) { 507bool apply_output_config(struct output_config *oc, struct sway_output *output) {
397 if (output == root->noop_output) { 508 if (output == root->fallback_output) {
398 return false; 509 return false;
399 } 510 }
400 511
@@ -403,14 +514,11 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
403 // Flag to prevent the output mode event handler from calling us 514 // Flag to prevent the output mode event handler from calling us
404 output->enabling = (!oc || oc->enabled); 515 output->enabling = (!oc || oc->enabled);
405 516
406 queue_output_config(oc, output); 517 struct wlr_output_state pending = {0};
407 518 queue_output_config(oc, output, &pending);
408 if (!oc || oc->dpms_state != DPMS_OFF) {
409 output->current_mode = wlr_output->pending.mode;
410 }
411 519
412 sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name); 520 sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name);
413 if (!wlr_output_commit(wlr_output)) { 521 if (!wlr_output_commit_state(wlr_output, &pending)) {
414 // Failed to commit output changes, maybe the output is missing a CRTC. 522 // Failed to commit output changes, maybe the output is missing a CRTC.
415 // Leave the output disabled for now and try again when the output gets 523 // Leave the output disabled for now and try again when the output gets
416 // the mode we asked for. 524 // the mode we asked for.
@@ -430,10 +538,6 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
430 return true; 538 return true;
431 } 539 }
432 540
433 if (config->reloading) {
434 output_damage_whole(output);
435 }
436
437 if (oc) { 541 if (oc) {
438 enum scale_filter_mode scale_filter_old = output->scale_filter; 542 enum scale_filter_mode scale_filter_old = output->scale_filter;
439 switch (oc->scale_filter) { 543 switch (oc->scale_filter) {
@@ -450,6 +554,7 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
450 if (scale_filter_old != output->scale_filter) { 554 if (scale_filter_old != output->scale_filter) {
451 sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name, 555 sway_log(SWAY_DEBUG, "Set %s scale_filter to %s", oc->name,
452 sway_output_scale_filter_to_string(output->scale_filter)); 556 sway_output_scale_filter_to_string(output->scale_filter));
557 wlr_damage_ring_add_whole(&output->scene_output->damage_ring);
453 } 558 }
454 } 559 }
455 560
@@ -462,12 +567,12 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
462 } 567 }
463 568
464 // Update output->{lx, ly, width, height} 569 // Update output->{lx, ly, width, height}
465 struct wlr_box *output_box = 570 struct wlr_box output_box;
466 wlr_output_layout_get_box(root->output_layout, wlr_output); 571 wlr_output_layout_get_box(root->output_layout, wlr_output, &output_box);
467 output->lx = output_box->x; 572 output->lx = output_box.x;
468 output->ly = output_box->y; 573 output->ly = output_box.y;
469 output->width = output_box->width; 574 output->width = output_box.width;
470 output->height = output_box->height; 575 output->height = output_box.height;
471 576
472 if (!output->enabled) { 577 if (!output->enabled) {
473 output_enable(output); 578 output_enable(output);
@@ -482,24 +587,26 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
482 // Reconfigure all devices, since input config may have been applied before 587 // Reconfigure all devices, since input config may have been applied before
483 // this output came online, and some config items (like map_to_output) are 588 // this output came online, and some config items (like map_to_output) are
484 // dependent on an output being present. 589 // dependent on an output being present.
485 input_manager_configure_all_inputs(); 590 input_manager_configure_all_input_mappings();
591 // Reconfigure the cursor images, since the scale may have changed.
592 input_manager_configure_xcursor();
486 return true; 593 return true;
487} 594}
488 595
489bool test_output_config(struct output_config *oc, struct sway_output *output) { 596bool test_output_config(struct output_config *oc, struct sway_output *output) {
490 if (output == root->noop_output) { 597 if (output == root->fallback_output) {
491 return false; 598 return false;
492 } 599 }
493 600
494 queue_output_config(oc, output); 601 struct wlr_output_state pending = {0};
495 bool ok = wlr_output_test(output->wlr_output); 602 queue_output_config(oc, output, &pending);
496 wlr_output_rollback(output->wlr_output); 603 return wlr_output_test_state(output->wlr_output, &pending);
497 return ok;
498} 604}
499 605
500static void default_output_config(struct output_config *oc, 606static void default_output_config(struct output_config *oc,
501 struct wlr_output *wlr_output) { 607 struct wlr_output *wlr_output) {
502 oc->enabled = 1; 608 oc->enabled = 1;
609 oc->power = 1;
503 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); 610 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
504 if (mode != NULL) { 611 if (mode != NULL) {
505 oc->width = mode->width; 612 oc->width = mode->width;
@@ -512,7 +619,6 @@ static void default_output_config(struct output_config *oc,
512 struct sway_output *output = wlr_output->data; 619 struct sway_output *output = wlr_output->data;
513 oc->subpixel = output->detected_subpixel; 620 oc->subpixel = output->detected_subpixel;
514 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; 621 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
515 oc->dpms_state = DPMS_ON;
516 oc->max_render_time = 0; 622 oc->max_render_time = 0;
517} 623}
518 624
@@ -524,9 +630,7 @@ static struct output_config *get_output_config(char *identifier,
524 struct output_config *oc_name = NULL; 630 struct output_config *oc_name = NULL;
525 struct output_config *oc_id = NULL; 631 struct output_config *oc_id = NULL;
526 632
527 size_t length = snprintf(NULL, 0, "%s on %s", identifier, name) + 1; 633 char *id_on_name = format_str("%s on %s", identifier, name);
528 char *id_on_name = malloc(length);
529 snprintf(id_on_name, length, "%s on %s", identifier, name);
530 int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); 634 int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name);
531 if (i >= 0) { 635 if (i >= 0) {
532 oc_id_on_name = config->output_configs->items[i]; 636 oc_id_on_name = config->output_configs->items[i];
@@ -567,10 +671,10 @@ static struct output_config *get_output_config(char *identifier,
567 671
568 sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)" 672 sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)"
569 " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)" 673 " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)"
570 " (dpms %d) (max render time: %d)", result->name, result->enabled, 674 " (power %d) (max render time: %d)", result->name, result->enabled,
571 result->width, result->height, result->refresh_rate, 675 result->width, result->height, result->refresh_rate,
572 result->x, result->y, result->scale, result->transform, 676 result->x, result->y, result->scale, result->transform,
573 result->background, result->background_option, result->dpms_state, 677 result->background, result->background_option, result->power,
574 result->max_render_time); 678 result->max_render_time);
575 } else if (oc_name) { 679 } else if (oc_name) {
576 // No identifier config, just return a copy of the name config 680 // No identifier config, just return a copy of the name config
@@ -613,12 +717,11 @@ void apply_output_config_to_outputs(struct output_config *oc) {
613 // this is during startup then there will be no container and config 717 // this is during startup then there will be no container and config
614 // will be applied during normal "new output" event from wlroots. 718 // will be applied during normal "new output" event from wlroots.
615 bool wildcard = strcmp(oc->name, "*") == 0; 719 bool wildcard = strcmp(oc->name, "*") == 0;
616 char id[128];
617 struct sway_output *sway_output, *tmp; 720 struct sway_output *sway_output, *tmp;
618 wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) { 721 wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) {
619 char *name = sway_output->wlr_output->name; 722 if (output_match_name_or_id(sway_output, oc->name)) {
620 output_get_identifier(id, sizeof(id), sway_output); 723 char id[128];
621 if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) { 724 output_get_identifier(id, sizeof(id), sway_output);
622 struct output_config *current = get_output_config(id, sway_output); 725 struct output_config *current = get_output_config(id, sway_output);
623 if (!current) { 726 if (!current) {
624 // No stored output config matched, apply oc directly 727 // No stored output config matched, apply oc directly
@@ -702,6 +805,8 @@ static bool _spawn_swaybg(char **command) {
702 sway_log_errno(SWAY_ERROR, "fork failed"); 805 sway_log_errno(SWAY_ERROR, "fork failed");
703 return false; 806 return false;
704 } else if (pid == 0) { 807 } else if (pid == 0) {
808 restore_nofile_limit();
809
705 pid = fork(); 810 pid = fork();
706 if (pid < 0) { 811 if (pid < 0) {
707 sway_log_errno(SWAY_ERROR, "fork failed"); 812 sway_log_errno(SWAY_ERROR, "fork failed");
diff --git a/sway/config/seat.c b/sway/config/seat.c
index 84260aa3..6d5d91ae 100644
--- a/sway/config/seat.c
+++ b/sway/config/seat.c
@@ -99,7 +99,6 @@ static void seat_attachment_config_free(
99 struct seat_attachment_config *attachment) { 99 struct seat_attachment_config *attachment) {
100 free(attachment->identifier); 100 free(attachment->identifier);
101 free(attachment); 101 free(attachment);
102 return;
103} 102}
104 103
105static struct seat_attachment_config *seat_attachment_config_copy( 104static struct seat_attachment_config *seat_attachment_config_copy(
diff --git a/sway/criteria.c b/sway/criteria.c
index 409160c5..78ea8b8a 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -3,7 +3,8 @@
3#include <stdio.h> 3#include <stdio.h>
4#include <stdbool.h> 4#include <stdbool.h>
5#include <strings.h> 5#include <strings.h>
6#include <pcre.h> 6#define PCRE2_CODE_UNIT_WIDTH 8
7#include <pcre2.h>
7#include "sway/criteria.h" 8#include "sway/criteria.h"
8#include "sway/tree/container.h" 9#include "sway/tree/container.h"
9#include "sway/config.h" 10#include "sway/config.h"
@@ -18,6 +19,7 @@
18bool criteria_is_empty(struct criteria *criteria) { 19bool criteria_is_empty(struct criteria *criteria) {
19 return !criteria->title 20 return !criteria->title
20 && !criteria->shell 21 && !criteria->shell
22 && !criteria->all
21 && !criteria->app_id 23 && !criteria->app_id
22 && !criteria->con_mark 24 && !criteria->con_mark
23 && !criteria->con_id 25 && !criteria->con_id
@@ -40,17 +42,19 @@ bool criteria_is_empty(struct criteria *criteria) {
40char *error = NULL; 42char *error = NULL;
41 43
42// Returns error string on failure or NULL otherwise. 44// Returns error string on failure or NULL otherwise.
43static bool generate_regex(pcre **regex, char *value) { 45static bool generate_regex(pcre2_code **regex, char *value) {
44 const char *reg_err; 46 int errorcode;
45 int offset; 47 PCRE2_SIZE offset;
46
47 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
48 48
49 *regex = pcre2_compile((PCRE2_SPTR)value, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &offset, NULL);
49 if (!*regex) { 50 if (!*regex) {
51 PCRE2_UCHAR buffer[256];
52 pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
53
50 const char *fmt = "Regex compilation for '%s' failed: %s"; 54 const char *fmt = "Regex compilation for '%s' failed: %s";
51 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; 55 int len = strlen(fmt) + strlen(value) + strlen((char*) buffer) - 3;
52 error = malloc(len); 56 error = malloc(len);
53 snprintf(error, len, fmt, value, reg_err); 57 snprintf(error, len, fmt, value, buffer);
54 return false; 58 return false;
55 } 59 }
56 60
@@ -66,7 +70,7 @@ static bool pattern_create(struct pattern **pattern, char *value) {
66 if (strcmp(value, "__focused__") == 0) { 70 if (strcmp(value, "__focused__") == 0) {
67 (*pattern)->match_type = PATTERN_FOCUSED; 71 (*pattern)->match_type = PATTERN_FOCUSED;
68 } else { 72 } else {
69 (*pattern)->match_type = PATTERN_PCRE; 73 (*pattern)->match_type = PATTERN_PCRE2;
70 if (!generate_regex(&(*pattern)->regex, value)) { 74 if (!generate_regex(&(*pattern)->regex, value)) {
71 return false; 75 return false;
72 }; 76 };
@@ -77,7 +81,7 @@ static bool pattern_create(struct pattern **pattern, char *value) {
77static void pattern_destroy(struct pattern *pattern) { 81static void pattern_destroy(struct pattern *pattern) {
78 if (pattern) { 82 if (pattern) {
79 if (pattern->regex) { 83 if (pattern->regex) {
80 pcre_free(pattern->regex); 84 pcre2_code_free(pattern->regex);
81 } 85 }
82 free(pattern); 86 free(pattern);
83 } 87 }
@@ -93,14 +97,18 @@ void criteria_destroy(struct criteria *criteria) {
93 pattern_destroy(criteria->window_role); 97 pattern_destroy(criteria->window_role);
94#endif 98#endif
95 pattern_destroy(criteria->con_mark); 99 pattern_destroy(criteria->con_mark);
96 free(criteria->workspace); 100 pattern_destroy(criteria->workspace);
101 free(criteria->target);
97 free(criteria->cmdlist); 102 free(criteria->cmdlist);
98 free(criteria->raw); 103 free(criteria->raw);
99 free(criteria); 104 free(criteria);
100} 105}
101 106
102static int regex_cmp(const char *item, const pcre *regex) { 107static int regex_cmp(const char *item, const pcre2_code *regex) {
103 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); 108 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
109 int result = pcre2_match(regex, (PCRE2_SPTR)item, strlen(item), 0, 0, match_data, NULL);
110 pcre2_match_data_free(match_data);
111 return result;
104} 112}
105 113
106#if HAVE_XWAYLAND 114#if HAVE_XWAYLAND
@@ -155,7 +163,7 @@ static bool criteria_matches_container(struct criteria *criteria,
155 bool exists = false; 163 bool exists = false;
156 struct sway_container *con = container; 164 struct sway_container *con = container;
157 for (int i = 0; i < con->marks->length; ++i) { 165 for (int i = 0; i < con->marks->length; ++i) {
158 if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) == 0) { 166 if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) >= 0) {
159 exists = true; 167 exists = true;
160 break; 168 break;
161 } 169 }
@@ -183,7 +191,7 @@ static bool criteria_matches_view(struct criteria *criteria,
183 if (criteria->title) { 191 if (criteria->title) {
184 const char *title = view_get_title(view); 192 const char *title = view_get_title(view);
185 if (!title) { 193 if (!title) {
186 return false; 194 title = "";
187 } 195 }
188 196
189 switch (criteria->title->match_type) { 197 switch (criteria->title->match_type) {
@@ -192,8 +200,8 @@ static bool criteria_matches_view(struct criteria *criteria,
192 return false; 200 return false;
193 } 201 }
194 break; 202 break;
195 case PATTERN_PCRE: 203 case PATTERN_PCRE2:
196 if (regex_cmp(title, criteria->title->regex) != 0) { 204 if (regex_cmp(title, criteria->title->regex) < 0) {
197 return false; 205 return false;
198 } 206 }
199 break; 207 break;
@@ -203,7 +211,7 @@ static bool criteria_matches_view(struct criteria *criteria,
203 if (criteria->shell) { 211 if (criteria->shell) {
204 const char *shell = view_get_shell(view); 212 const char *shell = view_get_shell(view);
205 if (!shell) { 213 if (!shell) {
206 return false; 214 shell = "";
207 } 215 }
208 216
209 switch (criteria->shell->match_type) { 217 switch (criteria->shell->match_type) {
@@ -212,8 +220,8 @@ static bool criteria_matches_view(struct criteria *criteria,
212 return false; 220 return false;
213 } 221 }
214 break; 222 break;
215 case PATTERN_PCRE: 223 case PATTERN_PCRE2:
216 if (regex_cmp(shell, criteria->shell->regex) != 0) { 224 if (regex_cmp(shell, criteria->shell->regex) < 0) {
217 return false; 225 return false;
218 } 226 }
219 break; 227 break;
@@ -223,7 +231,7 @@ static bool criteria_matches_view(struct criteria *criteria,
223 if (criteria->app_id) { 231 if (criteria->app_id) {
224 const char *app_id = view_get_app_id(view); 232 const char *app_id = view_get_app_id(view);
225 if (!app_id) { 233 if (!app_id) {
226 return false; 234 app_id = "";
227 } 235 }
228 236
229 switch (criteria->app_id->match_type) { 237 switch (criteria->app_id->match_type) {
@@ -232,8 +240,8 @@ static bool criteria_matches_view(struct criteria *criteria,
232 return false; 240 return false;
233 } 241 }
234 break; 242 break;
235 case PATTERN_PCRE: 243 case PATTERN_PCRE2:
236 if (regex_cmp(app_id, criteria->app_id->regex) != 0) { 244 if (regex_cmp(app_id, criteria->app_id->regex) < 0) {
237 return false; 245 return false;
238 } 246 }
239 break; 247 break;
@@ -255,7 +263,7 @@ static bool criteria_matches_view(struct criteria *criteria,
255 if (criteria->class) { 263 if (criteria->class) {
256 const char *class = view_get_class(view); 264 const char *class = view_get_class(view);
257 if (!class) { 265 if (!class) {
258 return false; 266 class = "";
259 } 267 }
260 268
261 switch (criteria->class->match_type) { 269 switch (criteria->class->match_type) {
@@ -264,8 +272,8 @@ static bool criteria_matches_view(struct criteria *criteria,
264 return false; 272 return false;
265 } 273 }
266 break; 274 break;
267 case PATTERN_PCRE: 275 case PATTERN_PCRE2:
268 if (regex_cmp(class, criteria->class->regex) != 0) { 276 if (regex_cmp(class, criteria->class->regex) < 0) {
269 return false; 277 return false;
270 } 278 }
271 break; 279 break;
@@ -275,17 +283,17 @@ static bool criteria_matches_view(struct criteria *criteria,
275 if (criteria->instance) { 283 if (criteria->instance) {
276 const char *instance = view_get_instance(view); 284 const char *instance = view_get_instance(view);
277 if (!instance) { 285 if (!instance) {
278 return false; 286 instance = "";
279 } 287 }
280 288
281 switch (criteria->instance->match_type) { 289 switch (criteria->instance->match_type) {
282 case PATTERN_FOCUSED: 290 case PATTERN_FOCUSED:
283 if (focused && strcmp(instance, view_get_instance(focused))) { 291 if (focused && lenient_strcmp(instance, view_get_instance(focused))) {
284 return false; 292 return false;
285 } 293 }
286 break; 294 break;
287 case PATTERN_PCRE: 295 case PATTERN_PCRE2:
288 if (regex_cmp(instance, criteria->instance->regex) != 0) { 296 if (regex_cmp(instance, criteria->instance->regex) < 0) {
289 return false; 297 return false;
290 } 298 }
291 break; 299 break;
@@ -295,17 +303,17 @@ static bool criteria_matches_view(struct criteria *criteria,
295 if (criteria->window_role) { 303 if (criteria->window_role) {
296 const char *window_role = view_get_window_role(view); 304 const char *window_role = view_get_window_role(view);
297 if (!window_role) { 305 if (!window_role) {
298 return false; 306 window_role = "";
299 } 307 }
300 308
301 switch (criteria->window_role->match_type) { 309 switch (criteria->window_role->match_type) {
302 case PATTERN_FOCUSED: 310 case PATTERN_FOCUSED:
303 if (focused && strcmp(window_role, view_get_window_role(focused))) { 311 if (focused && lenient_strcmp(window_role, view_get_window_role(focused))) {
304 return false; 312 return false;
305 } 313 }
306 break; 314 break;
307 case PATTERN_PCRE: 315 case PATTERN_PCRE2:
308 if (regex_cmp(window_role, criteria->window_role->regex) != 0) { 316 if (regex_cmp(window_role, criteria->window_role->regex) < 0) {
309 return false; 317 return false;
310 } 318 }
311 break; 319 break;
@@ -351,7 +359,7 @@ static bool criteria_matches_view(struct criteria *criteria,
351 } 359 }
352 360
353 if (criteria->workspace) { 361 if (criteria->workspace) {
354 struct sway_workspace *ws = view->container->workspace; 362 struct sway_workspace *ws = view->container->pending.workspace;
355 if (!ws) { 363 if (!ws) {
356 return false; 364 return false;
357 } 365 }
@@ -359,12 +367,12 @@ static bool criteria_matches_view(struct criteria *criteria,
359 switch (criteria->workspace->match_type) { 367 switch (criteria->workspace->match_type) {
360 case PATTERN_FOCUSED: 368 case PATTERN_FOCUSED:
361 if (focused && 369 if (focused &&
362 strcmp(ws->name, focused->container->workspace->name)) { 370 strcmp(ws->name, focused->container->pending.workspace->name)) {
363 return false; 371 return false;
364 } 372 }
365 break; 373 break;
366 case PATTERN_PCRE: 374 case PATTERN_PCRE2:
367 if (regex_cmp(ws->name, criteria->workspace->regex) != 0) { 375 if (regex_cmp(ws->name, criteria->workspace->regex) < 0) {
368 return false; 376 return false;
369 } 377 }
370 break; 378 break;
@@ -449,6 +457,7 @@ static enum atom_name parse_window_type(const char *type) {
449#endif 457#endif
450 458
451enum criteria_token { 459enum criteria_token {
460 T_ALL,
452 T_APP_ID, 461 T_APP_ID,
453 T_CON_ID, 462 T_CON_ID,
454 T_CON_MARK, 463 T_CON_MARK,
@@ -471,7 +480,9 @@ enum criteria_token {
471}; 480};
472 481
473static enum criteria_token token_from_name(char *name) { 482static enum criteria_token token_from_name(char *name) {
474 if (strcmp(name, "app_id") == 0) { 483 if (strcmp(name, "all") == 0) {
484 return T_ALL;
485 } else if (strcmp(name, "app_id") == 0) {
475 return T_APP_ID; 486 return T_APP_ID;
476 } else if (strcmp(name, "con_id") == 0) { 487 } else if (strcmp(name, "con_id") == 0) {
477 return T_CON_ID; 488 return T_CON_ID;
@@ -517,8 +528,8 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
517 return false; 528 return false;
518 } 529 }
519 530
520 // Require value, unless token is floating or tiled 531 // Require value, unless token is all, floating or tiled
521 if (!value && token != T_FLOATING && token != T_TILING) { 532 if (!value && token != T_ALL && token != T_FLOATING && token != T_TILING) {
522 const char *fmt = "Token '%s' requires a value"; 533 const char *fmt = "Token '%s' requires a value";
523 int len = strlen(fmt) + strlen(name) - 1; 534 int len = strlen(fmt) + strlen(name) - 1;
524 error = malloc(len); 535 error = malloc(len);
@@ -528,6 +539,9 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
528 539
529 char *endptr = NULL; 540 char *endptr = NULL;
530 switch (token) { 541 switch (token) {
542 case T_ALL:
543 criteria->all = true;
544 break;
531 case T_TITLE: 545 case T_TITLE:
532 pattern_create(&criteria->title, value); 546 pattern_create(&criteria->title, value);
533 break; 547 break;
@@ -676,7 +690,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
676 } 690 }
677 name = calloc(head - namestart + 1, 1); 691 name = calloc(head - namestart + 1, 1);
678 if (head != namestart) { 692 if (head != namestart) {
679 strncpy(name, namestart, head - namestart); 693 memcpy(name, namestart, head - namestart);
680 } 694 }
681 // Parse token value 695 // Parse token value
682 skip_spaces(&head); 696 skip_spaces(&head);
@@ -703,7 +717,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
703 } 717 }
704 } 718 }
705 value = calloc(head - valuestart + 1, 1); 719 value = calloc(head - valuestart + 1, 1);
706 strncpy(value, valuestart, head - valuestart); 720 memcpy(value, valuestart, head - valuestart);
707 if (in_quotes) { 721 if (in_quotes) {
708 ++head; 722 ++head;
709 in_quotes = false; 723 in_quotes = false;
@@ -734,7 +748,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
734 ++head; 748 ++head;
735 int len = head - raw; 749 int len = head - raw;
736 criteria->raw = calloc(len + 1, 1); 750 criteria->raw = calloc(len + 1, 1);
737 strncpy(criteria->raw, raw, len); 751 memcpy(criteria->raw, raw, len);
738 return criteria; 752 return criteria;
739 753
740cleanup: 754cleanup:
diff --git a/sway/desktop/desktop.c b/sway/desktop/desktop.c
deleted file mode 100644
index ec45d80a..00000000
--- a/sway/desktop/desktop.c
+++ /dev/null
@@ -1,39 +0,0 @@
1#include "sway/tree/container.h"
2#include "sway/desktop.h"
3#include "sway/output.h"
4
5void desktop_damage_surface(struct wlr_surface *surface, double lx, double ly,
6 bool whole) {
7 for (int i = 0; i < root->outputs->length; ++i) {
8 struct sway_output *output = root->outputs->items[i];
9 struct wlr_box *output_box = wlr_output_layout_get_box(
10 root->output_layout, output->wlr_output);
11 output_damage_surface(output, lx - output_box->x,
12 ly - output_box->y, surface, whole);
13 }
14}
15
16void desktop_damage_whole_container(struct sway_container *con) {
17 for (int i = 0; i < root->outputs->length; ++i) {
18 struct sway_output *output = root->outputs->items[i];
19 output_damage_whole_container(output, con);
20 }
21}
22
23void desktop_damage_box(struct wlr_box *box) {
24 for (int i = 0; i < root->outputs->length; ++i) {
25 struct sway_output *output = root->outputs->items[i];
26 output_damage_box(output, box);
27 }
28}
29
30void desktop_damage_view(struct sway_view *view) {
31 desktop_damage_whole_container(view->container);
32 struct wlr_box box = {
33 .x = view->container->current.content_x - view->geometry.x,
34 .y = view->container->current.content_y - view->geometry.y,
35 .width = view->surface->current.width,
36 .height = view->surface->current.height,
37 };
38 desktop_damage_box(&box);
39}
diff --git a/sway/desktop/idle_inhibit_v1.c b/sway/desktop/idle_inhibit_v1.c
index a5cfd5b2..f3af7aa1 100644
--- a/sway/desktop/idle_inhibit_v1.c
+++ b/sway/desktop/idle_inhibit_v1.c
@@ -1,5 +1,5 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <wlr/types/wlr_idle.h> 2#include <wlr/types/wlr_idle_notify_v1.h>
3#include "log.h" 3#include "log.h"
4#include "sway/desktop/idle_inhibit_v1.h" 4#include "sway/desktop/idle_inhibit_v1.h"
5#include "sway/input/seat.h" 5#include "sway/input/seat.h"
@@ -11,7 +11,7 @@
11static void destroy_inhibitor(struct sway_idle_inhibitor_v1 *inhibitor) { 11static void destroy_inhibitor(struct sway_idle_inhibitor_v1 *inhibitor) {
12 wl_list_remove(&inhibitor->link); 12 wl_list_remove(&inhibitor->link);
13 wl_list_remove(&inhibitor->destroy.link); 13 wl_list_remove(&inhibitor->destroy.link);
14 sway_idle_inhibit_v1_check_active(inhibitor->manager); 14 sway_idle_inhibit_v1_check_active();
15 free(inhibitor); 15 free(inhibitor);
16} 16}
17 17
@@ -34,43 +34,43 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data) {
34 return; 34 return;
35 } 35 }
36 36
37 inhibitor->manager = manager;
38 inhibitor->mode = INHIBIT_IDLE_APPLICATION; 37 inhibitor->mode = INHIBIT_IDLE_APPLICATION;
39 inhibitor->view = view_from_wlr_surface(wlr_inhibitor->surface); 38 inhibitor->wlr_inhibitor = wlr_inhibitor;
40 wl_list_insert(&manager->inhibitors, &inhibitor->link); 39 wl_list_insert(&manager->inhibitors, &inhibitor->link);
41 40
42 inhibitor->destroy.notify = handle_destroy; 41 inhibitor->destroy.notify = handle_destroy;
43 wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy); 42 wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->destroy);
44 43
45 sway_idle_inhibit_v1_check_active(manager); 44 sway_idle_inhibit_v1_check_active();
46} 45}
47 46
48void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view, 47void sway_idle_inhibit_v1_user_inhibitor_register(struct sway_view *view,
49 enum sway_idle_inhibit_mode mode) { 48 enum sway_idle_inhibit_mode mode) {
49 struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1;
50
50 struct sway_idle_inhibitor_v1 *inhibitor = 51 struct sway_idle_inhibitor_v1 *inhibitor =
51 calloc(1, sizeof(struct sway_idle_inhibitor_v1)); 52 calloc(1, sizeof(struct sway_idle_inhibitor_v1));
52 if (!inhibitor) { 53 if (!inhibitor) {
53 return; 54 return;
54 } 55 }
55 56
56 inhibitor->manager = server.idle_inhibit_manager_v1;
57 inhibitor->mode = mode; 57 inhibitor->mode = mode;
58 inhibitor->view = view; 58 inhibitor->view = view;
59 wl_list_insert(&inhibitor->manager->inhibitors, &inhibitor->link); 59 wl_list_insert(&manager->inhibitors, &inhibitor->link);
60 60
61 inhibitor->destroy.notify = handle_destroy; 61 inhibitor->destroy.notify = handle_destroy;
62 wl_signal_add(&view->events.unmap, &inhibitor->destroy); 62 wl_signal_add(&view->events.unmap, &inhibitor->destroy);
63 63
64 sway_idle_inhibit_v1_check_active(inhibitor->manager); 64 sway_idle_inhibit_v1_check_active();
65} 65}
66 66
67struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view( 67struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view(
68 struct sway_view *view) { 68 struct sway_view *view) {
69 struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1;
69 struct sway_idle_inhibitor_v1 *inhibitor; 70 struct sway_idle_inhibitor_v1 *inhibitor;
70 wl_list_for_each(inhibitor, &server.idle_inhibit_manager_v1->inhibitors, 71 wl_list_for_each(inhibitor, &manager->inhibitors, link) {
71 link) { 72 if (inhibitor->mode != INHIBIT_IDLE_APPLICATION &&
72 if (inhibitor->view == view && 73 inhibitor->view == view) {
73 inhibitor->mode != INHIBIT_IDLE_APPLICATION) {
74 return inhibitor; 74 return inhibitor;
75 } 75 }
76 } 76 }
@@ -79,11 +79,11 @@ struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_user_inhibitor_for_view(
79 79
80struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_application_inhibitor_for_view( 80struct sway_idle_inhibitor_v1 *sway_idle_inhibit_v1_application_inhibitor_for_view(
81 struct sway_view *view) { 81 struct sway_view *view) {
82 struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1;
82 struct sway_idle_inhibitor_v1 *inhibitor; 83 struct sway_idle_inhibitor_v1 *inhibitor;
83 wl_list_for_each(inhibitor, &server.idle_inhibit_manager_v1->inhibitors, 84 wl_list_for_each(inhibitor, &manager->inhibitors, link) {
84 link) { 85 if (inhibitor->mode == INHIBIT_IDLE_APPLICATION &&
85 if (inhibitor->view == view && 86 view_from_wlr_surface(inhibitor->wlr_inhibitor->surface) == view) {
86 inhibitor->mode == INHIBIT_IDLE_APPLICATION) {
87 return inhibitor; 87 return inhibitor;
88 } 88 }
89 } 89 }
@@ -104,10 +104,10 @@ void sway_idle_inhibit_v1_user_inhibitor_destroy(
104 104
105bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) { 105bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) {
106 switch (inhibitor->mode) { 106 switch (inhibitor->mode) {
107 case INHIBIT_IDLE_APPLICATION: 107 case INHIBIT_IDLE_APPLICATION:;
108 // If there is no view associated with the inhibitor, assume visible 108 // If there is no view associated with the inhibitor, assume visible
109 return !inhibitor->view || !inhibitor->view->container || 109 struct sway_view *view = view_from_wlr_surface(inhibitor->wlr_inhibitor->surface);
110 view_is_visible(inhibitor->view); 110 return !view || !view->container || view_is_visible(view);
111 case INHIBIT_IDLE_FOCUS:; 111 case INHIBIT_IDLE_FOCUS:;
112 struct sway_seat *seat = NULL; 112 struct sway_seat *seat = NULL;
113 wl_list_for_each(seat, &server.input->seats, link) { 113 wl_list_for_each(seat, &server.input->seats, link) {
@@ -130,8 +130,8 @@ bool sway_idle_inhibit_v1_is_active(struct sway_idle_inhibitor_v1 *inhibitor) {
130 return false; 130 return false;
131} 131}
132 132
133void sway_idle_inhibit_v1_check_active( 133void sway_idle_inhibit_v1_check_active(void) {
134 struct sway_idle_inhibit_manager_v1 *manager) { 134 struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1;
135 struct sway_idle_inhibitor_v1 *inhibitor; 135 struct sway_idle_inhibitor_v1 *inhibitor;
136 bool inhibited = false; 136 bool inhibited = false;
137 wl_list_for_each(inhibitor, &manager->inhibitors, link) { 137 wl_list_for_each(inhibitor, &manager->inhibitors, link) {
@@ -139,27 +139,21 @@ void sway_idle_inhibit_v1_check_active(
139 break; 139 break;
140 } 140 }
141 } 141 }
142 wlr_idle_set_enabled(manager->idle, NULL, !inhibited); 142 wlr_idle_notifier_v1_set_inhibited(server.idle_notifier_v1, inhibited);
143} 143}
144 144
145struct sway_idle_inhibit_manager_v1 *sway_idle_inhibit_manager_v1_create( 145bool sway_idle_inhibit_manager_v1_init(void) {
146 struct wl_display *wl_display, struct wlr_idle *idle) { 146 struct sway_idle_inhibit_manager_v1 *manager = &server.idle_inhibit_manager_v1;
147 struct sway_idle_inhibit_manager_v1 *manager =
148 calloc(1, sizeof(struct sway_idle_inhibit_manager_v1));
149 if (!manager) {
150 return NULL;
151 }
152 147
153 manager->wlr_manager = wlr_idle_inhibit_v1_create(wl_display); 148 manager->wlr_manager = wlr_idle_inhibit_v1_create(server.wl_display);
154 if (!manager->wlr_manager) { 149 if (!manager->wlr_manager) {
155 free(manager); 150 return false;
156 return NULL;
157 } 151 }
158 manager->idle = idle; 152
159 wl_signal_add(&manager->wlr_manager->events.new_inhibitor, 153 wl_signal_add(&manager->wlr_manager->events.new_inhibitor,
160 &manager->new_idle_inhibitor_v1); 154 &manager->new_idle_inhibitor_v1);
161 manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1; 155 manager->new_idle_inhibitor_v1.notify = handle_idle_inhibitor_v1;
162 wl_list_init(&manager->inhibitors); 156 wl_list_init(&manager->inhibitors);
163 157
164 return manager; 158 return true;
165} 159}
diff --git a/sway/desktop/launcher.c b/sway/desktop/launcher.c
new file mode 100644
index 00000000..00a7e38a
--- /dev/null
+++ b/sway/desktop/launcher.c
@@ -0,0 +1,255 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h>
3#include <string.h>
4#include <wlr/types/wlr_xdg_activation_v1.h>
5#include "sway/input/seat.h"
6#include "sway/output.h"
7#include "sway/desktop/launcher.h"
8#include "sway/tree/node.h"
9#include "sway/tree/container.h"
10#include "sway/tree/workspace.h"
11#include "sway/tree/root.h"
12#include "log.h"
13
14/**
15 * Get the pid of a parent process given the pid of a child process.
16 *
17 * Returns the parent pid or NULL if the parent pid cannot be determined.
18 */
19static pid_t get_parent_pid(pid_t child) {
20 pid_t parent = -1;
21 char file_name[100];
22 char *buffer = NULL;
23 const char *sep = " ";
24 FILE *stat = NULL;
25 size_t buf_size = 0;
26
27 snprintf(file_name, sizeof(file_name), "/proc/%d/stat", child);
28
29 if ((stat = fopen(file_name, "r"))) {
30 if (getline(&buffer, &buf_size, stat) != -1) {
31 strtok(buffer, sep); // pid
32 strtok(NULL, sep); // executable name
33 strtok(NULL, sep); // state
34 char *token = strtok(NULL, sep); // parent pid
35 parent = strtol(token, NULL, 10);
36 }
37 free(buffer);
38 fclose(stat);
39 }
40
41 if (parent) {
42 return (parent == child) ? -1 : parent;
43 }
44
45 return -1;
46}
47
48void launcher_ctx_consume(struct launcher_ctx *ctx) {
49 // The view is now responsible for destroying this ctx
50 wl_list_remove(&ctx->token_destroy.link);
51 wl_list_init(&ctx->token_destroy.link);
52
53 if (!ctx->activated) {
54 // An unactivated token hasn't been destroyed yet
55 wlr_xdg_activation_token_v1_destroy(ctx->token);
56 }
57 ctx->token = NULL;
58
59 // Prevent additional matches
60 wl_list_remove(&ctx->link);
61 wl_list_init(&ctx->link);
62}
63
64void launcher_ctx_destroy(struct launcher_ctx *ctx) {
65 if (ctx == NULL) {
66 return;
67 }
68 wl_list_remove(&ctx->node_destroy.link);
69 wl_list_remove(&ctx->token_destroy.link);
70 wl_list_remove(&ctx->link);
71 wlr_xdg_activation_token_v1_destroy(ctx->token);
72 free(ctx->fallback_name);
73 free(ctx);
74}
75
76struct launcher_ctx *launcher_ctx_find_pid(pid_t pid) {
77 if (wl_list_empty(&server.pending_launcher_ctxs)) {
78 return NULL;
79 }
80
81 struct launcher_ctx *ctx = NULL;
82 sway_log(SWAY_DEBUG, "Looking up workspace for pid %d", pid);
83
84 do {
85 struct launcher_ctx *_ctx = NULL;
86 wl_list_for_each(_ctx, &server.pending_launcher_ctxs, link) {
87 if (pid == _ctx->pid) {
88 ctx = _ctx;
89 sway_log(SWAY_DEBUG,
90 "found %s match for pid %d: %s",
91 node_type_to_str(ctx->node->type), pid, node_get_name(ctx->node));
92 break;
93 }
94 }
95 pid = get_parent_pid(pid);
96 } while (pid > 1);
97
98 return ctx;
99}
100
101struct sway_workspace *launcher_ctx_get_workspace(
102 struct launcher_ctx *ctx) {
103 struct sway_workspace *ws = NULL;
104 struct sway_output *output = NULL;
105
106 switch (ctx->node->type) {
107 case N_CONTAINER:
108 // Unimplemented
109 // TODO: add container matching?
110 ws = ctx->node->sway_container->pending.workspace;
111 break;
112 case N_WORKSPACE:
113 ws = ctx->node->sway_workspace;
114 break;
115 case N_OUTPUT:
116 output = ctx->node->sway_output;
117 ws = workspace_by_name(ctx->fallback_name);
118 if (!ws) {
119 sway_log(SWAY_DEBUG,
120 "Creating workspace %s for pid %d because it disappeared",
121 ctx->fallback_name, ctx->pid);
122 if (!output->enabled) {
123 sway_log(SWAY_DEBUG,
124 "Workspace output %s is disabled, trying another one",
125 output->wlr_output->name);
126 output = NULL;
127 }
128 ws = workspace_create(output, ctx->fallback_name);
129 }
130 break;
131 case N_ROOT:
132 ws = workspace_create(NULL, ctx->fallback_name);
133 break;
134 }
135
136 return ws;
137}
138
139static void ctx_handle_node_destroy(struct wl_listener *listener, void *data) {
140 struct launcher_ctx *ctx = wl_container_of(listener, ctx, node_destroy);
141 switch (ctx->node->type) {
142 case N_CONTAINER:
143 // Unimplemented
144 break;
145 case N_WORKSPACE:;
146 struct sway_workspace *ws = ctx->node->sway_workspace;
147 wl_list_remove(&ctx->node_destroy.link);
148 wl_list_init(&ctx->node_destroy.link);
149 // We want to save this ws name to recreate later, hopefully on the
150 // same output
151 free(ctx->fallback_name);
152 ctx->fallback_name = strdup(ws->name);
153 if (!ws->output || ws->output->node.destroying) {
154 // If the output is being destroyed it would be pointless to track
155 // If the output is being disabled, we'll find out if it's still
156 // disabled when we try to match it.
157 ctx->node = &root->node;
158 break;
159 }
160 ctx->node = &ws->output->node;
161 wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy);
162 break;
163 case N_OUTPUT:
164 wl_list_remove(&ctx->node_destroy.link);
165 wl_list_init(&ctx->node_destroy.link);
166 // We'll make the ws ctx->name somewhere else
167 ctx->node = &root->node;
168 break;
169 case N_ROOT:
170 // Unreachable
171 break;
172 }
173}
174
175static void token_handle_destroy(struct wl_listener *listener, void *data) {
176 struct launcher_ctx *ctx = wl_container_of(listener, ctx, token_destroy);
177 ctx->token = NULL;
178 launcher_ctx_destroy(ctx);
179}
180
181struct launcher_ctx *launcher_ctx_create(struct wlr_xdg_activation_token_v1 *token,
182 struct sway_node *node) {
183 struct launcher_ctx *ctx = calloc(1, sizeof(struct launcher_ctx));
184
185 const char *fallback_name = NULL;
186 struct sway_workspace *ws = NULL;
187 switch (node->type) {
188 case N_CONTAINER:
189 // Unimplemented
190 free(ctx);
191 return NULL;
192 case N_WORKSPACE:
193 ws = node->sway_workspace;
194 fallback_name = ws->name;
195 break;
196 case N_OUTPUT:;
197 struct sway_output *output = node->sway_output;
198 ws = output_get_active_workspace(output);
199 fallback_name = ws ? ws->name : NULL;
200 break;
201 case N_ROOT:
202 // Unimplemented
203 free(ctx);
204 return NULL;
205 }
206
207 if (!fallback_name) {
208 // TODO: implement a better fallback.
209 free(ctx);
210 return NULL;
211 }
212
213 ctx->fallback_name = strdup(fallback_name);
214 ctx->token = token;
215 ctx->node = node;
216
217 ctx->node_destroy.notify = ctx_handle_node_destroy;
218 wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy);
219
220 ctx->token_destroy.notify = token_handle_destroy;
221 wl_signal_add(&token->events.destroy, &ctx->token_destroy);
222
223 wl_list_init(&ctx->link);
224 wl_list_insert(&server.pending_launcher_ctxs, &ctx->link);
225
226 token->data = ctx;
227 return ctx;
228}
229
230// Creates a context with a new token for the internal launcher
231struct launcher_ctx *launcher_ctx_create_internal(void) {
232 struct sway_seat *seat = input_manager_current_seat();
233 struct sway_workspace *ws = seat_get_focused_workspace(seat);
234 if (!ws) {
235 sway_log(SWAY_DEBUG, "Failed to create launch context. No workspace.");
236 return NULL;
237 }
238
239 struct wlr_xdg_activation_token_v1 *token =
240 wlr_xdg_activation_token_v1_create(server.xdg_activation_v1);
241 token->seat = seat->wlr_seat;
242
243 struct launcher_ctx *ctx = launcher_ctx_create(token, &ws->node);
244 if (!ctx) {
245 wlr_xdg_activation_token_v1_destroy(token);
246 return NULL;
247 }
248
249 return ctx;
250}
251
252const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx) {
253 const char *token = wlr_xdg_activation_token_v1_get_name(ctx->token);
254 return token;
255}
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index d4ca4fb4..c71abce7 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -2,11 +2,11 @@
2#include <stdlib.h> 2#include <stdlib.h>
3#include <string.h> 3#include <string.h>
4#include <wayland-server-core.h> 4#include <wayland-server-core.h>
5#include <wlr/types/wlr_box.h>
6#include <wlr/types/wlr_layer_shell_v1.h> 5#include <wlr/types/wlr_layer_shell_v1.h>
7#include <wlr/types/wlr_output_damage.h>
8#include <wlr/types/wlr_output.h> 6#include <wlr/types/wlr_output.h>
7#include <wlr/types/wlr_subcompositor.h>
9#include "log.h" 8#include "log.h"
9#include "sway/scene_descriptor.h"
10#include "sway/desktop/transaction.h" 10#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 11#include "sway/input/cursor.h"
12#include "sway/input/input-manager.h" 12#include "sway/input/input-manager.h"
@@ -16,156 +16,57 @@
16#include "sway/server.h" 16#include "sway/server.h"
17#include "sway/tree/arrange.h" 17#include "sway/tree/arrange.h"
18#include "sway/tree/workspace.h" 18#include "sway/tree/workspace.h"
19#include <wlr/types/wlr_scene.h>
20
21struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface(
22 struct wlr_surface *surface) {
23 struct wlr_layer_surface_v1 *layer;
24 do {
25 if (!surface) {
26 return NULL;
27 }
28 // Topmost layer surface
29 if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface))) {
30 return layer;
31 }
32 // Layer subsurface
33 if (wlr_subsurface_try_from_wlr_surface(surface)) {
34 surface = wlr_surface_get_root_surface(surface);
35 continue;
36 }
19 37
20static void apply_exclusive(struct wlr_box *usable_area, 38 // Layer surface popup
21 uint32_t anchor, int32_t exclusive, 39 struct wlr_xdg_surface *xdg_surface = NULL;
22 int32_t margin_top, int32_t margin_right, 40 if ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface)) &&
23 int32_t margin_bottom, int32_t margin_left) { 41 xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP && xdg_surface->popup != NULL) {
24 if (exclusive <= 0) { 42 if (!xdg_surface->popup->parent) {
25 return; 43 return NULL;
26 }
27 struct {
28 uint32_t singular_anchor;
29 uint32_t anchor_triplet;
30 int *positive_axis;
31 int *negative_axis;
32 int margin;
33 } edges[] = {
34 // Top
35 {
36 .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
37 .anchor_triplet =
38 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
39 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
40 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
41 .positive_axis = &usable_area->y,
42 .negative_axis = &usable_area->height,
43 .margin = margin_top,
44 },
45 // Bottom
46 {
47 .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
48 .anchor_triplet =
49 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
50 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
51 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
52 .positive_axis = NULL,
53 .negative_axis = &usable_area->height,
54 .margin = margin_bottom,
55 },
56 // Left
57 {
58 .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT,
59 .anchor_triplet =
60 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
61 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
62 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
63 .positive_axis = &usable_area->x,
64 .negative_axis = &usable_area->width,
65 .margin = margin_left,
66 },
67 // Right
68 {
69 .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,
70 .anchor_triplet =
71 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT |
72 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
73 ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
74 .positive_axis = NULL,
75 .negative_axis = &usable_area->width,
76 .margin = margin_right,
77 },
78 };
79 for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {
80 if ((anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet)
81 && exclusive + edges[i].margin > 0) {
82 if (edges[i].positive_axis) {
83 *edges[i].positive_axis += exclusive + edges[i].margin;
84 } 44 }
85 if (edges[i].negative_axis) { 45 surface = wlr_surface_get_root_surface(xdg_surface->popup->parent);
86 *edges[i].negative_axis -= exclusive + edges[i].margin; 46 continue;
87 }
88 break;
89 } 47 }
90 } 48
49 // Return early if the surface is not a layer/xdg_popup/sub surface
50 return NULL;
51 } while (true);
91} 52}
92 53
93static void arrange_layer(struct sway_output *output, struct wl_list *list, 54static void arrange_surface(struct sway_output *output, const struct wlr_box *full_area,
94 struct wlr_box *usable_area, bool exclusive) { 55 struct wlr_box *usable_area, struct wlr_scene_tree *tree) {
95 struct sway_layer_surface *sway_layer; 56 struct wlr_scene_node *node;
96 struct wlr_box full_area = { 0 }; 57 wl_list_for_each(node, &tree->children, link) {
97 wlr_output_effective_resolution(output->wlr_output, 58 struct sway_layer_surface *surface = scene_descriptor_try_get(node,
98 &full_area.width, &full_area.height); 59 SWAY_SCENE_DESC_LAYER_SHELL);
99 wl_list_for_each(sway_layer, list, link) { 60 // surface could be null during destruction
100 struct wlr_layer_surface_v1 *layer = sway_layer->layer_surface; 61 if (!surface) {
101 struct wlr_layer_surface_v1_state *state = &layer->current;
102 if (exclusive != (state->exclusive_zone > 0)) {
103 continue; 62 continue;
104 } 63 }
105 struct wlr_box bounds; 64
106 if (state->exclusive_zone == -1) { 65 if (!surface->scene->layer_surface->initialized) {
107 bounds = full_area;
108 } else {
109 bounds = *usable_area;
110 }
111 struct wlr_box box = {
112 .width = state->desired_width,
113 .height = state->desired_height
114 };
115 // Horizontal axis
116 const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
117 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
118 if ((state->anchor & both_horiz) && box.width == 0) {
119 box.x = bounds.x;
120 box.width = bounds.width;
121 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
122 box.x = bounds.x;
123 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
124 box.x = bounds.x + (bounds.width - box.width);
125 } else {
126 box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
127 }
128 // Vertical axis
129 const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
130 | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
131 if ((state->anchor & both_vert) && box.height == 0) {
132 box.y = bounds.y;
133 box.height = bounds.height;
134 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
135 box.y = bounds.y;
136 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
137 box.y = bounds.y + (bounds.height - box.height);
138 } else {
139 box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
140 }
141 // Margin
142 if ((state->anchor & both_horiz) == both_horiz) {
143 box.x += state->margin.left;
144 box.width -= state->margin.left + state->margin.right;
145 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) {
146 box.x += state->margin.left;
147 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) {
148 box.x -= state->margin.right;
149 }
150 if ((state->anchor & both_vert) == both_vert) {
151 box.y += state->margin.top;
152 box.height -= state->margin.top + state->margin.bottom;
153 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) {
154 box.y += state->margin.top;
155 } else if ((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) {
156 box.y -= state->margin.bottom;
157 }
158 if (box.width < 0 || box.height < 0) {
159 // TODO: Bubble up a protocol error?
160 wlr_layer_surface_v1_close(layer);
161 continue; 66 continue;
162 } 67 }
163 // Apply 68
164 sway_layer->geo = box; 69 wlr_scene_layer_surface_v1_configure(surface->scene, full_area, usable_area);
165 apply_exclusive(usable_area, state->anchor, state->exclusive_zone,
166 state->margin.top, state->margin.right,
167 state->margin.bottom, state->margin.left);
168 wlr_layer_surface_v1_configure(layer, box.width, box.height);
169 } 70 }
170} 71}
171 72
@@ -173,81 +74,81 @@ void arrange_layers(struct sway_output *output) {
173 struct wlr_box usable_area = { 0 }; 74 struct wlr_box usable_area = { 0 };
174 wlr_output_effective_resolution(output->wlr_output, 75 wlr_output_effective_resolution(output->wlr_output,
175 &usable_area.width, &usable_area.height); 76 &usable_area.width, &usable_area.height);
77 const struct wlr_box full_area = usable_area;
78
79 arrange_surface(output, &full_area, &usable_area, output->layers.shell_background);
80 arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom);
81 arrange_surface(output, &full_area, &usable_area, output->layers.shell_top);
82 arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay);
176 83
177 // Arrange exclusive surfaces from top->bottom 84 if (!wlr_box_equal(&usable_area, &output->usable_area)) {
178 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
179 &usable_area, true);
180 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
181 &usable_area, true);
182 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
183 &usable_area, true);
184 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
185 &usable_area, true);
186
187 if (memcmp(&usable_area, &output->usable_area,
188 sizeof(struct wlr_box)) != 0) {
189 sway_log(SWAY_DEBUG, "Usable area changed, rearranging output"); 85 sway_log(SWAY_DEBUG, "Usable area changed, rearranging output");
190 memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); 86 output->usable_area = usable_area;
191 arrange_output(output); 87 arrange_output(output);
192 } 88 }
89}
193 90
194 // Arrange non-exlusive surfaces from top->bottom 91static struct wlr_scene_tree *sway_layer_get_scene(struct sway_output *output,
195 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], 92 enum zwlr_layer_shell_v1_layer type) {
196 &usable_area, false); 93 switch (type) {
197 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], 94 case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND:
198 &usable_area, false); 95 return output->layers.shell_background;
199 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], 96 case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM:
200 &usable_area, false); 97 return output->layers.shell_bottom;
201 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], 98 case ZWLR_LAYER_SHELL_V1_LAYER_TOP:
202 &usable_area, false); 99 return output->layers.shell_top;
203 100 case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY:
204 // Find topmost keyboard interactive layer, if such a layer exists 101 return output->layers.shell_overlay;
205 uint32_t layers_above_shell[] = {
206 ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY,
207 ZWLR_LAYER_SHELL_V1_LAYER_TOP,
208 };
209 size_t nlayers = sizeof(layers_above_shell) / sizeof(layers_above_shell[0]);
210 struct sway_layer_surface *layer, *topmost = NULL;
211 for (size_t i = 0; i < nlayers; ++i) {
212 wl_list_for_each_reverse(layer,
213 &output->layers[layers_above_shell[i]], link) {
214 if (layer->layer_surface->current.keyboard_interactive &&
215 layer->layer_surface->mapped) {
216 topmost = layer;
217 break;
218 }
219 }
220 if (topmost != NULL) {
221 break;
222 }
223 } 102 }
224 103
225 struct sway_seat *seat; 104 sway_assert(false, "unreachable");
226 wl_list_for_each(seat, &server.input->seats, link) { 105 return NULL;
227 if (topmost != NULL) { 106}
228 seat_set_focus_layer(seat, topmost->layer_surface); 107
229 } else if (seat->focused_layer && 108static struct sway_layer_surface *sway_layer_surface_create(
230 !seat->focused_layer->current.keyboard_interactive) { 109 struct wlr_scene_layer_surface_v1 *scene) {
231 seat_set_focus_layer(seat, NULL); 110 struct sway_layer_surface *surface = calloc(1, sizeof(*surface));
232 } 111 if (!surface) {
112 sway_log(SWAY_ERROR, "Could not allocate a scene_layer surface");
113 return NULL;
114 }
115
116 struct wlr_scene_tree *popups = wlr_scene_tree_create(root->layers.popup);
117 if (!popups) {
118 sway_log(SWAY_ERROR, "Could not allocate a scene_layer popup node");
119 free(surface);
120 return NULL;
233 } 121 }
122
123 surface->tree = scene->tree;
124 surface->scene = scene;
125 surface->layer_surface = scene->layer_surface;
126 surface->popups = popups;
127
128 return surface;
234} 129}
235 130
236static struct sway_layer_surface *find_mapped_layer_by_client( 131static struct sway_layer_surface *find_mapped_layer_by_client(
237 struct wl_client *client, struct wlr_output *ignore_output) { 132 struct wl_client *client, struct sway_output *ignore_output) {
238 for (int i = 0; i < root->outputs->length; ++i) { 133 for (int i = 0; i < root->outputs->length; ++i) {
239 struct sway_output *output = root->outputs->items[i]; 134 struct sway_output *output = root->outputs->items[i];
240 if (output->wlr_output == ignore_output) { 135 if (output == ignore_output) {
241 continue; 136 continue;
242 } 137 }
243 // For now we'll only check the overlay layer 138 // For now we'll only check the overlay layer
244 struct sway_layer_surface *lsurface; 139 struct wlr_scene_node *node;
245 wl_list_for_each(lsurface, 140 wl_list_for_each (node, &output->layers.shell_overlay->children, link) {
246 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], link) { 141 struct sway_layer_surface *surface = scene_descriptor_try_get(node,
247 struct wl_resource *resource = lsurface->layer_surface->resource; 142 SWAY_SCENE_DESC_LAYER_SHELL);
143 if (!surface) {
144 continue;
145 }
146
147 struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface;
148 struct wl_resource *resource = layer_surface->resource;
248 if (wl_resource_get_client(resource) == client 149 if (wl_resource_get_client(resource) == client
249 && lsurface->layer_surface->mapped) { 150 && layer_surface->surface->mapped) {
250 return lsurface; 151 return surface;
251 } 152 }
252 } 153 }
253 } 154 }
@@ -255,280 +156,144 @@ static struct sway_layer_surface *find_mapped_layer_by_client(
255} 156}
256 157
257static void handle_output_destroy(struct wl_listener *listener, void *data) { 158static void handle_output_destroy(struct wl_listener *listener, void *data) {
258 struct sway_layer_surface *sway_layer = 159 struct sway_layer_surface *layer =
259 wl_container_of(listener, sway_layer, output_destroy); 160 wl_container_of(listener, layer, output_destroy);
260 // Determine if this layer is being used by an exclusive client. If it is,
261 // try and find another layer owned by this client to pass focus to.
262 struct sway_seat *seat = input_manager_get_default_seat();
263 struct wl_client *client =
264 wl_resource_get_client(sway_layer->layer_surface->resource);
265 bool set_focus = seat->exclusive_client == client;
266
267 wl_list_remove(&sway_layer->output_destroy.link);
268 wl_list_remove(&sway_layer->link);
269 wl_list_init(&sway_layer->link);
270
271 if (set_focus) {
272 struct sway_layer_surface *layer =
273 find_mapped_layer_by_client(client, sway_layer->layer_surface->output);
274 if (layer) {
275 seat_set_focus_layer(seat, layer->layer_surface);
276 }
277 }
278 161
279 sway_layer->layer_surface->output = NULL; 162 layer->output = NULL;
280 wlr_layer_surface_v1_close(sway_layer->layer_surface); 163 wlr_scene_node_destroy(&layer->scene->tree->node);
281} 164}
282 165
283static void handle_surface_commit(struct wl_listener *listener, void *data) { 166static void handle_node_destroy(struct wl_listener *listener, void *data) {
284 struct sway_layer_surface *layer = 167 struct sway_layer_surface *layer =
285 wl_container_of(listener, layer, surface_commit); 168 wl_container_of(listener, layer, node_destroy);
286 struct wlr_layer_surface_v1 *layer_surface = layer->layer_surface;
287 struct wlr_output *wlr_output = layer_surface->output;
288 if (wlr_output == NULL) {
289 return;
290 }
291
292 struct sway_output *output = wlr_output->data;
293 struct wlr_box old_geo = layer->geo;
294 arrange_layers(output);
295
296 bool geo_changed =
297 memcmp(&old_geo, &layer->geo, sizeof(struct wlr_box)) != 0;
298 bool layer_changed = layer->layer != layer_surface->current.layer;
299 if (layer_changed) {
300 wl_list_remove(&layer->link);
301 wl_list_insert(&output->layers[layer_surface->current.layer],
302 &layer->link);
303 layer->layer = layer_surface->current.layer;
304 }
305 if (geo_changed || layer_changed) {
306 output_damage_surface(output, old_geo.x, old_geo.y,
307 layer_surface->surface, true);
308 output_damage_surface(output, layer->geo.x, layer->geo.y,
309 layer_surface->surface, true);
310 } else {
311 output_damage_surface(output, layer->geo.x, layer->geo.y,
312 layer_surface->surface, false);
313 }
314 169
315 transaction_commit_dirty(); 170 // destroy the scene descriptor straight away if it exists, otherwise
316} 171 // we will try to reflow still considering the destroyed node.
172 scene_descriptor_destroy(&layer->tree->node, SWAY_SCENE_DESC_LAYER_SHELL);
317 173
318static void unmap(struct sway_layer_surface *sway_layer) { 174 // Determine if this layer is being used by an exclusive client. If it is,
319 struct sway_seat *seat; 175 // try and find another layer owned by this client to pass focus to.
320 wl_list_for_each(seat, &server.input->seats, link) { 176 struct sway_seat *seat = input_manager_get_default_seat();
321 if (seat->focused_layer == sway_layer->layer_surface) { 177 struct wl_client *client =
322 seat_set_focus_layer(seat, NULL); 178 wl_resource_get_client(layer->layer_surface->resource);
179 if (!server.session_lock.lock) {
180 struct sway_layer_surface *consider_layer =
181 find_mapped_layer_by_client(client, layer->output);
182 if (consider_layer) {
183 seat_set_focus_layer(seat, consider_layer->layer_surface);
323 } 184 }
324 } 185 }
325 186
326 cursor_rebase_all(); 187 if (layer->output) {
327 188 arrange_layers(layer->output);
328 struct wlr_output *wlr_output = sway_layer->layer_surface->output; 189 transaction_commit_dirty();
329 if (wlr_output == NULL) {
330 return;
331 } 190 }
332 struct sway_output *output = wlr_output->data;
333 if (output == NULL) {
334 return;
335 }
336 output_damage_surface(output, sway_layer->geo.x, sway_layer->geo.y,
337 sway_layer->layer_surface->surface, true);
338}
339 191
340static void handle_destroy(struct wl_listener *listener, void *data) { 192 wlr_scene_node_destroy(&layer->popups->node);
341 struct sway_layer_surface *sway_layer =
342 wl_container_of(listener, sway_layer, destroy);
343 sway_log(SWAY_DEBUG, "Layer surface destroyed (%s)",
344 sway_layer->layer_surface->namespace);
345 if (sway_layer->layer_surface->mapped) {
346 unmap(sway_layer);
347 }
348 wl_list_remove(&sway_layer->link);
349 wl_list_remove(&sway_layer->destroy.link);
350 wl_list_remove(&sway_layer->map.link);
351 wl_list_remove(&sway_layer->unmap.link);
352 wl_list_remove(&sway_layer->surface_commit.link);
353 wl_list_remove(&sway_layer->new_popup.link);
354 wl_list_remove(&sway_layer->new_subsurface.link);
355 if (sway_layer->layer_surface->output != NULL) {
356 struct sway_output *output = sway_layer->layer_surface->output->data;
357 if (output != NULL) {
358 arrange_layers(output);
359 transaction_commit_dirty();
360 }
361 wl_list_remove(&sway_layer->output_destroy.link);
362 sway_layer->layer_surface->output = NULL;
363 }
364 free(sway_layer);
365}
366 193
367static void handle_map(struct wl_listener *listener, void *data) { 194 wl_list_remove(&layer->map.link);
368 struct sway_layer_surface *sway_layer = wl_container_of(listener, 195 wl_list_remove(&layer->unmap.link);
369 sway_layer, map); 196 wl_list_remove(&layer->surface_commit.link);
370 struct sway_output *output = sway_layer->layer_surface->output->data; 197 wl_list_remove(&layer->node_destroy.link);
371 output_damage_surface(output, sway_layer->geo.x, sway_layer->geo.y, 198 wl_list_remove(&layer->output_destroy.link);
372 sway_layer->layer_surface->surface, true);
373 wlr_surface_send_enter(sway_layer->layer_surface->surface,
374 sway_layer->layer_surface->output);
375 cursor_rebase_all();
376}
377 199
378static void handle_unmap(struct wl_listener *listener, void *data) { 200 free(layer);
379 struct sway_layer_surface *sway_layer = wl_container_of(
380 listener, sway_layer, unmap);
381 unmap(sway_layer);
382} 201}
383 202
384static void subsurface_damage(struct sway_layer_subsurface *subsurface, 203static void handle_surface_commit(struct wl_listener *listener, void *data) {
385 bool whole) { 204 struct sway_layer_surface *surface =
386 struct sway_layer_surface *layer = subsurface->layer_surface; 205 wl_container_of(listener, surface, surface_commit);
387 struct wlr_output *wlr_output = layer->layer_surface->output; 206
388 if (!wlr_output) { 207 struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface;
208 if (!layer_surface->initialized) {
389 return; 209 return;
390 } 210 }
391 struct sway_output *output = wlr_output->data;
392 int ox = subsurface->wlr_subsurface->current.x + layer->geo.x;
393 int oy = subsurface->wlr_subsurface->current.y + layer->geo.y;
394 output_damage_surface(
395 output, ox, oy, subsurface->wlr_subsurface->surface, whole);
396}
397
398static void subsurface_handle_unmap(struct wl_listener *listener, void *data) {
399 struct sway_layer_subsurface *subsurface =
400 wl_container_of(listener, subsurface, unmap);
401 subsurface_damage(subsurface, true);
402}
403 211
404static void subsurface_handle_map(struct wl_listener *listener, void *data) { 212 uint32_t committed = layer_surface->current.committed;
405 struct sway_layer_subsurface *subsurface = 213 if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) {
406 wl_container_of(listener, subsurface, map); 214 enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer;
407 subsurface_damage(subsurface, true); 215 struct wlr_scene_tree *output_layer = sway_layer_get_scene(
408} 216 surface->output, layer_type);
409 217 wlr_scene_node_reparent(&surface->scene->tree->node, output_layer);
410static void subsurface_handle_commit(struct wl_listener *listener, void *data) {
411 struct sway_layer_subsurface *subsurface =
412 wl_container_of(listener, subsurface, commit);
413 subsurface_damage(subsurface, false);
414}
415
416static void subsurface_handle_destroy(struct wl_listener *listener,
417 void *data) {
418 struct sway_layer_subsurface *subsurface =
419 wl_container_of(listener, subsurface, destroy);
420
421 wl_list_remove(&subsurface->map.link);
422 wl_list_remove(&subsurface->unmap.link);
423 wl_list_remove(&subsurface->destroy.link);
424 wl_list_remove(&subsurface->commit.link);
425 free(subsurface);
426}
427
428static struct sway_layer_subsurface *create_subsurface(
429 struct wlr_subsurface *wlr_subsurface,
430 struct sway_layer_surface *layer_surface) {
431 struct sway_layer_subsurface *subsurface =
432 calloc(1, sizeof(struct sway_layer_surface));
433 if (subsurface == NULL) {
434 return NULL;
435 } 218 }
436 219
437 subsurface->wlr_subsurface = wlr_subsurface; 220 if (layer_surface->initial_commit || committed || layer_surface->surface->mapped != surface->mapped) {
438 subsurface->layer_surface = layer_surface; 221 surface->mapped = layer_surface->surface->mapped;
439 222 arrange_layers(surface->output);
440 subsurface->map.notify = subsurface_handle_map; 223 transaction_commit_dirty();
441 wl_signal_add(&wlr_subsurface->events.map, &subsurface->map);
442 subsurface->unmap.notify = subsurface_handle_unmap;
443 wl_signal_add(&wlr_subsurface->events.unmap, &subsurface->unmap);
444 subsurface->destroy.notify = subsurface_handle_destroy;
445 wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
446 subsurface->commit.notify = subsurface_handle_commit;
447 wl_signal_add(&wlr_subsurface->surface->events.commit, &subsurface->commit);
448
449 return subsurface;
450}
451
452static void handle_new_subsurface(struct wl_listener *listener, void *data) {
453 struct sway_layer_surface *sway_layer_surface =
454 wl_container_of(listener, sway_layer_surface, new_subsurface);
455 struct wlr_subsurface *wlr_subsurface = data;
456 create_subsurface(wlr_subsurface, sway_layer_surface);
457}
458
459
460static struct sway_layer_surface *popup_get_layer(
461 struct sway_layer_popup *popup) {
462 while (popup->parent_type == LAYER_PARENT_POPUP) {
463 popup = popup->parent_popup;
464 } 224 }
465 return popup->parent_layer; 225
226 int lx, ly;
227 wlr_scene_node_coords(&surface->scene->tree->node, &lx, &ly);
228 wlr_scene_node_set_position(&surface->popups->node, lx, ly);
466} 229}
467 230
468static void popup_damage(struct sway_layer_popup *layer_popup, bool whole) { 231static void handle_map(struct wl_listener *listener, void *data) {
469 struct wlr_xdg_popup *popup = layer_popup->wlr_popup; 232 struct sway_layer_surface *surface = wl_container_of(listener,
470 struct wlr_surface *surface = popup->base->surface; 233 surface, map);
471 int popup_sx = popup->geometry.x - popup->base->geometry.x; 234
472 int popup_sy = popup->geometry.y - popup->base->geometry.y; 235 struct wlr_layer_surface_v1 *layer_surface =
473 int ox = popup_sx, oy = popup_sy; 236 surface->scene->layer_surface;
474 struct sway_layer_surface *layer; 237
475 while (true) { 238 // focus on new surface
476 if (layer_popup->parent_type == LAYER_PARENT_POPUP) { 239 if (layer_surface->current.keyboard_interactive &&
477 layer_popup = layer_popup->parent_popup; 240 (layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ||
478 ox += layer_popup->wlr_popup->geometry.x; 241 layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP)) {
479 oy += layer_popup->wlr_popup->geometry.y; 242 struct sway_seat *seat;
480 } else { 243 wl_list_for_each(seat, &server.input->seats, link) {
481 layer = layer_popup->parent_layer; 244 // but only if the currently focused layer has a lower precedence
482 ox += layer->geo.x; 245 if (!seat->focused_layer ||
483 oy += layer->geo.y; 246 seat->focused_layer->current.layer >= layer_surface->current.layer) {
484 break; 247 seat_set_focus_layer(seat, layer_surface);
248 }
485 } 249 }
250 arrange_layers(surface->output);
486 } 251 }
487 struct wlr_output *wlr_output = layer->layer_surface->output;
488 struct sway_output *output = wlr_output->data;
489 output_damage_surface(output, ox, oy, surface, whole);
490}
491 252
492static void popup_handle_map(struct wl_listener *listener, void *data) { 253 cursor_rebase_all();
493 struct sway_layer_popup *popup = wl_container_of(listener, popup, map);
494 struct sway_layer_surface *layer = popup_get_layer(popup);
495 struct wlr_output *wlr_output = layer->layer_surface->output;
496 wlr_surface_send_enter(popup->wlr_popup->base->surface, wlr_output);
497 popup_damage(popup, true);
498} 254}
499 255
500static void popup_handle_unmap(struct wl_listener *listener, void *data) { 256static void handle_unmap(struct wl_listener *listener, void *data) {
501 struct sway_layer_popup *popup = wl_container_of(listener, popup, unmap); 257 struct sway_layer_surface *surface = wl_container_of(
502 popup_damage(popup, true); 258 listener, surface, unmap);
503} 259 struct sway_seat *seat;
260 wl_list_for_each(seat, &server.input->seats, link) {
261 if (seat->focused_layer == surface->layer_surface) {
262 seat_set_focus_layer(seat, NULL);
263 }
264 }
504 265
505static void popup_handle_commit(struct wl_listener *listener, void *data) { 266 cursor_rebase_all();
506 struct sway_layer_popup *popup = wl_container_of(listener, popup, commit);
507 popup_damage(popup, false);
508} 267}
509 268
510static void popup_handle_destroy(struct wl_listener *listener, void *data) { 269static void popup_handle_destroy(struct wl_listener *listener, void *data) {
511 struct sway_layer_popup *popup = 270 struct sway_layer_popup *popup =
512 wl_container_of(listener, popup, destroy); 271 wl_container_of(listener, popup, destroy);
513 272
514 wl_list_remove(&popup->map.link);
515 wl_list_remove(&popup->unmap.link);
516 wl_list_remove(&popup->destroy.link); 273 wl_list_remove(&popup->destroy.link);
274 wl_list_remove(&popup->new_popup.link);
517 wl_list_remove(&popup->commit.link); 275 wl_list_remove(&popup->commit.link);
518 free(popup); 276 free(popup);
519} 277}
520 278
521static void popup_unconstrain(struct sway_layer_popup *popup) { 279static void popup_unconstrain(struct sway_layer_popup *popup) {
522 struct sway_layer_surface *layer = popup_get_layer(popup);
523 struct wlr_xdg_popup *wlr_popup = popup->wlr_popup; 280 struct wlr_xdg_popup *wlr_popup = popup->wlr_popup;
281 struct sway_output *output = popup->toplevel->output;
524 282
525 struct sway_output *output = layer->layer_surface->output->data; 283 // if a client tries to create a popup while we are in the process of destroying
284 // its output, don't crash.
285 if (!output) {
286 return;
287 }
288
289 int lx, ly;
290 wlr_scene_node_coords(&popup->toplevel->scene->tree->node, &lx, &ly);
526 291
527 // the output box expressed in the coordinate system of the toplevel parent 292 // the output box expressed in the coordinate system of the toplevel parent
528 // of the popup 293 // of the popup
529 struct wlr_box output_toplevel_sx_box = { 294 struct wlr_box output_toplevel_sx_box = {
530 .x = -layer->geo.x, 295 .x = output->lx - lx,
531 .y = -layer->geo.y, 296 .y = output->ly - ly,
532 .width = output->width, 297 .width = output->width,
533 .height = output->height, 298 .height = output->height,
534 }; 299 };
@@ -536,32 +301,38 @@ static void popup_unconstrain(struct sway_layer_popup *popup) {
536 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); 301 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box);
537} 302}
538 303
304static void popup_handle_commit(struct wl_listener *listener, void *data) {
305 struct sway_layer_popup *popup = wl_container_of(listener, popup, commit);
306 if (popup->wlr_popup->base->initial_commit) {
307 popup_unconstrain(popup);
308 }
309}
310
539static void popup_handle_new_popup(struct wl_listener *listener, void *data); 311static void popup_handle_new_popup(struct wl_listener *listener, void *data);
540 312
541static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup, 313static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup,
542 enum layer_parent parent_type, void *parent) { 314 struct sway_layer_surface *toplevel, struct wlr_scene_tree *parent) {
543 struct sway_layer_popup *popup = 315 struct sway_layer_popup *popup = calloc(1, sizeof(*popup));
544 calloc(1, sizeof(struct sway_layer_popup));
545 if (popup == NULL) { 316 if (popup == NULL) {
546 return NULL; 317 return NULL;
547 } 318 }
548 319
320 popup->toplevel = toplevel;
549 popup->wlr_popup = wlr_popup; 321 popup->wlr_popup = wlr_popup;
550 popup->parent_type = parent_type; 322 popup->scene = wlr_scene_xdg_surface_create(parent,
551 popup->parent_layer = parent; 323 wlr_popup->base);
324
325 if (!popup->scene) {
326 free(popup);
327 return NULL;
328 }
552 329
553 popup->map.notify = popup_handle_map;
554 wl_signal_add(&wlr_popup->base->events.map, &popup->map);
555 popup->unmap.notify = popup_handle_unmap;
556 wl_signal_add(&wlr_popup->base->events.unmap, &popup->unmap);
557 popup->destroy.notify = popup_handle_destroy; 330 popup->destroy.notify = popup_handle_destroy;
558 wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); 331 wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy);
559 popup->commit.notify = popup_handle_commit;
560 wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
561 popup->new_popup.notify = popup_handle_new_popup; 332 popup->new_popup.notify = popup_handle_new_popup;
562 wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); 333 wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
563 334 popup->commit.notify = popup_handle_commit;
564 popup_unconstrain(popup); 335 wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
565 336
566 return popup; 337 return popup;
567} 338}
@@ -570,19 +341,14 @@ static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
570 struct sway_layer_popup *sway_layer_popup = 341 struct sway_layer_popup *sway_layer_popup =
571 wl_container_of(listener, sway_layer_popup, new_popup); 342 wl_container_of(listener, sway_layer_popup, new_popup);
572 struct wlr_xdg_popup *wlr_popup = data; 343 struct wlr_xdg_popup *wlr_popup = data;
573 create_popup(wlr_popup, LAYER_PARENT_POPUP, sway_layer_popup); 344 create_popup(wlr_popup, sway_layer_popup->toplevel, sway_layer_popup->scene);
574} 345}
575 346
576static void handle_new_popup(struct wl_listener *listener, void *data) { 347static void handle_new_popup(struct wl_listener *listener, void *data) {
577 struct sway_layer_surface *sway_layer_surface = 348 struct sway_layer_surface *sway_layer_surface =
578 wl_container_of(listener, sway_layer_surface, new_popup); 349 wl_container_of(listener, sway_layer_surface, new_popup);
579 struct wlr_xdg_popup *wlr_popup = data; 350 struct wlr_xdg_popup *wlr_popup = data;
580 create_popup(wlr_popup, LAYER_PARENT_LAYER, sway_layer_surface); 351 create_popup(wlr_popup, sway_layer_surface, sway_layer_surface->popups);
581}
582
583struct sway_layer_surface *layer_from_wlr_layer_surface_v1(
584 struct wlr_layer_surface_v1 *layer_surface) {
585 return layer_surface->data;
586} 352}
587 353
588void handle_layer_shell_surface(struct wl_listener *listener, void *data) { 354void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
@@ -590,14 +356,14 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
590 sway_log(SWAY_DEBUG, "new layer surface: namespace %s layer %d anchor %" PRIu32 356 sway_log(SWAY_DEBUG, "new layer surface: namespace %s layer %d anchor %" PRIu32
591 " size %" PRIu32 "x%" PRIu32 " margin %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",", 357 " size %" PRIu32 "x%" PRIu32 " margin %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",",
592 layer_surface->namespace, 358 layer_surface->namespace,
593 layer_surface->client_pending.layer, 359 layer_surface->pending.layer,
594 layer_surface->client_pending.anchor, 360 layer_surface->pending.anchor,
595 layer_surface->client_pending.desired_width, 361 layer_surface->pending.desired_width,
596 layer_surface->client_pending.desired_height, 362 layer_surface->pending.desired_height,
597 layer_surface->client_pending.margin.top, 363 layer_surface->pending.margin.top,
598 layer_surface->client_pending.margin.right, 364 layer_surface->pending.margin.right,
599 layer_surface->client_pending.margin.bottom, 365 layer_surface->pending.margin.bottom,
600 layer_surface->client_pending.margin.left); 366 layer_surface->pending.margin.left);
601 367
602 if (!layer_surface->output) { 368 if (!layer_surface->output) {
603 // Assign last active output 369 // Assign last active output
@@ -609,12 +375,12 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
609 output = ws->output; 375 output = ws->output;
610 } 376 }
611 } 377 }
612 if (!output || output == root->noop_output) { 378 if (!output || output == root->fallback_output) {
613 if (!root->outputs->length) { 379 if (!root->outputs->length) {
614 sway_log(SWAY_ERROR, 380 sway_log(SWAY_ERROR,
615 "no output to auto-assign layer surface '%s' to", 381 "no output to auto-assign layer surface '%s' to",
616 layer_surface->namespace); 382 layer_surface->namespace);
617 wlr_layer_surface_v1_close(layer_surface); 383 wlr_layer_surface_v1_destroy(layer_surface);
618 return; 384 return;
619 } 385 }
620 output = root->outputs->items[0]; 386 output = root->outputs->items[0];
@@ -622,42 +388,51 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
622 layer_surface->output = output->wlr_output; 388 layer_surface->output = output->wlr_output;
623 } 389 }
624 390
625 struct sway_layer_surface *sway_layer = 391 struct sway_output *output = layer_surface->output->data;
626 calloc(1, sizeof(struct sway_layer_surface)); 392
627 if (!sway_layer) { 393 enum zwlr_layer_shell_v1_layer layer_type = layer_surface->pending.layer;
394 struct wlr_scene_tree *output_layer = sway_layer_get_scene(
395 output, layer_type);
396 struct wlr_scene_layer_surface_v1 *scene_surface =
397 wlr_scene_layer_surface_v1_create(output_layer, layer_surface);
398 if (!scene_surface) {
399 sway_log(SWAY_ERROR, "Could not allocate a layer_surface_v1");
628 return; 400 return;
629 } 401 }
630 402
631 sway_layer->surface_commit.notify = handle_surface_commit; 403 struct sway_layer_surface *surface =
632 wl_signal_add(&layer_surface->surface->events.commit, 404 sway_layer_surface_create(scene_surface);
633 &sway_layer->surface_commit); 405 if (!surface) {
634 406 wlr_layer_surface_v1_destroy(layer_surface);
635 sway_layer->destroy.notify = handle_destroy;
636 wl_signal_add(&layer_surface->events.destroy, &sway_layer->destroy);
637 sway_layer->map.notify = handle_map;
638 wl_signal_add(&layer_surface->events.map, &sway_layer->map);
639 sway_layer->unmap.notify = handle_unmap;
640 wl_signal_add(&layer_surface->events.unmap, &sway_layer->unmap);
641 sway_layer->new_popup.notify = handle_new_popup;
642 wl_signal_add(&layer_surface->events.new_popup, &sway_layer->new_popup);
643 sway_layer->new_subsurface.notify = handle_new_subsurface;
644 wl_signal_add(&layer_surface->surface->events.new_subsurface,
645 &sway_layer->new_subsurface);
646
647 sway_layer->layer_surface = layer_surface;
648 layer_surface->data = sway_layer;
649 407
650 struct sway_output *output = layer_surface->output->data; 408 sway_log(SWAY_ERROR, "Could not allocate a sway_layer_surface");
651 sway_layer->output_destroy.notify = handle_output_destroy; 409 return;
652 wl_signal_add(&output->events.destroy, &sway_layer->output_destroy); 410 }
653 411
654 wl_list_insert(&output->layers[layer_surface->client_pending.layer], 412 if (!scene_descriptor_assign(&scene_surface->tree->node,
655 &sway_layer->link); 413 SWAY_SCENE_DESC_LAYER_SHELL, surface)) {
656 414 sway_log(SWAY_ERROR, "Failed to allocate a layer surface descriptor");
657 // Temporarily set the layer's current state to client_pending 415 // destroying the layer_surface will also destroy its corresponding
658 // So that we can easily arrange it 416 // scene node
659 struct wlr_layer_surface_v1_state old_state = layer_surface->current; 417 wlr_layer_surface_v1_destroy(layer_surface);
660 layer_surface->current = layer_surface->client_pending; 418 return;
661 arrange_layers(output); 419 }
662 layer_surface->current = old_state; 420
421 surface->output = output;
422
423 surface->surface_commit.notify = handle_surface_commit;
424 wl_signal_add(&layer_surface->surface->events.commit,
425 &surface->surface_commit);
426 surface->map.notify = handle_map;
427 wl_signal_add(&layer_surface->surface->events.map, &surface->map);
428 surface->unmap.notify = handle_unmap;
429 wl_signal_add(&layer_surface->surface->events.unmap, &surface->unmap);
430 surface->new_popup.notify = handle_new_popup;
431 wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup);
432
433 surface->output_destroy.notify = handle_output_destroy;
434 wl_signal_add(&output->events.disable, &surface->output_destroy);
435
436 surface->node_destroy.notify = handle_node_destroy;
437 wl_signal_add(&scene_surface->tree->node.events.destroy, &surface->node_destroy);
663} 438}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 5edc8f96..9c4baafd 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -4,39 +4,57 @@
4#include <strings.h> 4#include <strings.h>
5#include <time.h> 5#include <time.h>
6#include <wayland-server-core.h> 6#include <wayland-server-core.h>
7#include <wlr/config.h>
8#include <wlr/backend/headless.h>
9#include <wlr/render/swapchain.h>
7#include <wlr/render/wlr_renderer.h> 10#include <wlr/render/wlr_renderer.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_buffer.h> 11#include <wlr/types/wlr_buffer.h>
12#include <wlr/types/wlr_gamma_control_v1.h>
10#include <wlr/types/wlr_matrix.h> 13#include <wlr/types/wlr_matrix.h>
11#include <wlr/types/wlr_output_damage.h>
12#include <wlr/types/wlr_output_layout.h> 14#include <wlr/types/wlr_output_layout.h>
13#include <wlr/types/wlr_output.h> 15#include <wlr/types/wlr_output.h>
14#include <wlr/types/wlr_presentation_time.h> 16#include <wlr/types/wlr_presentation_time.h>
15#include <wlr/types/wlr_surface.h> 17#include <wlr/types/wlr_compositor.h>
16#include <wlr/util/region.h> 18#include <wlr/util/region.h>
19#include <wlr/util/transform.h>
17#include "config.h" 20#include "config.h"
18#include "log.h" 21#include "log.h"
19#include "sway/config.h" 22#include "sway/config.h"
20#include "sway/desktop/transaction.h" 23#include "sway/desktop/transaction.h"
21#include "sway/input/input-manager.h" 24#include "sway/input/input-manager.h"
22#include "sway/input/seat.h" 25#include "sway/input/seat.h"
26#include "sway/ipc-server.h"
23#include "sway/layers.h" 27#include "sway/layers.h"
24#include "sway/output.h" 28#include "sway/output.h"
29#include "sway/scene_descriptor.h"
25#include "sway/server.h" 30#include "sway/server.h"
26#include "sway/surface.h"
27#include "sway/tree/arrange.h" 31#include "sway/tree/arrange.h"
28#include "sway/tree/container.h" 32#include "sway/tree/container.h"
29#include "sway/tree/root.h" 33#include "sway/tree/root.h"
30#include "sway/tree/view.h" 34#include "sway/tree/view.h"
31#include "sway/tree/workspace.h" 35#include "sway/tree/workspace.h"
32 36
37#if WLR_HAS_DRM_BACKEND
38#include <wlr/backend/drm.h>
39#include <wlr/types/wlr_drm_lease_v1.h>
40#endif
41
42bool output_match_name_or_id(struct sway_output *output,
43 const char *name_or_id) {
44 if (strcmp(name_or_id, "*") == 0) {
45 return true;
46 }
47
48 char identifier[128];
49 output_get_identifier(identifier, sizeof(identifier), output);
50 return strcasecmp(identifier, name_or_id) == 0
51 || strcasecmp(output->wlr_output->name, name_or_id) == 0;
52}
53
33struct sway_output *output_by_name_or_id(const char *name_or_id) { 54struct sway_output *output_by_name_or_id(const char *name_or_id) {
34 for (int i = 0; i < root->outputs->length; ++i) { 55 for (int i = 0; i < root->outputs->length; ++i) {
35 struct sway_output *output = root->outputs->items[i]; 56 struct sway_output *output = root->outputs->items[i];
36 char identifier[128]; 57 if (output_match_name_or_id(output, name_or_id)) {
37 output_get_identifier(identifier, sizeof(identifier), output);
38 if (strcasecmp(identifier, name_or_id) == 0
39 || strcasecmp(output->wlr_output->name, name_or_id) == 0) {
40 return output; 58 return output;
41 } 59 }
42 } 60 }
@@ -46,583 +64,209 @@ struct sway_output *output_by_name_or_id(const char *name_or_id) {
46struct sway_output *all_output_by_name_or_id(const char *name_or_id) { 64struct sway_output *all_output_by_name_or_id(const char *name_or_id) {
47 struct sway_output *output; 65 struct sway_output *output;
48 wl_list_for_each(output, &root->all_outputs, link) { 66 wl_list_for_each(output, &root->all_outputs, link) {
49 char identifier[128]; 67 if (output_match_name_or_id(output, name_or_id)) {
50 output_get_identifier(identifier, sizeof(identifier), output);
51 if (strcasecmp(identifier, name_or_id) == 0
52 || strcasecmp(output->wlr_output->name, name_or_id) == 0) {
53 return output; 68 return output;
54 } 69 }
55 } 70 }
56 return NULL; 71 return NULL;
57} 72}
58 73
59/**
60 * Rotate a child's position relative to a parent. The parent size is (pw, ph),
61 * the child position is (*sx, *sy) and its size is (sw, sh).
62 */
63static void rotate_child_position(double *sx, double *sy, double sw, double sh,
64 double pw, double ph, float rotation) {
65 if (rotation == 0.0f) {
66 return;
67 }
68 74
69 // Coordinates relative to the center of the subsurface 75struct sway_workspace *output_get_active_workspace(struct sway_output *output) {
70 double ox = *sx - pw/2 + sw/2, 76 struct sway_seat *seat = input_manager_current_seat();
71 oy = *sy - ph/2 + sh/2; 77 struct sway_node *focus = seat_get_active_tiling_child(seat, &output->node);
72 // Rotated coordinates 78 if (!focus) {
73 double rx = cos(-rotation)*ox - sin(-rotation)*oy, 79 if (!output->workspaces->length) {
74 ry = cos(-rotation)*oy + sin(-rotation)*ox; 80 return NULL;
75 *sx = rx + pw/2 - sw/2; 81 }
76 *sy = ry + ph/2 - sh/2; 82 return output->workspaces->items[0];
83 }
84 return focus->sway_workspace;
77} 85}
78 86
79struct surface_iterator_data { 87struct send_frame_done_data {
80 sway_surface_iterator_func_t user_iterator; 88 struct timespec when;
81 void *user_data; 89 int msec_until_refresh;
82
83 struct sway_output *output; 90 struct sway_output *output;
84 struct sway_view *view;
85 double ox, oy;
86 int width, height;
87 float rotation;
88}; 91};
89 92
90static bool get_surface_box(struct surface_iterator_data *data, 93struct buffer_timer {
91 struct wlr_surface *surface, int sx, int sy, 94 struct wl_listener destroy;
92 struct wlr_box *surface_box) { 95 struct wl_event_source *frame_done_timer;
93 struct sway_output *output = data->output; 96};
94
95 if (!wlr_surface_has_buffer(surface)) {
96 return false;
97 }
98
99 int sw = surface->current.width;
100 int sh = surface->current.height;
101
102 double _sx = sx + surface->sx;
103 double _sy = sy + surface->sy;
104 rotate_child_position(&_sx, &_sy, sw, sh, data->width, data->height,
105 data->rotation);
106
107 struct wlr_box box = {
108 .x = data->ox + _sx,
109 .y = data->oy + _sy,
110 .width = sw,
111 .height = sh,
112 };
113 if (surface_box != NULL) {
114 memcpy(surface_box, &box, sizeof(struct wlr_box));
115 }
116
117 struct wlr_box rotated_box;
118 wlr_box_rotated_bounds(&rotated_box, &box, data->rotation);
119
120 struct wlr_box output_box = {
121 .width = output->width,
122 .height = output->height,
123 };
124
125 struct wlr_box intersection;
126 return wlr_box_intersection(&intersection, &output_box, &rotated_box);
127}
128
129static void output_for_each_surface_iterator(struct wlr_surface *surface,
130 int sx, int sy, void *_data) {
131 struct surface_iterator_data *data = _data;
132
133 struct wlr_box box;
134 bool intersects = get_surface_box(data, surface, sx, sy, &box);
135 if (!intersects) {
136 return;
137 }
138
139 data->user_iterator(data->output, data->view, surface, &box, data->rotation,
140 data->user_data);
141}
142 97
143void output_surface_for_each_surface(struct sway_output *output, 98static int handle_buffer_timer(void *data) {
144 struct wlr_surface *surface, double ox, double oy, 99 struct wlr_scene_buffer *buffer = data;
145 sway_surface_iterator_func_t iterator, void *user_data) {
146 struct surface_iterator_data data = {
147 .user_iterator = iterator,
148 .user_data = user_data,
149 .output = output,
150 .view = NULL,
151 .ox = ox,
152 .oy = oy,
153 .width = surface->current.width,
154 .height = surface->current.height,
155 .rotation = 0,
156 };
157
158 wlr_surface_for_each_surface(surface,
159 output_for_each_surface_iterator, &data);
160}
161 100
162void output_view_for_each_surface(struct sway_output *output, 101 struct timespec now;
163 struct sway_view *view, sway_surface_iterator_func_t iterator, 102 clock_gettime(CLOCK_MONOTONIC, &now);
164 void *user_data) { 103 wlr_scene_buffer_send_frame_done(buffer, &now);
165 struct surface_iterator_data data = { 104 return 0;
166 .user_iterator = iterator,
167 .user_data = user_data,
168 .output = output,
169 .view = view,
170 .ox = view->container->surface_x - output->lx
171 - view->geometry.x,
172 .oy = view->container->surface_y - output->ly
173 - view->geometry.y,
174 .width = view->container->current.content_width,
175 .height = view->container->current.content_height,
176 .rotation = 0, // TODO
177 };
178
179 view_for_each_surface(view, output_for_each_surface_iterator, &data);
180} 105}
181 106
182void output_view_for_each_popup_surface(struct sway_output *output, 107static void handle_buffer_timer_destroy(struct wl_listener *listener,
183 struct sway_view *view, sway_surface_iterator_func_t iterator, 108 void *data) {
184 void *user_data) { 109 struct buffer_timer *timer = wl_container_of(listener, timer, destroy);
185 struct surface_iterator_data data = {
186 .user_iterator = iterator,
187 .user_data = user_data,
188 .output = output,
189 .view = view,
190 .ox = view->container->surface_x - output->lx
191 - view->geometry.x,
192 .oy = view->container->surface_y - output->ly
193 - view->geometry.y,
194 .width = view->container->current.content_width,
195 .height = view->container->current.content_height,
196 .rotation = 0, // TODO
197 };
198
199 view_for_each_popup_surface(view, output_for_each_surface_iterator, &data);
200}
201 110
202void output_layer_for_each_surface(struct sway_output *output, 111 wl_list_remove(&timer->destroy.link);
203 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, 112 wl_event_source_remove(timer->frame_done_timer);
204 void *user_data) { 113 free(timer);
205 struct sway_layer_surface *layer_surface;
206 wl_list_for_each(layer_surface, layer_surfaces, link) {
207 struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
208 layer_surface->layer_surface;
209 output_surface_for_each_surface(output, wlr_layer_surface_v1->surface,
210 layer_surface->geo.x, layer_surface->geo.y, iterator,
211 user_data);
212
213 struct wlr_xdg_popup *state;
214 wl_list_for_each(state, &wlr_layer_surface_v1->popups, link) {
215 struct wlr_xdg_surface *popup = state->base;
216 if (!popup->configured) {
217 continue;
218 }
219
220 double popup_sx, popup_sy;
221 popup_sx = layer_surface->geo.x +
222 popup->popup->geometry.x - popup->geometry.x;
223 popup_sy = layer_surface->geo.y +
224 popup->popup->geometry.y - popup->geometry.y;
225
226 struct wlr_surface *surface = popup->surface;
227
228 struct surface_iterator_data data = {
229 .user_iterator = iterator,
230 .user_data = user_data,
231 .output = output,
232 .view = NULL,
233 .ox = popup_sx,
234 .oy = popup_sy,
235 .width = surface->current.width,
236 .height = surface->current.height,
237 .rotation = 0,
238 };
239
240 wlr_xdg_surface_for_each_surface(
241 popup, output_for_each_surface_iterator, &data);
242 }
243 }
244} 114}
245 115
246void output_layer_for_each_toplevel_surface(struct sway_output *output, 116static struct buffer_timer *buffer_timer_get_or_create(struct wlr_scene_buffer *buffer) {
247 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, 117 struct buffer_timer *timer =
248 void *user_data) { 118 scene_descriptor_try_get(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER);
249 struct sway_layer_surface *layer_surface; 119 if (timer) {
250 wl_list_for_each(layer_surface, layer_surfaces, link) { 120 return timer;
251 struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
252 layer_surface->layer_surface;
253 output_surface_for_each_surface(output, wlr_layer_surface_v1->surface,
254 layer_surface->geo.x, layer_surface->geo.y, iterator,
255 user_data);
256 } 121 }
257}
258 122
259 123 timer = calloc(1, sizeof(struct buffer_timer));
260void output_layer_for_each_popup_surface(struct sway_output *output, 124 if (!timer) {
261 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, 125 return NULL;
262 void *user_data) {
263 struct sway_layer_surface *layer_surface;
264 wl_list_for_each(layer_surface, layer_surfaces, link) {
265 struct wlr_layer_surface_v1 *wlr_layer_surface_v1 =
266 layer_surface->layer_surface;
267
268 struct wlr_xdg_popup *state;
269 wl_list_for_each(state, &wlr_layer_surface_v1->popups, link) {
270 struct wlr_xdg_surface *popup = state->base;
271 if (!popup->configured) {
272 continue;
273 }
274
275 double popup_sx, popup_sy;
276 popup_sx = layer_surface->geo.x +
277 popup->popup->geometry.x - popup->geometry.x;
278 popup_sy = layer_surface->geo.y +
279 popup->popup->geometry.y - popup->geometry.y;
280
281 struct wlr_surface *surface = popup->surface;
282
283 struct surface_iterator_data data = {
284 .user_iterator = iterator,
285 .user_data = user_data,
286 .output = output,
287 .view = NULL,
288 .ox = popup_sx,
289 .oy = popup_sy,
290 .width = surface->current.width,
291 .height = surface->current.height,
292 .rotation = 0,
293 };
294
295 wlr_xdg_surface_for_each_surface(
296 popup, output_for_each_surface_iterator, &data);
297 }
298 } 126 }
299}
300 127
301#if HAVE_XWAYLAND 128 timer->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop,
302void output_unmanaged_for_each_surface(struct sway_output *output, 129 handle_buffer_timer, buffer);
303 struct wl_list *unmanaged, sway_surface_iterator_func_t iterator, 130 if (!timer->frame_done_timer) {
304 void *user_data) { 131 free(timer);
305 struct sway_xwayland_unmanaged *unmanaged_surface; 132 return NULL;
306 wl_list_for_each(unmanaged_surface, unmanaged, link) {
307 struct wlr_xwayland_surface *xsurface =
308 unmanaged_surface->wlr_xwayland_surface;
309 double ox = unmanaged_surface->lx - output->lx;
310 double oy = unmanaged_surface->ly - output->ly;
311
312 output_surface_for_each_surface(output, xsurface->surface, ox, oy,
313 iterator, user_data);
314 } 133 }
315}
316#endif
317 134
318void output_drag_icons_for_each_surface(struct sway_output *output, 135 scene_descriptor_assign(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER, timer);
319 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator,
320 void *user_data) {
321 struct sway_drag_icon *drag_icon;
322 wl_list_for_each(drag_icon, drag_icons, link) {
323 double ox = drag_icon->x - output->lx;
324 double oy = drag_icon->y - output->ly;
325
326 if (drag_icon->wlr_drag_icon->mapped) {
327 output_surface_for_each_surface(output,
328 drag_icon->wlr_drag_icon->surface, ox, oy,
329 iterator, user_data);
330 }
331 }
332}
333 136
334static void for_each_surface_container_iterator(struct sway_container *con, 137 timer->destroy.notify = handle_buffer_timer_destroy;
335 void *_data) { 138 wl_signal_add(&buffer->node.events.destroy, &timer->destroy);
336 if (!con->view || !view_is_visible(con->view)) {
337 return;
338 }
339 139
340 struct surface_iterator_data *data = _data; 140 return timer;
341 output_view_for_each_surface(data->output, con->view,
342 data->user_iterator, data->user_data);
343} 141}
344 142
345static void output_for_each_surface(struct sway_output *output, 143static void send_frame_done_iterator(struct wlr_scene_buffer *buffer,
346 sway_surface_iterator_func_t iterator, void *user_data) { 144 int x, int y, void *user_data) {
347 if (output_has_opaque_overlay_layer_surface(output)) { 145 struct send_frame_done_data *data = user_data;
348 goto overlay; 146 struct sway_output *output = data->output;
349 } 147 int view_max_render_time = 0;
350 148
351 struct surface_iterator_data data = { 149 if (buffer->primary_output != data->output->scene_output) {
352 .user_iterator = iterator, 150 return;
353 .user_data = user_data,
354 .output = output,
355 .view = NULL,
356 };
357
358 struct sway_workspace *workspace = output_get_active_workspace(output);
359 struct sway_container *fullscreen_con = root->fullscreen_global;
360 if (!fullscreen_con) {
361 if (!workspace) {
362 return;
363 }
364 fullscreen_con = workspace->current.fullscreen;
365 }
366 if (fullscreen_con) {
367 for_each_surface_container_iterator(fullscreen_con, &data);
368 container_for_each_child(fullscreen_con,
369 for_each_surface_container_iterator, &data);
370
371 // TODO: Show transient containers for fullscreen global
372 if (fullscreen_con == workspace->current.fullscreen) {
373 for (int i = 0; i < workspace->current.floating->length; ++i) {
374 struct sway_container *floater =
375 workspace->current.floating->items[i];
376 if (container_is_transient_for(floater, fullscreen_con)) {
377 for_each_surface_container_iterator(floater, &data);
378 }
379 }
380 }
381#if HAVE_XWAYLAND
382 output_unmanaged_for_each_surface(output, &root->xwayland_unmanaged,
383 iterator, user_data);
384#endif
385 } else {
386 output_layer_for_each_surface(output,
387 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
388 iterator, user_data);
389 output_layer_for_each_surface(output,
390 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
391 iterator, user_data);
392
393 workspace_for_each_container(workspace,
394 for_each_surface_container_iterator, &data);
395
396#if HAVE_XWAYLAND
397 output_unmanaged_for_each_surface(output, &root->xwayland_unmanaged,
398 iterator, user_data);
399#endif
400 output_layer_for_each_surface(output,
401 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
402 iterator, user_data);
403 } 151 }
404 152
405overlay: 153 struct wlr_scene_node *current = &buffer->node;
406 output_layer_for_each_surface(output, 154 while (true) {
407 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], 155 struct sway_view *view = scene_descriptor_try_get(current,
408 iterator, user_data); 156 SWAY_SCENE_DESC_VIEW);
409 output_drag_icons_for_each_surface(output, &root->drag_icons, 157 if (view) {
410 iterator, user_data); 158 view_max_render_time = view->max_render_time;
411} 159 break;
412
413static int scale_length(int length, int offset, float scale) {
414 return round((offset + length) * scale) - round(offset * scale);
415}
416
417void scale_box(struct wlr_box *box, float scale) {
418 box->width = scale_length(box->width, box->x, scale);
419 box->height = scale_length(box->height, box->y, scale);
420 box->x = round(box->x * scale);
421 box->y = round(box->y * scale);
422}
423
424struct sway_workspace *output_get_active_workspace(struct sway_output *output) {
425 struct sway_seat *seat = input_manager_current_seat();
426 struct sway_node *focus = seat_get_active_tiling_child(seat, &output->node);
427 if (!focus) {
428 if (!output->workspaces->length) {
429 return NULL;
430 } 160 }
431 return output->workspaces->items[0];
432 }
433 return focus->sway_workspace;
434}
435 161
436bool output_has_opaque_overlay_layer_surface(struct sway_output *output) { 162 if (!current->parent) {
437 struct sway_layer_surface *sway_layer_surface; 163 break;
438 wl_list_for_each(sway_layer_surface,
439 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], link) {
440 struct wlr_surface *wlr_surface = sway_layer_surface->layer_surface->surface;
441 pixman_box32_t output_box = {
442 .x2 = output->width,
443 .y2 = output->height,
444 };
445 pixman_region32_t surface_opaque_box;
446 pixman_region32_init(&surface_opaque_box);
447 pixman_region32_copy(&surface_opaque_box, &wlr_surface->opaque_region);
448 pixman_region32_translate(&surface_opaque_box,
449 sway_layer_surface->geo.x, sway_layer_surface->geo.y);
450 pixman_region_overlap_t contains =
451 pixman_region32_contains_rectangle(&surface_opaque_box, &output_box);
452 pixman_region32_fini(&surface_opaque_box);
453
454 if (contains == PIXMAN_REGION_IN) {
455 return true;
456 } 164 }
457 }
458 return false;
459}
460 165
461struct send_frame_done_data { 166 current = &current->parent->node;
462 struct timespec when;
463 int msec_until_refresh;
464};
465
466static void send_frame_done_iterator(struct sway_output *output, struct sway_view *view,
467 struct wlr_surface *surface, struct wlr_box *box, float rotation,
468 void *user_data) {
469 int view_max_render_time = 0;
470 if (view != NULL) {
471 view_max_render_time = view->max_render_time;
472 } 167 }
473 168
474 struct send_frame_done_data *data = user_data;
475
476 int delay = data->msec_until_refresh - output->max_render_time 169 int delay = data->msec_until_refresh - output->max_render_time
477 - view_max_render_time; 170 - view_max_render_time;
478 171
479 if (output->max_render_time == 0 || view_max_render_time == 0 || delay < 1) { 172 struct buffer_timer *timer = NULL;
480 wlr_surface_send_frame_done(surface, &data->when);
481 } else {
482 struct sway_surface *sway_surface = surface->data;
483 wl_event_source_timer_update(sway_surface->frame_done_timer, delay);
484 }
485}
486
487static void send_frame_done(struct sway_output *output, struct send_frame_done_data *data) {
488 output_for_each_surface(output, send_frame_done_iterator, data);
489}
490
491static void count_surface_iterator(struct sway_output *output, struct sway_view *view,
492 struct wlr_surface *surface, struct wlr_box *_box, float rotation,
493 void *data) {
494 size_t *n = data;
495 (*n)++;
496}
497
498static bool scan_out_fullscreen_view(struct sway_output *output,
499 struct sway_view *view) {
500 struct wlr_output *wlr_output = output->wlr_output;
501 struct sway_workspace *workspace = output->current.active_workspace;
502 if (!sway_assert(workspace, "Expected an active workspace")) {
503 return false;
504 }
505 173
506 if (!wl_list_empty(&view->saved_buffers)) { 174 if (output->max_render_time != 0 && view_max_render_time != 0 && delay > 0) {
507 return false; 175 timer = buffer_timer_get_or_create(buffer);
508 } 176 }
509 177
510 for (int i = 0; i < workspace->current.floating->length; ++i) { 178 if (timer) {
511 struct sway_container *floater = 179 wl_event_source_timer_update(timer->frame_done_timer, delay);
512 workspace->current.floating->items[i]; 180 } else {
513 if (container_is_transient_for(floater, view->container)) { 181 wlr_scene_buffer_send_frame_done(buffer, &data->when);
514 return false;
515 }
516 } 182 }
183}
517 184
518#if HAVE_XWAYLAND 185static enum wlr_scale_filter_mode get_scale_filter(struct sway_output *output) {
519 if (!wl_list_empty(&root->xwayland_unmanaged)) { 186 switch (output->scale_filter) {
520 return false; 187 case SCALE_FILTER_LINEAR:
188 return WLR_SCALE_FILTER_BILINEAR;
189 case SCALE_FILTER_NEAREST:
190 return WLR_SCALE_FILTER_NEAREST;
191 default:
192 abort(); // unreachable
521 } 193 }
522#endif 194}
523 195
524 if (!wl_list_empty(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY])) { 196static void output_configure_scene(struct sway_output *output,
525 return false; 197 struct wlr_scene_node *node, float opacity) {
526 } 198 if (!node->enabled) {
527 if (!wl_list_empty(&root->drag_icons)) { 199 return;
528 return false;
529 } 200 }
530 201
531 struct wlr_surface *surface = view->surface; 202 struct sway_container *con =
532 if (surface == NULL) { 203 scene_descriptor_try_get(node, SWAY_SCENE_DESC_CONTAINER);
533 return false; 204 if (con) {
534 } 205 opacity = con->alpha;
535 size_t n_surfaces = 0;
536 output_view_for_each_surface(output, view,
537 count_surface_iterator, &n_surfaces);
538 if (n_surfaces != 1) {
539 return false;
540 } 206 }
541 207
542 if (surface->buffer == NULL) { 208 if (node->type == WLR_SCENE_NODE_BUFFER) {
543 return false; 209 struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
544 }
545 210
546 if ((float)surface->current.scale != wlr_output->scale || 211 // hack: don't call the scene setter because that will damage all outputs
547 surface->current.transform != wlr_output->transform) { 212 // We don't want to damage outputs that aren't our current output that
548 return false; 213 // we're configuring
549 } 214 buffer->filter_mode = get_scale_filter(output);
550 215
551 wlr_output_attach_buffer(wlr_output, &surface->buffer->base); 216 wlr_scene_buffer_set_opacity(buffer, opacity);
552 if (!wlr_output_test(wlr_output)) { 217 } else if (node->type == WLR_SCENE_NODE_TREE) {
553 return false; 218 struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
219 struct wlr_scene_node *node;
220 wl_list_for_each(node, &tree->children, link) {
221 output_configure_scene(output, node, opacity);
222 }
554 } 223 }
555
556 wlr_presentation_surface_sampled_on_output(server.presentation, surface,
557 wlr_output);
558
559 return wlr_output_commit(wlr_output);
560} 224}
561 225
562static int output_repaint_timer_handler(void *data) { 226static int output_repaint_timer_handler(void *data) {
563 struct sway_output *output = data; 227 struct sway_output *output = data;
564 if (output->wlr_output == NULL) {
565 return 0;
566 }
567
568 output->wlr_output->frame_pending = false;
569 228
570 struct sway_workspace *workspace = output->current.active_workspace; 229 if (!output->enabled) {
571 if (workspace == NULL) {
572 return 0; 230 return 0;
573 } 231 }
574 232
575 struct sway_container *fullscreen_con = root->fullscreen_global; 233 output->wlr_output->frame_pending = false;
576 if (!fullscreen_con) {
577 fullscreen_con = workspace->current.fullscreen;
578 }
579 234
580 if (fullscreen_con && fullscreen_con->view) { 235 output_configure_scene(output, &root->root_scene->tree.node, 1.0f);
581 // Try to scan-out the fullscreen view
582 static bool last_scanned_out = false;
583 bool scanned_out =
584 scan_out_fullscreen_view(output, fullscreen_con->view);
585 236
586 if (scanned_out && !last_scanned_out) { 237 if (output->gamma_lut_changed) {
587 sway_log(SWAY_DEBUG, "Scanning out fullscreen view on %s", 238 struct wlr_output_state pending;
588 output->wlr_output->name); 239 wlr_output_state_init(&pending);
240 if (!wlr_scene_output_build_state(output->scene_output, &pending, NULL)) {
241 return 0;
589 } 242 }
590 if (last_scanned_out && !scanned_out) { 243
591 sway_log(SWAY_DEBUG, "Stopping fullscreen view scan out on %s", 244 output->gamma_lut_changed = false;
592 output->wlr_output->name); 245 struct wlr_gamma_control_v1 *gamma_control =
246 wlr_gamma_control_manager_v1_get_control(
247 server.gamma_control_manager_v1, output->wlr_output);
248 if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) {
249 wlr_output_state_finish(&pending);
250 return 0;
593 } 251 }
594 last_scanned_out = scanned_out;
595 252
596 if (scanned_out) { 253 if (!wlr_output_commit_state(output->wlr_output, &pending)) {
254 wlr_gamma_control_v1_send_failed_and_destroy(gamma_control);
255 wlr_output_state_finish(&pending);
597 return 0; 256 return 0;
598 } 257 }
599 }
600 258
601 bool needs_frame; 259 wlr_output_state_finish(&pending);
602 pixman_region32_t damage;
603 pixman_region32_init(&damage);
604 if (!wlr_output_damage_attach_render(output->damage,
605 &needs_frame, &damage)) {
606 return 0; 260 return 0;
607 } 261 }
608 262
609 if (needs_frame) { 263 wlr_scene_output_commit(output->scene_output, NULL);
610 struct timespec now;
611 clock_gettime(CLOCK_MONOTONIC, &now);
612
613 output_render(output, &now, &damage);
614 } else {
615 wlr_output_rollback(output->wlr_output);
616 }
617
618 pixman_region32_fini(&damage);
619
620 return 0; 264 return 0;
621} 265}
622 266
623static void damage_handle_frame(struct wl_listener *listener, void *user_data) { 267static void handle_frame(struct wl_listener *listener, void *user_data) {
624 struct sway_output *output = 268 struct sway_output *output =
625 wl_container_of(listener, output, damage_frame); 269 wl_container_of(listener, output, frame);
626 if (!output->enabled || !output->wlr_output->enabled) { 270 if (!output->enabled || !output->wlr_output->enabled) {
627 return; 271 return;
628 } 272 }
@@ -633,9 +277,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *user_data) {
633 277
634 if (output->max_render_time != 0) { 278 if (output->max_render_time != 0) {
635 struct timespec now; 279 struct timespec now;
636 clockid_t presentation_clock 280 clock_gettime(CLOCK_MONOTONIC, &now);
637 = wlr_backend_get_presentation_clock(server.backend);
638 clock_gettime(presentation_clock, &now);
639 281
640 const long NSEC_IN_SECONDS = 1000000000; 282 const long NSEC_IN_SECONDS = 1000000000;
641 struct timespec predicted_refresh = output->last_presentation; 283 struct timespec predicted_refresh = output->last_presentation;
@@ -682,124 +324,8 @@ static void damage_handle_frame(struct wl_listener *listener, void *user_data) {
682 struct send_frame_done_data data = {0}; 324 struct send_frame_done_data data = {0};
683 clock_gettime(CLOCK_MONOTONIC, &data.when); 325 clock_gettime(CLOCK_MONOTONIC, &data.when);
684 data.msec_until_refresh = msec_until_refresh; 326 data.msec_until_refresh = msec_until_refresh;
685 send_frame_done(output, &data); 327 data.output = output;
686} 328 wlr_scene_output_for_each_buffer(output->scene_output, send_frame_done_iterator, &data);
687
688void output_damage_whole(struct sway_output *output) {
689 // The output can exist with no wlr_output if it's just been disconnected
690 // and the transaction to evacuate it has't completed yet.
691 if (output && output->wlr_output && output->damage) {
692 wlr_output_damage_add_whole(output->damage);
693 }
694}
695
696static void damage_surface_iterator(struct sway_output *output, struct sway_view *view,
697 struct wlr_surface *surface, struct wlr_box *_box, float rotation,
698 void *_data) {
699 bool *data = _data;
700 bool whole = *data;
701
702 struct wlr_box box = *_box;
703 scale_box(&box, output->wlr_output->scale);
704
705 int center_x = box.x + box.width/2;
706 int center_y = box.y + box.height/2;
707
708 if (pixman_region32_not_empty(&surface->buffer_damage)) {
709 pixman_region32_t damage;
710 pixman_region32_init(&damage);
711 wlr_surface_get_effective_damage(surface, &damage);
712 wlr_region_scale(&damage, &damage, output->wlr_output->scale);
713 if (ceil(output->wlr_output->scale) > surface->current.scale) {
714 // When scaling up a surface, it'll become blurry so we need to
715 // expand the damage region
716 wlr_region_expand(&damage, &damage,
717 ceil(output->wlr_output->scale) - surface->current.scale);
718 }
719 pixman_region32_translate(&damage, box.x, box.y);
720 wlr_region_rotated_bounds(&damage, &damage, rotation,
721 center_x, center_y);
722 wlr_output_damage_add(output->damage, &damage);
723 pixman_region32_fini(&damage);
724 }
725
726 if (whole) {
727 wlr_box_rotated_bounds(&box, &box, rotation);
728 wlr_output_damage_add_box(output->damage, &box);
729 }
730
731 if (!wl_list_empty(&surface->current.frame_callback_list)) {
732 wlr_output_schedule_frame(output->wlr_output);
733 }
734}
735
736void output_damage_surface(struct sway_output *output, double ox, double oy,
737 struct wlr_surface *surface, bool whole) {
738 output_surface_for_each_surface(output, surface, ox, oy,
739 damage_surface_iterator, &whole);
740}
741
742void output_damage_from_view(struct sway_output *output,
743 struct sway_view *view) {
744 if (!view_is_visible(view)) {
745 return;
746 }
747 bool whole = false;
748 output_view_for_each_surface(output, view, damage_surface_iterator, &whole);
749}
750
751// Expecting an unscaled box in layout coordinates
752void output_damage_box(struct sway_output *output, struct wlr_box *_box) {
753 struct wlr_box box;
754 memcpy(&box, _box, sizeof(struct wlr_box));
755 box.x -= output->lx;
756 box.y -= output->ly;
757 scale_box(&box, output->wlr_output->scale);
758 wlr_output_damage_add_box(output->damage, &box);
759}
760
761static void damage_child_views_iterator(struct sway_container *con,
762 void *data) {
763 if (!con->view || !view_is_visible(con->view)) {
764 return;
765 }
766 struct sway_output *output = data;
767 bool whole = true;
768 output_view_for_each_surface(output, con->view, damage_surface_iterator,
769 &whole);
770}
771
772void output_damage_whole_container(struct sway_output *output,
773 struct sway_container *con) {
774 // Pad the box by 1px, because the width is a double and might be a fraction
775 struct wlr_box box = {
776 .x = con->current.x - output->lx - 1,
777 .y = con->current.y - output->ly - 1,
778 .width = con->current.width + 2,
779 .height = con->current.height + 2,
780 };
781 scale_box(&box, output->wlr_output->scale);
782 wlr_output_damage_add_box(output->damage, &box);
783 // Damage subsurfaces as well, which may extend outside the box
784 if (con->view) {
785 damage_child_views_iterator(con, output);
786 } else {
787 container_for_each_child(con, damage_child_views_iterator, output);
788 }
789}
790
791static void damage_handle_destroy(struct wl_listener *listener, void *data) {
792 struct sway_output *output =
793 wl_container_of(listener, output, damage_destroy);
794 if (!output->enabled) {
795 return;
796 }
797 output_disable(output);
798
799 wl_list_remove(&output->damage_destroy.link);
800 wl_list_remove(&output->damage_frame.link);
801
802 transaction_commit_dirty();
803} 329}
804 330
805static void update_output_manager_config(struct sway_server *server) { 331static void update_output_manager_config(struct sway_server *server) {
@@ -808,73 +334,61 @@ static void update_output_manager_config(struct sway_server *server) {
808 334
809 struct sway_output *output; 335 struct sway_output *output;
810 wl_list_for_each(output, &root->all_outputs, link) { 336 wl_list_for_each(output, &root->all_outputs, link) {
811 if (output == root->noop_output) { 337 if (output == root->fallback_output) {
812 continue; 338 continue;
813 } 339 }
814 struct wlr_output_configuration_head_v1 *config_head = 340 struct wlr_output_configuration_head_v1 *config_head =
815 wlr_output_configuration_head_v1_create(config, output->wlr_output); 341 wlr_output_configuration_head_v1_create(config, output->wlr_output);
816 struct wlr_box *output_box = wlr_output_layout_get_box( 342 struct wlr_box output_box;
817 root->output_layout, output->wlr_output); 343 wlr_output_layout_get_box(root->output_layout,
818 // We mark the output enabled even if it is switched off by DPMS 344 output->wlr_output, &output_box);
819 config_head->state.enabled = output->enabled; 345 // We mark the output enabled when it's switched off but not disabled
820 config_head->state.mode = output->current_mode; 346 config_head->state.enabled = !wlr_box_empty(&output_box);
821 if (output_box) { 347 config_head->state.x = output_box.x;
822 config_head->state.x = output_box->x; 348 config_head->state.y = output_box.y;
823 config_head->state.y = output_box->y;
824 }
825 } 349 }
826 350
827 wlr_output_manager_v1_set_configuration(server->output_manager_v1, config); 351 wlr_output_manager_v1_set_configuration(server->output_manager_v1, config);
352
353 ipc_event_output();
828} 354}
829 355
830static void handle_destroy(struct wl_listener *listener, void *data) { 356static void begin_destroy(struct sway_output *output) {
831 struct sway_output *output = wl_container_of(listener, output, destroy);
832 struct sway_server *server = output->server; 357 struct sway_server *server = output->server;
833 wl_signal_emit(&output->events.destroy, output);
834 358
835 if (output->enabled) { 359 if (output->enabled) {
836 output_disable(output); 360 output_disable(output);
837 } 361 }
362
838 output_begin_destroy(output); 363 output_begin_destroy(output);
839 364
365 wl_list_remove(&output->link);
366
367 wl_list_remove(&output->layout_destroy.link);
840 wl_list_remove(&output->destroy.link); 368 wl_list_remove(&output->destroy.link);
841 wl_list_remove(&output->commit.link); 369 wl_list_remove(&output->commit.link);
842 wl_list_remove(&output->mode.link);
843 wl_list_remove(&output->present.link); 370 wl_list_remove(&output->present.link);
371 wl_list_remove(&output->frame.link);
372 wl_list_remove(&output->request_state.link);
373
374 wlr_scene_output_destroy(output->scene_output);
375 output->scene_output = NULL;
376 output->wlr_output->data = NULL;
377 output->wlr_output = NULL;
844 378
845 transaction_commit_dirty(); 379 transaction_commit_dirty();
846 380
847 update_output_manager_config(server); 381 update_output_manager_config(server);
848} 382}
849 383
850static void handle_mode(struct wl_listener *listener, void *data) { 384static void handle_destroy(struct wl_listener *listener, void *data) {
851 struct sway_output *output = wl_container_of(listener, output, mode); 385 struct sway_output *output = wl_container_of(listener, output, destroy);
852 if (!output->enabled && !output->enabling) { 386 begin_destroy(output);
853 struct output_config *oc = find_output_config(output);
854 if (output->wlr_output->current_mode != NULL &&
855 (!oc || oc->enabled)) {
856 // We want to enable this output, but it didn't work last time,
857 // possibly because we hadn't enough CRTCs. Try again now that the
858 // output has a mode.
859 sway_log(SWAY_DEBUG, "Output %s has gained a CRTC, "
860 "trying to enable it", output->wlr_output->name);
861 apply_output_config(oc, output);
862 }
863 return;
864 }
865 if (!output->enabled) {
866 return;
867 }
868 arrange_layers(output);
869 arrange_output(output);
870 transaction_commit_dirty();
871
872 update_output_manager_config(output->server);
873} 387}
874 388
875static void update_textures(struct sway_container *con, void *data) { 389static void handle_layout_destroy(struct wl_listener *listener, void *data) {
876 container_update_title_textures(con); 390 struct sway_output *output = wl_container_of(listener, output, layout_destroy);
877 container_update_marks_textures(con); 391 begin_destroy(output);
878} 392}
879 393
880static void handle_commit(struct wl_listener *listener, void *data) { 394static void handle_commit(struct wl_listener *listener, void *data) {
@@ -885,24 +399,28 @@ static void handle_commit(struct wl_listener *listener, void *data) {
885 return; 399 return;
886 } 400 }
887 401
888 if (event->committed & WLR_OUTPUT_STATE_SCALE) { 402 if (event->state->committed & (
889 output_for_each_container(output, update_textures, NULL); 403 WLR_OUTPUT_STATE_MODE |
890 } 404 WLR_OUTPUT_STATE_TRANSFORM |
891 405 WLR_OUTPUT_STATE_SCALE)) {
892 if (event->committed & (WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SCALE)) {
893 arrange_layers(output); 406 arrange_layers(output);
894 arrange_output(output); 407 arrange_output(output);
895 transaction_commit_dirty(); 408 transaction_commit_dirty();
896 409
897 update_output_manager_config(output->server); 410 update_output_manager_config(output->server);
898 } 411 }
412
413 // Next time the output is enabled, try to re-apply the gamma LUT
414 if ((event->state->committed & WLR_OUTPUT_STATE_ENABLED) && !output->wlr_output->enabled) {
415 output->gamma_lut_changed = true;
416 }
899} 417}
900 418
901static void handle_present(struct wl_listener *listener, void *data) { 419static void handle_present(struct wl_listener *listener, void *data) {
902 struct sway_output *output = wl_container_of(listener, output, present); 420 struct sway_output *output = wl_container_of(listener, output, present);
903 struct wlr_output_event_present *output_event = data; 421 struct wlr_output_event_present *output_event = data;
904 422
905 if (!output->enabled) { 423 if (!output->enabled || !output_event->presented) {
906 return; 424 return;
907 } 425 }
908 426
@@ -910,34 +428,90 @@ static void handle_present(struct wl_listener *listener, void *data) {
910 output->refresh_nsec = output_event->refresh; 428 output->refresh_nsec = output_event->refresh;
911} 429}
912 430
431static void handle_request_state(struct wl_listener *listener, void *data) {
432 struct sway_output *output =
433 wl_container_of(listener, output, request_state);
434 const struct wlr_output_event_request_state *event = data;
435 wlr_output_commit_state(output->wlr_output, event->state);
436}
437
438static unsigned int last_headless_num = 0;
439
913void handle_new_output(struct wl_listener *listener, void *data) { 440void handle_new_output(struct wl_listener *listener, void *data) {
914 struct sway_server *server = wl_container_of(listener, server, new_output); 441 struct sway_server *server = wl_container_of(listener, server, new_output);
915 struct wlr_output *wlr_output = data; 442 struct wlr_output *wlr_output = data;
916 sway_log(SWAY_DEBUG, "New output %p: %s", wlr_output, wlr_output->name); 443
444 if (wlr_output == root->fallback_output->wlr_output) {
445 return;
446 }
447
448 if (wlr_output_is_headless(wlr_output)) {
449 char name[64];
450 snprintf(name, sizeof(name), "HEADLESS-%u", ++last_headless_num);
451 wlr_output_set_name(wlr_output, name);
452 }
453
454 sway_log(SWAY_DEBUG, "New output %p: %s (non-desktop: %d)",
455 wlr_output, wlr_output->name, wlr_output->non_desktop);
456
457 if (wlr_output->non_desktop) {
458 sway_log(SWAY_DEBUG, "Not configuring non-desktop output");
459 struct sway_output_non_desktop *non_desktop = output_non_desktop_create(wlr_output);
460#if WLR_HAS_DRM_BACKEND
461 if (server->drm_lease_manager) {
462 wlr_drm_lease_v1_manager_offer_output(server->drm_lease_manager,
463 wlr_output);
464 }
465#endif
466 list_add(root->non_desktop_outputs, non_desktop);
467 return;
468 }
469
470 if (!wlr_output_init_render(wlr_output, server->allocator,
471 server->renderer)) {
472 sway_log(SWAY_ERROR, "Failed to init output render");
473 return;
474 }
475
476 // Create the scene output here so we're not accidentally creating one for
477 // the fallback output
478 struct wlr_scene_output *scene_output =
479 wlr_scene_output_create(root->root_scene, wlr_output);
480 if (!scene_output) {
481 sway_log(SWAY_ERROR, "Failed to create a scene output");
482 return;
483 }
917 484
918 struct sway_output *output = output_create(wlr_output); 485 struct sway_output *output = output_create(wlr_output);
919 if (!output) { 486 if (!output) {
487 sway_log(SWAY_ERROR, "Failed to create a sway output");
488 wlr_scene_output_destroy(scene_output);
920 return; 489 return;
921 } 490 }
491
922 output->server = server; 492 output->server = server;
923 output->damage = wlr_output_damage_create(wlr_output); 493 output->scene_output = scene_output;
924 494
495 wl_signal_add(&root->output_layout->events.destroy, &output->layout_destroy);
496 output->layout_destroy.notify = handle_layout_destroy;
925 wl_signal_add(&wlr_output->events.destroy, &output->destroy); 497 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
926 output->destroy.notify = handle_destroy; 498 output->destroy.notify = handle_destroy;
927 wl_signal_add(&wlr_output->events.commit, &output->commit); 499 wl_signal_add(&wlr_output->events.commit, &output->commit);
928 output->commit.notify = handle_commit; 500 output->commit.notify = handle_commit;
929 wl_signal_add(&wlr_output->events.mode, &output->mode);
930 output->mode.notify = handle_mode;
931 wl_signal_add(&wlr_output->events.present, &output->present); 501 wl_signal_add(&wlr_output->events.present, &output->present);
932 output->present.notify = handle_present; 502 output->present.notify = handle_present;
933 wl_signal_add(&output->damage->events.frame, &output->damage_frame); 503 wl_signal_add(&wlr_output->events.frame, &output->frame);
934 output->damage_frame.notify = damage_handle_frame; 504 output->frame.notify = handle_frame;
935 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); 505 wl_signal_add(&wlr_output->events.request_state, &output->request_state);
936 output->damage_destroy.notify = damage_handle_destroy; 506 output->request_state.notify = handle_request_state;
937 507
938 output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, 508 output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
939 output_repaint_timer_handler, output); 509 output_repaint_timer_handler, output);
940 510
511 if (server->session_lock.lock) {
512 sway_session_lock_add_output(server->session_lock.lock, output);
513 }
514
941 struct output_config *oc = find_output_config(output); 515 struct output_config *oc = find_output_config(output);
942 apply_output_config(oc, output); 516 apply_output_config(oc, output);
943 free_output_config(oc); 517 free_output_config(oc);
@@ -954,6 +528,21 @@ void handle_output_layout_change(struct wl_listener *listener,
954 update_output_manager_config(server); 528 update_output_manager_config(server);
955} 529}
956 530
531void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) {
532 struct sway_server *server =
533 wl_container_of(listener, server, gamma_control_set_gamma);
534 const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data;
535
536 struct sway_output *output = event->output->data;
537
538 if(!output) {
539 return;
540 }
541
542 output->gamma_lut_changed = true;
543 wlr_output_schedule_frame(output->wlr_output);
544}
545
957static void output_manager_apply(struct sway_server *server, 546static void output_manager_apply(struct sway_server *server,
958 struct wlr_output_configuration_v1 *config, bool test_only) { 547 struct wlr_output_configuration_v1 *config, bool test_only) {
959 // TODO: perform atomic tests on the whole backend atomically 548 // TODO: perform atomic tests on the whole backend atomically
@@ -1002,6 +591,7 @@ static void output_manager_apply(struct sway_server *server,
1002 oc->y = config_head->state.y; 591 oc->y = config_head->state.y;
1003 oc->transform = config_head->state.transform; 592 oc->transform = config_head->state.transform;
1004 oc->scale = config_head->state.scale; 593 oc->scale = config_head->state.scale;
594 oc->adaptive_sync = config_head->state.adaptive_sync_enabled;
1005 595
1006 if (test_only) { 596 if (test_only) {
1007 ok &= test_output_config(oc, output); 597 ok &= test_output_config(oc, output);
@@ -1047,10 +637,10 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener,
1047 struct output_config *oc = new_output_config(output->wlr_output->name); 637 struct output_config *oc = new_output_config(output->wlr_output->name);
1048 switch (event->mode) { 638 switch (event->mode) {
1049 case ZWLR_OUTPUT_POWER_V1_MODE_OFF: 639 case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
1050 oc->dpms_state = DPMS_OFF; 640 oc->power = 0;
1051 break; 641 break;
1052 case ZWLR_OUTPUT_POWER_V1_MODE_ON: 642 case ZWLR_OUTPUT_POWER_V1_MODE_ON:
1053 oc->dpms_state = DPMS_ON; 643 oc->power = 1;
1054 break; 644 break;
1055 } 645 }
1056 oc = store_output_config(oc); 646 oc = store_output_config(oc);
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
deleted file mode 100644
index bd85282c..00000000
--- a/sway/desktop/render.c
+++ /dev/null
@@ -1,1125 +0,0 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h>
3#include <GLES2/gl2.h>
4#include <stdlib.h>
5#include <strings.h>
6#include <time.h>
7#include <wayland-server-core.h>
8#include <wlr/render/gles2.h>
9#include <wlr/render/wlr_renderer.h>
10#include <wlr/types/wlr_box.h>
11#include <wlr/types/wlr_buffer.h>
12#include <wlr/types/wlr_matrix.h>
13#include <wlr/types/wlr_output_damage.h>
14#include <wlr/types/wlr_output_layout.h>
15#include <wlr/types/wlr_output.h>
16#include <wlr/types/wlr_surface.h>
17#include <wlr/util/region.h>
18#include "log.h"
19#include "config.h"
20#include "sway/config.h"
21#include "sway/input/input-manager.h"
22#include "sway/input/seat.h"
23#include "sway/layers.h"
24#include "sway/output.h"
25#include "sway/server.h"
26#include "sway/tree/arrange.h"
27#include "sway/tree/container.h"
28#include "sway/tree/root.h"
29#include "sway/tree/view.h"
30#include "sway/tree/workspace.h"
31
32struct render_data {
33 pixman_region32_t *damage;
34 float alpha;
35};
36
37/**
38 * Apply scale to a width or height.
39 *
40 * One does not simply multiply the width by the scale. We allow fractional
41 * scaling, which means the resulting scaled width might be a decimal.
42 * So we round it.
43 *
44 * But even this can produce undesirable results depending on the X or Y offset
45 * of the box. For example, with a scale of 1.5, a box with width=1 should not
46 * scale to 2px if its X coordinate is 1, because the X coordinate would have
47 * scaled to 2px.
48 */
49static int scale_length(int length, int offset, float scale) {
50 return round((offset + length) * scale) - round(offset * scale);
51}
52
53static void scissor_output(struct wlr_output *wlr_output,
54 pixman_box32_t *rect) {
55 struct wlr_renderer *renderer = wlr_backend_get_renderer(wlr_output->backend);
56 assert(renderer);
57
58 struct wlr_box box = {
59 .x = rect->x1,
60 .y = rect->y1,
61 .width = rect->x2 - rect->x1,
62 .height = rect->y2 - rect->y1,
63 };
64
65 int ow, oh;
66 wlr_output_transformed_resolution(wlr_output, &ow, &oh);
67
68 enum wl_output_transform transform =
69 wlr_output_transform_invert(wlr_output->transform);
70 wlr_box_transform(&box, &box, transform, ow, oh);
71
72 wlr_renderer_scissor(renderer, &box);
73}
74
75static void set_scale_filter(struct wlr_output *wlr_output,
76 struct wlr_texture *texture, enum scale_filter_mode scale_filter) {
77 if (!wlr_texture_is_gles2(texture)) {
78 return;
79 }
80
81 struct wlr_gles2_texture_attribs attribs;
82 wlr_gles2_texture_get_attribs(texture, &attribs);
83
84 glBindTexture(attribs.target, attribs.tex);
85
86 switch (scale_filter) {
87 case SCALE_FILTER_LINEAR:
88 glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
89 break;
90 case SCALE_FILTER_NEAREST:
91 glTexParameteri(attribs.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
92 break;
93 case SCALE_FILTER_DEFAULT:
94 case SCALE_FILTER_SMART:
95 assert(false); // unreachable
96 }
97}
98
99static void render_texture(struct wlr_output *wlr_output,
100 pixman_region32_t *output_damage, struct wlr_texture *texture,
101 const struct wlr_fbox *src_box, const struct wlr_box *dst_box,
102 const float matrix[static 9], float alpha) {
103 struct wlr_renderer *renderer =
104 wlr_backend_get_renderer(wlr_output->backend);
105 struct sway_output *output = wlr_output->data;
106
107 struct wlr_gles2_texture_attribs attribs;
108 wlr_gles2_texture_get_attribs(texture, &attribs);
109
110 pixman_region32_t damage;
111 pixman_region32_init(&damage);
112 pixman_region32_union_rect(&damage, &damage, dst_box->x, dst_box->y,
113 dst_box->width, dst_box->height);
114 pixman_region32_intersect(&damage, &damage, output_damage);
115 bool damaged = pixman_region32_not_empty(&damage);
116 if (!damaged) {
117 goto damage_finish;
118 }
119
120 int nrects;
121 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
122 for (int i = 0; i < nrects; ++i) {
123 scissor_output(wlr_output, &rects[i]);
124 set_scale_filter(wlr_output, texture, output->scale_filter);
125 if (src_box != NULL) {
126 wlr_render_subtexture_with_matrix(renderer, texture, src_box, matrix, alpha);
127 } else {
128 wlr_render_texture_with_matrix(renderer, texture, matrix, alpha);
129 }
130 }
131
132damage_finish:
133 pixman_region32_fini(&damage);
134}
135
136static void render_surface_iterator(struct sway_output *output, struct sway_view *view,
137 struct wlr_surface *surface, struct wlr_box *_box, float rotation,
138 void *_data) {
139 struct render_data *data = _data;
140 struct wlr_output *wlr_output = output->wlr_output;
141 pixman_region32_t *output_damage = data->damage;
142 float alpha = data->alpha;
143
144 struct wlr_texture *texture = wlr_surface_get_texture(surface);
145 if (!texture) {
146 return;
147 }
148
149 struct wlr_fbox src_box;
150 wlr_surface_get_buffer_source_box(surface, &src_box);
151
152 struct wlr_box dst_box = *_box;
153 scale_box(&dst_box, wlr_output->scale);
154
155 float matrix[9];
156 enum wl_output_transform transform =
157 wlr_output_transform_invert(surface->current.transform);
158 wlr_matrix_project_box(matrix, &dst_box, transform, rotation,
159 wlr_output->transform_matrix);
160
161 render_texture(wlr_output, output_damage, texture,
162 &src_box, &dst_box, matrix, alpha);
163
164 wlr_presentation_surface_sampled_on_output(server.presentation, surface,
165 wlr_output);
166}
167
168static void render_layer_toplevel(struct sway_output *output,
169 pixman_region32_t *damage, struct wl_list *layer_surfaces) {
170 struct render_data data = {
171 .damage = damage,
172 .alpha = 1.0f,
173 };
174 output_layer_for_each_toplevel_surface(output, layer_surfaces,
175 render_surface_iterator, &data);
176}
177
178static void render_layer_popups(struct sway_output *output,
179 pixman_region32_t *damage, struct wl_list *layer_surfaces) {
180 struct render_data data = {
181 .damage = damage,
182 .alpha = 1.0f,
183 };
184 output_layer_for_each_popup_surface(output, layer_surfaces,
185 render_surface_iterator, &data);
186}
187
188#if HAVE_XWAYLAND
189static void render_unmanaged(struct sway_output *output,
190 pixman_region32_t *damage, struct wl_list *unmanaged) {
191 struct render_data data = {
192 .damage = damage,
193 .alpha = 1.0f,
194 };
195 output_unmanaged_for_each_surface(output, unmanaged,
196 render_surface_iterator, &data);
197}
198#endif
199
200static void render_drag_icons(struct sway_output *output,
201 pixman_region32_t *damage, struct wl_list *drag_icons) {
202 struct render_data data = {
203 .damage = damage,
204 .alpha = 1.0f,
205 };
206 output_drag_icons_for_each_surface(output, drag_icons,
207 render_surface_iterator, &data);
208}
209
210// _box.x and .y are expected to be layout-local
211// _box.width and .height are expected to be output-buffer-local
212void render_rect(struct sway_output *output,
213 pixman_region32_t *output_damage, const struct wlr_box *_box,
214 float color[static 4]) {
215 struct wlr_output *wlr_output = output->wlr_output;
216 struct wlr_renderer *renderer =
217 wlr_backend_get_renderer(wlr_output->backend);
218
219 struct wlr_box box;
220 memcpy(&box, _box, sizeof(struct wlr_box));
221 box.x -= output->lx * wlr_output->scale;
222 box.y -= output->ly * wlr_output->scale;
223
224 pixman_region32_t damage;
225 pixman_region32_init(&damage);
226 pixman_region32_union_rect(&damage, &damage, box.x, box.y,
227 box.width, box.height);
228 pixman_region32_intersect(&damage, &damage, output_damage);
229 bool damaged = pixman_region32_not_empty(&damage);
230 if (!damaged) {
231 goto damage_finish;
232 }
233
234 int nrects;
235 pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects);
236 for (int i = 0; i < nrects; ++i) {
237 scissor_output(wlr_output, &rects[i]);
238 wlr_render_rect(renderer, &box, color,
239 wlr_output->transform_matrix);
240 }
241
242damage_finish:
243 pixman_region32_fini(&damage);
244}
245
246void premultiply_alpha(float color[4], float opacity) {
247 color[3] *= opacity;
248 color[0] *= color[3];
249 color[1] *= color[3];
250 color[2] *= color[3];
251}
252
253static void render_view_toplevels(struct sway_view *view,
254 struct sway_output *output, pixman_region32_t *damage, float alpha) {
255 struct render_data data = {
256 .damage = damage,
257 .alpha = alpha,
258 };
259 // Render all toplevels without descending into popups
260 double ox = view->container->surface_x -
261 output->lx - view->geometry.x;
262 double oy = view->container->surface_y -
263 output->ly - view->geometry.y;
264 output_surface_for_each_surface(output, view->surface, ox, oy,
265 render_surface_iterator, &data);
266}
267
268static void render_view_popups(struct sway_view *view,
269 struct sway_output *output, pixman_region32_t *damage, float alpha) {
270 struct render_data data = {
271 .damage = damage,
272 .alpha = alpha,
273 };
274 output_view_for_each_popup_surface(output, view,
275 render_surface_iterator, &data);
276}
277
278static void render_saved_view(struct sway_view *view,
279 struct sway_output *output, pixman_region32_t *damage, float alpha) {
280 struct wlr_output *wlr_output = output->wlr_output;
281
282 if (wl_list_empty(&view->saved_buffers)) {
283 return;
284 }
285 struct sway_saved_buffer *saved_buf;
286 wl_list_for_each(saved_buf, &view->saved_buffers, link) {
287 if (!saved_buf->buffer->texture) {
288 continue;
289 }
290
291 struct wlr_box box = {
292 .x = view->container->surface_x - output->lx -
293 view->saved_geometry.x + saved_buf->x,
294 .y = view->container->surface_y - output->ly -
295 view->saved_geometry.y + saved_buf->y,
296 .width = saved_buf->width,
297 .height = saved_buf->height,
298 };
299
300 struct wlr_box output_box = {
301 .width = output->width,
302 .height = output->height,
303 };
304
305 struct wlr_box intersection;
306 bool intersects = wlr_box_intersection(&intersection, &output_box, &box);
307 if (!intersects) {
308 continue;
309 }
310
311 scale_box(&box, wlr_output->scale);
312
313 float matrix[9];
314 enum wl_output_transform transform = wlr_output_transform_invert(saved_buf->transform);
315 wlr_matrix_project_box(matrix, &box, transform, 0,
316 wlr_output->transform_matrix);
317
318 render_texture(wlr_output, damage, saved_buf->buffer->texture,
319 &saved_buf->source_box, &box, matrix, alpha);
320 }
321
322 // FIXME: we should set the surface that this saved buffer originates from
323 // as sampled here.
324 // https://github.com/swaywm/sway/pull/4465#discussion_r321082059
325}
326
327/**
328 * Render a view's surface and left/bottom/right borders.
329 */
330static void render_view(struct sway_output *output, pixman_region32_t *damage,
331 struct sway_container *con, struct border_colors *colors) {
332 struct sway_view *view = con->view;
333 if (!wl_list_empty(&view->saved_buffers)) {
334 render_saved_view(view, output, damage, view->container->alpha);
335 } else if (view->surface) {
336 render_view_toplevels(view, output, damage, view->container->alpha);
337 }
338
339 if (con->current.border == B_NONE || con->current.border == B_CSD) {
340 return;
341 }
342
343 struct wlr_box box;
344 float output_scale = output->wlr_output->scale;
345 float color[4];
346 struct sway_container_state *state = &con->current;
347
348 if (state->border_left) {
349 memcpy(&color, colors->child_border, sizeof(float) * 4);
350 premultiply_alpha(color, con->alpha);
351 box.x = state->x;
352 box.y = state->content_y;
353 box.width = state->border_thickness;
354 box.height = state->content_height;
355 scale_box(&box, output_scale);
356 render_rect(output, damage, &box, color);
357 }
358
359 list_t *siblings = container_get_current_siblings(con);
360 enum sway_container_layout layout =
361 container_current_parent_layout(con);
362
363 if (state->border_right) {
364 if (!container_is_floating(con) && siblings->length == 1 && layout == L_HORIZ) {
365 memcpy(&color, colors->indicator, sizeof(float) * 4);
366 } else {
367 memcpy(&color, colors->child_border, sizeof(float) * 4);
368 }
369 premultiply_alpha(color, con->alpha);
370 box.x = state->content_x + state->content_width;
371 box.y = state->content_y;
372 box.width = state->border_thickness;
373 box.height = state->content_height;
374 scale_box(&box, output_scale);
375 render_rect(output, damage, &box, color);
376 }
377
378 if (state->border_bottom) {
379 if (!container_is_floating(con) && siblings->length == 1 && layout == L_VERT) {
380 memcpy(&color, colors->indicator, sizeof(float) * 4);
381 } else {
382 memcpy(&color, colors->child_border, sizeof(float) * 4);
383 }
384 premultiply_alpha(color, con->alpha);
385 box.x = state->x;
386 box.y = state->content_y + state->content_height;
387 box.width = state->width;
388 box.height = state->border_thickness;
389 scale_box(&box, output_scale);
390 render_rect(output, damage, &box, color);
391 }
392}
393
394/**
395 * Render a titlebar.
396 *
397 * Care must be taken not to render over the same pixel multiple times,
398 * otherwise the colors will be incorrect when using opacity.
399 *
400 * The height is: 1px border, 3px padding, font height, 3px padding, 1px border
401 * The left side is: 1px border, 2px padding, title
402 */
403static void render_titlebar(struct sway_output *output,
404 pixman_region32_t *output_damage, struct sway_container *con,
405 int x, int y, int width,
406 struct border_colors *colors, struct wlr_texture *title_texture,
407 struct wlr_texture *marks_texture) {
408 struct wlr_box box;
409 float color[4];
410 float output_scale = output->wlr_output->scale;
411 double output_x = output->lx;
412 double output_y = output->ly;
413 int titlebar_border_thickness = config->titlebar_border_thickness;
414 int titlebar_h_padding = config->titlebar_h_padding;
415 int titlebar_v_padding = config->titlebar_v_padding;
416 enum alignment title_align = config->title_align;
417
418 // Single pixel bar above title
419 memcpy(&color, colors->border, sizeof(float) * 4);
420 premultiply_alpha(color, con->alpha);
421 box.x = x;
422 box.y = y;
423 box.width = width;
424 box.height = titlebar_border_thickness;
425 scale_box(&box, output_scale);
426 render_rect(output, output_damage, &box, color);
427
428 // Single pixel bar below title
429 box.x = x;
430 box.y = y + container_titlebar_height() - titlebar_border_thickness;
431 box.width = width;
432 box.height = titlebar_border_thickness;
433 scale_box(&box, output_scale);
434 render_rect(output, output_damage, &box, color);
435
436 // Single pixel left edge
437 box.x = x;
438 box.y = y + titlebar_border_thickness;
439 box.width = titlebar_border_thickness;
440 box.height = container_titlebar_height() - titlebar_border_thickness * 2;
441 scale_box(&box, output_scale);
442 render_rect(output, output_damage, &box, color);
443
444 // Single pixel right edge
445 box.x = x + width - titlebar_border_thickness;
446 box.y = y + titlebar_border_thickness;
447 box.width = titlebar_border_thickness;
448 box.height = container_titlebar_height() - titlebar_border_thickness * 2;
449 scale_box(&box, output_scale);
450 render_rect(output, output_damage, &box, color);
451
452 int inner_x = x - output_x + titlebar_h_padding;
453 int bg_y = y + titlebar_border_thickness;
454 size_t inner_width = width - titlebar_h_padding * 2;
455
456 // output-buffer local
457 int ob_inner_x = round(inner_x * output_scale);
458 int ob_inner_width = scale_length(inner_width, inner_x, output_scale);
459 int ob_bg_height = scale_length(
460 (titlebar_v_padding - titlebar_border_thickness) * 2 +
461 config->font_height, bg_y, output_scale);
462
463 // Marks
464 int ob_marks_x = 0; // output-buffer-local
465 int ob_marks_width = 0; // output-buffer-local
466 if (config->show_marks && marks_texture) {
467 struct wlr_box texture_box;
468 wlr_texture_get_size(marks_texture,
469 &texture_box.width, &texture_box.height);
470 ob_marks_width = texture_box.width;
471
472 // The marks texture might be shorter than the config->font_height, in
473 // which case we need to pad it as evenly as possible above and below.
474 int ob_padding_total = ob_bg_height - texture_box.height;
475 int ob_padding_above = floor(ob_padding_total / 2.0);
476 int ob_padding_below = ceil(ob_padding_total / 2.0);
477
478 // Render texture. If the title is on the right, the marks will be on
479 // the left. Otherwise, they will be on the right.
480 if (title_align == ALIGN_RIGHT || texture_box.width > ob_inner_width) {
481 texture_box.x = ob_inner_x;
482 } else {
483 texture_box.x = ob_inner_x + ob_inner_width - texture_box.width;
484 }
485 ob_marks_x = texture_box.x;
486
487 texture_box.y = round((bg_y - output_y) * output_scale) +
488 ob_padding_above;
489
490 float matrix[9];
491 wlr_matrix_project_box(matrix, &texture_box,
492 WL_OUTPUT_TRANSFORM_NORMAL,
493 0.0, output->wlr_output->transform_matrix);
494
495 if (ob_inner_width < texture_box.width) {
496 texture_box.width = ob_inner_width;
497 }
498 render_texture(output->wlr_output, output_damage, marks_texture,
499 NULL, &texture_box, matrix, con->alpha);
500
501 // Padding above
502 memcpy(&color, colors->background, sizeof(float) * 4);
503 premultiply_alpha(color, con->alpha);
504 box.x = texture_box.x + round(output_x * output_scale);
505 box.y = round((y + titlebar_border_thickness) * output_scale);
506 box.width = texture_box.width;
507 box.height = ob_padding_above;
508 render_rect(output, output_damage, &box, color);
509
510 // Padding below
511 box.y += ob_padding_above + texture_box.height;
512 box.height = ob_padding_below;
513 render_rect(output, output_damage, &box, color);
514 }
515
516 // Title text
517 int ob_title_x = 0; // output-buffer-local
518 int ob_title_width = 0; // output-buffer-local
519 if (title_texture) {
520 struct wlr_box texture_box;
521 wlr_texture_get_size(title_texture,
522 &texture_box.width, &texture_box.height);
523 ob_title_width = texture_box.width;
524
525 // The title texture might be shorter than the config->font_height,
526 // in which case we need to pad it above and below.
527 int ob_padding_above = round((config->font_baseline -
528 con->title_baseline + titlebar_v_padding -
529 titlebar_border_thickness) * output_scale);
530 int ob_padding_below = ob_bg_height - ob_padding_above -
531 texture_box.height;
532
533 // Render texture
534 if (texture_box.width > ob_inner_width - ob_marks_width) {
535 texture_box.x = (title_align == ALIGN_RIGHT && ob_marks_width)
536 ? ob_marks_x + ob_marks_width : ob_inner_x;
537 } else if (title_align == ALIGN_LEFT) {
538 texture_box.x = ob_inner_x;
539 } else if (title_align == ALIGN_CENTER) {
540 // If there are marks visible, center between the edge and marks.
541 // Otherwise, center in the inner area.
542 if (ob_marks_width) {
543 texture_box.x = (ob_inner_x + ob_marks_x) / 2
544 - texture_box.width / 2;
545 } else {
546 texture_box.x = ob_inner_x + ob_inner_width / 2
547 - texture_box.width / 2;
548 }
549 } else {
550 texture_box.x = ob_inner_x + ob_inner_width - texture_box.width;
551 }
552 ob_title_x = texture_box.x;
553
554 texture_box.y =
555 round((bg_y - output_y) * output_scale) + ob_padding_above;
556
557 float matrix[9];
558 wlr_matrix_project_box(matrix, &texture_box,
559 WL_OUTPUT_TRANSFORM_NORMAL,
560 0.0, output->wlr_output->transform_matrix);
561
562 if (ob_inner_width - ob_marks_width < texture_box.width) {
563 texture_box.width = ob_inner_width - ob_marks_width;
564 }
565
566 render_texture(output->wlr_output, output_damage, title_texture,
567 NULL, &texture_box, matrix, con->alpha);
568
569 // Padding above
570 memcpy(&color, colors->background, sizeof(float) * 4);
571 premultiply_alpha(color, con->alpha);
572 box.x = texture_box.x + round(output_x * output_scale);
573 box.y = round((y + titlebar_border_thickness) * output_scale);
574 box.width = texture_box.width;
575 box.height = ob_padding_above;
576 render_rect(output, output_damage, &box, color);
577
578 // Padding below
579 box.y += ob_padding_above + texture_box.height;
580 box.height = ob_padding_below;
581 render_rect(output, output_damage, &box, color);
582 }
583
584 // Determine the left + right extends of the textures (output-buffer local)
585 int ob_left_x, ob_left_width, ob_right_x, ob_right_width;
586 if (ob_title_width == 0 && ob_marks_width == 0) {
587 ob_left_x = ob_inner_x;
588 ob_left_width = 0;
589 ob_right_x = ob_inner_x;
590 ob_right_width = 0;
591 } else if (ob_title_x < ob_marks_x) {
592 ob_left_x = ob_title_x;
593 ob_left_width = ob_title_width;
594 ob_right_x = ob_marks_x;
595 ob_right_width = ob_marks_width;
596 } else {
597 ob_left_x = ob_marks_x;
598 ob_left_width = ob_marks_width;
599 ob_right_x = ob_title_x;
600 ob_right_width = ob_title_width;
601 }
602 if (ob_left_x < ob_inner_x) {
603 ob_left_x = ob_inner_x;
604 } else if (ob_left_x + ob_left_width > ob_right_x + ob_right_width) {
605 ob_right_x = ob_left_x;
606 ob_right_width = ob_left_width;
607 }
608
609 // Filler between title and marks
610 box.width = ob_right_x - ob_left_x - ob_left_width;
611 if (box.width > 0) {
612 box.x = ob_left_x + ob_left_width + round(output_x * output_scale);
613 box.y = round(bg_y * output_scale);
614 box.height = ob_bg_height;
615 render_rect(output, output_damage, &box, color);
616 }
617
618 // Padding on left side
619 box.x = x + titlebar_border_thickness;
620 box.y = y + titlebar_border_thickness;
621 box.width = titlebar_h_padding - titlebar_border_thickness;
622 box.height = (titlebar_v_padding - titlebar_border_thickness) * 2 +
623 config->font_height;
624 scale_box(&box, output_scale);
625 int left_x = ob_left_x + round(output_x * output_scale);
626 if (box.x + box.width < left_x) {
627 box.width += left_x - box.x - box.width;
628 }
629 render_rect(output, output_damage, &box, color);
630
631 // Padding on right side
632 box.x = x + width - titlebar_h_padding;
633 box.y = y + titlebar_border_thickness;
634 box.width = titlebar_h_padding - titlebar_border_thickness;
635 box.height = (titlebar_v_padding - titlebar_border_thickness) * 2 +
636 config->font_height;
637 scale_box(&box, output_scale);
638 int right_rx = ob_right_x + ob_right_width + round(output_x * output_scale);
639 if (right_rx < box.x) {
640 box.width += box.x - right_rx;
641 box.x = right_rx;
642 }
643 render_rect(output, output_damage, &box, color);
644}
645
646/**
647 * Render the top border line for a view using "border pixel".
648 */
649static void render_top_border(struct sway_output *output,
650 pixman_region32_t *output_damage, struct sway_container *con,
651 struct border_colors *colors) {
652 struct sway_container_state *state = &con->current;
653 if (!state->border_top) {
654 return;
655 }
656 struct wlr_box box;
657 float color[4];
658 float output_scale = output->wlr_output->scale;
659
660 // Child border - top edge
661 memcpy(&color, colors->child_border, sizeof(float) * 4);
662 premultiply_alpha(color, con->alpha);
663 box.x = state->x;
664 box.y = state->y;
665 box.width = state->width;
666 box.height = state->border_thickness;
667 scale_box(&box, output_scale);
668 render_rect(output, output_damage, &box, color);
669}
670
671struct parent_data {
672 enum sway_container_layout layout;
673 struct wlr_box box;
674 list_t *children;
675 bool focused;
676 struct sway_container *active_child;
677};
678
679static void render_container(struct sway_output *output,
680 pixman_region32_t *damage, struct sway_container *con, bool parent_focused);
681
682/**
683 * Render a container's children using a L_HORIZ or L_VERT layout.
684 *
685 * Wrap child views in borders and leave child containers borderless because
686 * they'll apply their own borders to their children.
687 */
688static void render_containers_linear(struct sway_output *output,
689 pixman_region32_t *damage, struct parent_data *parent) {
690 for (int i = 0; i < parent->children->length; ++i) {
691 struct sway_container *child = parent->children->items[i];
692
693 if (child->view) {
694 struct sway_view *view = child->view;
695 struct border_colors *colors;
696 struct wlr_texture *title_texture;
697 struct wlr_texture *marks_texture;
698 struct sway_container_state *state = &child->current;
699
700 if (view_is_urgent(view)) {
701 colors = &config->border_colors.urgent;
702 title_texture = child->title_urgent;
703 marks_texture = child->marks_urgent;
704 } else if (state->focused || parent->focused) {
705 colors = &config->border_colors.focused;
706 title_texture = child->title_focused;
707 marks_texture = child->marks_focused;
708 } else if (child == parent->active_child) {
709 colors = &config->border_colors.focused_inactive;
710 title_texture = child->title_focused_inactive;
711 marks_texture = child->marks_focused_inactive;
712 } else {
713 colors = &config->border_colors.unfocused;
714 title_texture = child->title_unfocused;
715 marks_texture = child->marks_unfocused;
716 }
717
718 if (state->border == B_NORMAL) {
719 render_titlebar(output, damage, child, state->x,
720 state->y, state->width, colors,
721 title_texture, marks_texture);
722 } else if (state->border == B_PIXEL) {
723 render_top_border(output, damage, child, colors);
724 }
725 render_view(output, damage, child, colors);
726 } else {
727 render_container(output, damage, child,
728 parent->focused || child->current.focused);
729 }
730 }
731}
732
733/**
734 * Render a container's children using the L_TABBED layout.
735 */
736static void render_containers_tabbed(struct sway_output *output,
737 pixman_region32_t *damage, struct parent_data *parent) {
738 if (!parent->children->length) {
739 return;
740 }
741 struct sway_container *current = parent->active_child;
742 struct border_colors *current_colors = &config->border_colors.unfocused;
743 int tab_width = parent->box.width / parent->children->length;
744
745 // Render tabs
746 for (int i = 0; i < parent->children->length; ++i) {
747 struct sway_container *child = parent->children->items[i];
748 struct sway_view *view = child->view;
749 struct sway_container_state *cstate = &child->current;
750 struct border_colors *colors;
751 struct wlr_texture *title_texture;
752 struct wlr_texture *marks_texture;
753 bool urgent = view ?
754 view_is_urgent(view) : container_has_urgent_child(child);
755
756 if (urgent) {
757 colors = &config->border_colors.urgent;
758 title_texture = child->title_urgent;
759 marks_texture = child->marks_urgent;
760 } else if (cstate->focused || parent->focused) {
761 colors = &config->border_colors.focused;
762 title_texture = child->title_focused;
763 marks_texture = child->marks_focused;
764 } else if (child == parent->active_child) {
765 colors = &config->border_colors.focused_inactive;
766 title_texture = child->title_focused_inactive;
767 marks_texture = child->marks_focused_inactive;
768 } else {
769 colors = &config->border_colors.unfocused;
770 title_texture = child->title_unfocused;
771 marks_texture = child->marks_unfocused;
772 }
773
774 int x = cstate->x + tab_width * i;
775
776 // Make last tab use the remaining width of the parent
777 if (i == parent->children->length - 1) {
778 tab_width = parent->box.width - tab_width * i;
779 }
780
781 render_titlebar(output, damage, child, x, parent->box.y, tab_width,
782 colors, title_texture, marks_texture);
783
784 if (child == current) {
785 current_colors = colors;
786 }
787 }
788
789 // Render surface and left/right/bottom borders
790 if (current->view) {
791 render_view(output, damage, current, current_colors);
792 } else {
793 render_container(output, damage, current,
794 parent->focused || current->current.focused);
795 }
796}
797
798/**
799 * Render a container's children using the L_STACKED layout.
800 */
801static void render_containers_stacked(struct sway_output *output,
802 pixman_region32_t *damage, struct parent_data *parent) {
803 if (!parent->children->length) {
804 return;
805 }
806 struct sway_container *current = parent->active_child;
807 struct border_colors *current_colors = &config->border_colors.unfocused;
808 size_t titlebar_height = container_titlebar_height();
809
810 // Render titles
811 for (int i = 0; i < parent->children->length; ++i) {
812 struct sway_container *child = parent->children->items[i];
813 struct sway_view *view = child->view;
814 struct sway_container_state *cstate = &child->current;
815 struct border_colors *colors;
816 struct wlr_texture *title_texture;
817 struct wlr_texture *marks_texture;
818 bool urgent = view ?
819 view_is_urgent(view) : container_has_urgent_child(child);
820
821 if (urgent) {
822 colors = &config->border_colors.urgent;
823 title_texture = child->title_urgent;
824 marks_texture = child->marks_urgent;
825 } else if (cstate->focused || parent->focused) {
826 colors = &config->border_colors.focused;
827 title_texture = child->title_focused;
828 marks_texture = child->marks_focused;
829 } else if (child == parent->active_child) {
830 colors = &config->border_colors.focused_inactive;
831 title_texture = child->title_focused_inactive;
832 marks_texture = child->marks_focused_inactive;
833 } else {
834 colors = &config->border_colors.unfocused;
835 title_texture = child->title_unfocused;
836 marks_texture = child->marks_unfocused;
837 }
838
839 int y = parent->box.y + titlebar_height * i;
840 render_titlebar(output, damage, child, parent->box.x, y,
841 parent->box.width, colors, title_texture, marks_texture);
842
843 if (child == current) {
844 current_colors = colors;
845 }
846 }
847
848 // Render surface and left/right/bottom borders
849 if (current->view) {
850 render_view(output, damage, current, current_colors);
851 } else {
852 render_container(output, damage, current,
853 parent->focused || current->current.focused);
854 }
855}
856
857static void render_containers(struct sway_output *output,
858 pixman_region32_t *damage, struct parent_data *parent) {
859 if (config->hide_lone_tab && parent->children->length == 1) {
860 struct sway_container *child = parent->children->items[0];
861 if (child->view) {
862 render_containers_linear(output,damage, parent);
863 return;
864 }
865 }
866
867 switch (parent->layout) {
868 case L_NONE:
869 case L_HORIZ:
870 case L_VERT:
871 render_containers_linear(output, damage, parent);
872 break;
873 case L_STACKED:
874 render_containers_stacked(output, damage, parent);
875 break;
876 case L_TABBED:
877 render_containers_tabbed(output, damage, parent);
878 break;
879 }
880}
881
882static void render_container(struct sway_output *output,
883 pixman_region32_t *damage, struct sway_container *con, bool focused) {
884 struct parent_data data = {
885 .layout = con->current.layout,
886 .box = {
887 .x = con->current.x,
888 .y = con->current.y,
889 .width = con->current.width,
890 .height = con->current.height,
891 },
892 .children = con->current.children,
893 .focused = focused,
894 .active_child = con->current.focused_inactive_child,
895 };
896 render_containers(output, damage, &data);
897}
898
899static void render_workspace(struct sway_output *output,
900 pixman_region32_t *damage, struct sway_workspace *ws, bool focused) {
901 struct parent_data data = {
902 .layout = ws->current.layout,
903 .box = {
904 .x = ws->current.x,
905 .y = ws->current.y,
906 .width = ws->current.width,
907 .height = ws->current.height,
908 },
909 .children = ws->current.tiling,
910 .focused = focused,
911 .active_child = ws->current.focused_inactive_child,
912 };
913 render_containers(output, damage, &data);
914}
915
916static void render_floating_container(struct sway_output *soutput,
917 pixman_region32_t *damage, struct sway_container *con) {
918 if (con->view) {
919 struct sway_view *view = con->view;
920 struct border_colors *colors;
921 struct wlr_texture *title_texture;
922 struct wlr_texture *marks_texture;
923
924 if (view_is_urgent(view)) {
925 colors = &config->border_colors.urgent;
926 title_texture = con->title_urgent;
927 marks_texture = con->marks_urgent;
928 } else if (con->current.focused) {
929 colors = &config->border_colors.focused;
930 title_texture = con->title_focused;
931 marks_texture = con->marks_focused;
932 } else {
933 colors = &config->border_colors.unfocused;
934 title_texture = con->title_unfocused;
935 marks_texture = con->marks_unfocused;
936 }
937
938 if (con->current.border == B_NORMAL) {
939 render_titlebar(soutput, damage, con, con->current.x,
940 con->current.y, con->current.width, colors,
941 title_texture, marks_texture);
942 } else if (con->current.border == B_PIXEL) {
943 render_top_border(soutput, damage, con, colors);
944 }
945 render_view(soutput, damage, con, colors);
946 } else {
947 render_container(soutput, damage, con, con->current.focused);
948 }
949}
950
951static void render_floating(struct sway_output *soutput,
952 pixman_region32_t *damage) {
953 for (int i = 0; i < root->outputs->length; ++i) {
954 struct sway_output *output = root->outputs->items[i];
955 for (int j = 0; j < output->current.workspaces->length; ++j) {
956 struct sway_workspace *ws = output->current.workspaces->items[j];
957 if (!workspace_is_visible(ws)) {
958 continue;
959 }
960 for (int k = 0; k < ws->current.floating->length; ++k) {
961 struct sway_container *floater = ws->current.floating->items[k];
962 if (floater->fullscreen_mode != FULLSCREEN_NONE) {
963 continue;
964 }
965 render_floating_container(soutput, damage, floater);
966 }
967 }
968 }
969}
970
971static void render_seatops(struct sway_output *output,
972 pixman_region32_t *damage) {
973 struct sway_seat *seat;
974 wl_list_for_each(seat, &server.input->seats, link) {
975 seatop_render(seat, output, damage);
976 }
977}
978
979void output_render(struct sway_output *output, struct timespec *when,
980 pixman_region32_t *damage) {
981 struct wlr_output *wlr_output = output->wlr_output;
982
983 struct wlr_renderer *renderer =
984 wlr_backend_get_renderer(wlr_output->backend);
985 if (!sway_assert(renderer != NULL,
986 "expected the output backend to have a renderer")) {
987 return;
988 }
989
990 struct sway_workspace *workspace = output->current.active_workspace;
991 if (workspace == NULL) {
992 return;
993 }
994
995 struct sway_container *fullscreen_con = root->fullscreen_global;
996 if (!fullscreen_con) {
997 fullscreen_con = workspace->current.fullscreen;
998 }
999
1000 wlr_renderer_begin(renderer, wlr_output->width, wlr_output->height);
1001
1002 if (!pixman_region32_not_empty(damage)) {
1003 // Output isn't damaged but needs buffer swap
1004 goto renderer_end;
1005 }
1006
1007 if (debug.damage == DAMAGE_HIGHLIGHT) {
1008 wlr_renderer_clear(renderer, (float[]){1, 1, 0, 1});
1009 } else if (debug.damage == DAMAGE_RERENDER) {
1010 int width, height;
1011 wlr_output_transformed_resolution(wlr_output, &width, &height);
1012 pixman_region32_union_rect(damage, damage, 0, 0, width, height);
1013 }
1014
1015 if (output_has_opaque_overlay_layer_surface(output)) {
1016 goto render_overlay;
1017 }
1018
1019 if (fullscreen_con) {
1020 float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f};
1021
1022 int nrects;
1023 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
1024 for (int i = 0; i < nrects; ++i) {
1025 scissor_output(wlr_output, &rects[i]);
1026 wlr_renderer_clear(renderer, clear_color);
1027 }
1028
1029 if (fullscreen_con->view) {
1030 if (!wl_list_empty(&fullscreen_con->view->saved_buffers)) {
1031 render_saved_view(fullscreen_con->view, output, damage, 1.0f);
1032 } else if (fullscreen_con->view->surface) {
1033 render_view_toplevels(fullscreen_con->view,
1034 output, damage, 1.0f);
1035 }
1036 } else {
1037 render_container(output, damage, fullscreen_con,
1038 fullscreen_con->current.focused);
1039 }
1040
1041 for (int i = 0; i < workspace->current.floating->length; ++i) {
1042 struct sway_container *floater =
1043 workspace->current.floating->items[i];
1044 if (container_is_transient_for(floater, fullscreen_con)) {
1045 render_floating_container(output, damage, floater);
1046 }
1047 }
1048#if HAVE_XWAYLAND
1049 render_unmanaged(output, damage, &root->xwayland_unmanaged);
1050#endif
1051 } else {
1052 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
1053
1054 int nrects;
1055 pixman_box32_t *rects = pixman_region32_rectangles(damage, &nrects);
1056 for (int i = 0; i < nrects; ++i) {
1057 scissor_output(wlr_output, &rects[i]);
1058 wlr_renderer_clear(renderer, clear_color);
1059 }
1060
1061 render_layer_toplevel(output, damage,
1062 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
1063 render_layer_toplevel(output, damage,
1064 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
1065
1066 render_workspace(output, damage, workspace, workspace->current.focused);
1067 render_floating(output, damage);
1068#if HAVE_XWAYLAND
1069 render_unmanaged(output, damage, &root->xwayland_unmanaged);
1070#endif
1071 render_layer_toplevel(output, damage,
1072 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
1073
1074 render_layer_popups(output, damage,
1075 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]);
1076 render_layer_popups(output, damage,
1077 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]);
1078 render_layer_popups(output, damage,
1079 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
1080 }
1081
1082 render_seatops(output, damage);
1083
1084 struct sway_seat *seat = input_manager_current_seat();
1085 struct sway_container *focus = seat_get_focused_container(seat);
1086 if (focus && focus->view) {
1087 render_view_popups(focus->view, output, damage, focus->alpha);
1088 }
1089
1090render_overlay:
1091 render_layer_toplevel(output, damage,
1092 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
1093 render_layer_popups(output, damage,
1094 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
1095 render_drag_icons(output, damage, &root->drag_icons);
1096
1097renderer_end:
1098 wlr_renderer_scissor(renderer, NULL);
1099 wlr_output_render_software_cursors(wlr_output, damage);
1100 wlr_renderer_end(renderer);
1101
1102 int width, height;
1103 wlr_output_transformed_resolution(wlr_output, &width, &height);
1104
1105 pixman_region32_t frame_damage;
1106 pixman_region32_init(&frame_damage);
1107
1108 enum wl_output_transform transform =
1109 wlr_output_transform_invert(wlr_output->transform);
1110 wlr_region_transform(&frame_damage, &output->damage->current,
1111 transform, width, height);
1112
1113 if (debug.damage == DAMAGE_HIGHLIGHT) {
1114 pixman_region32_union_rect(&frame_damage, &frame_damage,
1115 0, 0, wlr_output->width, wlr_output->height);
1116 }
1117
1118 wlr_output_set_damage(wlr_output, &frame_damage);
1119 pixman_region32_fini(&frame_damage);
1120
1121 if (!wlr_output_commit(wlr_output)) {
1122 return;
1123 }
1124 output->last_frame = *when;
1125}
diff --git a/sway/desktop/surface.c b/sway/desktop/surface.c
deleted file mode 100644
index 767b2045..00000000
--- a/sway/desktop/surface.c
+++ /dev/null
@@ -1,46 +0,0 @@
1#define _POSIX_C_SOURCE 200112L
2#include <stdlib.h>
3#include <time.h>
4#include <wlr/types/wlr_surface.h>
5#include "sway/server.h"
6#include "sway/surface.h"
7
8static void handle_destroy(struct wl_listener *listener, void *data) {
9 struct sway_surface *surface = wl_container_of(listener, surface, destroy);
10
11 surface->wlr_surface->data = NULL;
12 wl_list_remove(&surface->destroy.link);
13
14 if (surface->frame_done_timer) {
15 wl_event_source_remove(surface->frame_done_timer);
16 }
17
18 free(surface);
19}
20
21static int surface_frame_done_timer_handler(void *data) {
22 struct sway_surface *surface = data;
23
24 struct timespec now;
25 clock_gettime(CLOCK_MONOTONIC, &now);
26 wlr_surface_send_frame_done(surface->wlr_surface, &now);
27
28 return 0;
29}
30
31void handle_compositor_new_surface(struct wl_listener *listener, void *data) {
32 struct wlr_surface *wlr_surface = data;
33
34 struct sway_surface *surface = calloc(1, sizeof(struct sway_surface));
35 surface->wlr_surface = wlr_surface;
36 wlr_surface->data = surface;
37
38 surface->destroy.notify = handle_destroy;
39 wl_signal_add(&wlr_surface->events.destroy, &surface->destroy);
40
41 surface->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop,
42 surface_frame_done_timer_handler, surface);
43 if (!surface->frame_done_timer) {
44 wl_resource_post_no_memory(wlr_surface->resource);
45 }
46}
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index eac38991..acc3e3f9 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -5,7 +5,7 @@
5#include <time.h> 5#include <time.h>
6#include <wlr/types/wlr_buffer.h> 6#include <wlr/types/wlr_buffer.h>
7#include "sway/config.h" 7#include "sway/config.h"
8#include "sway/desktop.h" 8#include "sway/scene_descriptor.h"
9#include "sway/desktop/idle_inhibit_v1.h" 9#include "sway/desktop/idle_inhibit_v1.h"
10#include "sway/desktop/transaction.h" 10#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 11#include "sway/input/cursor.h"
@@ -35,6 +35,8 @@ struct sway_transaction_instruction {
35 struct sway_container_state container_state; 35 struct sway_container_state container_state;
36 }; 36 };
37 uint32_t serial; 37 uint32_t serial;
38 bool server_request;
39 bool waiting;
38}; 40};
39 41
40static struct sway_transaction *transaction_create(void) { 42static struct sway_transaction *transaction_create(void) {
@@ -86,7 +88,11 @@ static void transaction_destroy(struct sway_transaction *transaction) {
86static void copy_output_state(struct sway_output *output, 88static void copy_output_state(struct sway_output *output,
87 struct sway_transaction_instruction *instruction) { 89 struct sway_transaction_instruction *instruction) {
88 struct sway_output_state *state = &instruction->output_state; 90 struct sway_output_state *state = &instruction->output_state;
89 state->workspaces = create_list(); 91 if (state->workspaces) {
92 state->workspaces->length = 0;
93 } else {
94 state->workspaces = create_list();
95 }
90 list_cat(state->workspaces, output->workspaces); 96 list_cat(state->workspaces, output->workspaces);
91 97
92 state->active_workspace = output_get_active_workspace(output); 98 state->active_workspace = output_get_active_workspace(output);
@@ -104,8 +110,16 @@ static void copy_workspace_state(struct sway_workspace *ws,
104 state->layout = ws->layout; 110 state->layout = ws->layout;
105 111
106 state->output = ws->output; 112 state->output = ws->output;
107 state->floating = create_list(); 113 if (state->floating) {
108 state->tiling = create_list(); 114 state->floating->length = 0;
115 } else {
116 state->floating = create_list();
117 }
118 if (state->tiling) {
119 state->tiling->length = 0;
120 } else {
121 state->tiling = create_list();
122 }
109 list_cat(state->floating, ws->floating); 123 list_cat(state->floating, ws->floating);
110 list_cat(state->tiling, ws->tiling); 124 list_cat(state->tiling, ws->tiling);
111 125
@@ -115,8 +129,8 @@ static void copy_workspace_state(struct sway_workspace *ws,
115 // Set focused_inactive_child to the direct tiling child 129 // Set focused_inactive_child to the direct tiling child
116 struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws); 130 struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws);
117 if (focus) { 131 if (focus) {
118 while (focus->parent) { 132 while (focus->pending.parent) {
119 focus = focus->parent; 133 focus = focus->pending.parent;
120 } 134 }
121 } 135 }
122 state->focused_inactive_child = focus; 136 state->focused_inactive_child = focus;
@@ -126,28 +140,19 @@ static void copy_container_state(struct sway_container *container,
126 struct sway_transaction_instruction *instruction) { 140 struct sway_transaction_instruction *instruction) {
127 struct sway_container_state *state = &instruction->container_state; 141 struct sway_container_state *state = &instruction->container_state;
128 142
129 state->layout = container->layout; 143 if (state->children) {
130 state->x = container->x; 144 list_free(state->children);
131 state->y = container->y; 145 }
132 state->width = container->width; 146
133 state->height = container->height; 147 memcpy(state, &container->pending, sizeof(struct sway_container_state));
134 state->fullscreen_mode = container->fullscreen_mode;
135 state->parent = container->parent;
136 state->workspace = container->workspace;
137 state->border = container->border;
138 state->border_thickness = container->border_thickness;
139 state->border_top = container->border_top;
140 state->border_left = container->border_left;
141 state->border_right = container->border_right;
142 state->border_bottom = container->border_bottom;
143 state->content_x = container->content_x;
144 state->content_y = container->content_y;
145 state->content_width = container->content_width;
146 state->content_height = container->content_height;
147 148
148 if (!container->view) { 149 if (!container->view) {
150 // We store a copy of the child list to avoid having it mutated after
151 // we copy the state.
149 state->children = create_list(); 152 state->children = create_list();
150 list_cat(state->children, container->children); 153 list_cat(state->children, container->pending.children);
154 } else {
155 state->children = NULL;
151 } 156 }
152 157
153 struct sway_seat *seat = input_manager_current_seat(); 158 struct sway_seat *seat = input_manager_current_seat();
@@ -161,14 +166,36 @@ static void copy_container_state(struct sway_container *container,
161} 166}
162 167
163static void transaction_add_node(struct sway_transaction *transaction, 168static void transaction_add_node(struct sway_transaction *transaction,
164 struct sway_node *node) { 169 struct sway_node *node, bool server_request) {
165 struct sway_transaction_instruction *instruction = 170 struct sway_transaction_instruction *instruction = NULL;
166 calloc(1, sizeof(struct sway_transaction_instruction)); 171
167 if (!sway_assert(instruction, "Unable to allocate instruction")) { 172 // Check if we have an instruction for this node already, in which case we
168 return; 173 // update that instead of creating a new one.
174 if (node->ntxnrefs > 0) {
175 for (int idx = 0; idx < transaction->instructions->length; idx++) {
176 struct sway_transaction_instruction *other =
177 transaction->instructions->items[idx];
178 if (other->node == node) {
179 instruction = other;
180 break;
181 }
182 }
183 }
184
185 if (!instruction) {
186 instruction = calloc(1, sizeof(struct sway_transaction_instruction));
187 if (!sway_assert(instruction, "Unable to allocate instruction")) {
188 return;
189 }
190 instruction->transaction = transaction;
191 instruction->node = node;
192 instruction->server_request = server_request;
193
194 list_add(transaction->instructions, instruction);
195 node->ntxnrefs++;
196 } else if (server_request) {
197 instruction->server_request = true;
169 } 198 }
170 instruction->transaction = transaction;
171 instruction->node = node;
172 199
173 switch (node->type) { 200 switch (node->type) {
174 case N_ROOT: 201 case N_ROOT:
@@ -183,46 +210,24 @@ static void transaction_add_node(struct sway_transaction *transaction,
183 copy_container_state(node->sway_container, instruction); 210 copy_container_state(node->sway_container, instruction);
184 break; 211 break;
185 } 212 }
186
187 list_add(transaction->instructions, instruction);
188 node->ntxnrefs++;
189} 213}
190 214
191static void apply_output_state(struct sway_output *output, 215static void apply_output_state(struct sway_output *output,
192 struct sway_output_state *state) { 216 struct sway_output_state *state) {
193 output_damage_whole(output);
194 list_free(output->current.workspaces); 217 list_free(output->current.workspaces);
195 memcpy(&output->current, state, sizeof(struct sway_output_state)); 218 memcpy(&output->current, state, sizeof(struct sway_output_state));
196 output_damage_whole(output);
197} 219}
198 220
199static void apply_workspace_state(struct sway_workspace *ws, 221static void apply_workspace_state(struct sway_workspace *ws,
200 struct sway_workspace_state *state) { 222 struct sway_workspace_state *state) {
201 output_damage_whole(ws->current.output);
202 list_free(ws->current.floating); 223 list_free(ws->current.floating);
203 list_free(ws->current.tiling); 224 list_free(ws->current.tiling);
204 memcpy(&ws->current, state, sizeof(struct sway_workspace_state)); 225 memcpy(&ws->current, state, sizeof(struct sway_workspace_state));
205 output_damage_whole(ws->current.output);
206} 226}
207 227
208static void apply_container_state(struct sway_container *container, 228static void apply_container_state(struct sway_container *container,
209 struct sway_container_state *state) { 229 struct sway_container_state *state) {
210 struct sway_view *view = container->view; 230 struct sway_view *view = container->view;
211 // Damage the old location
212 desktop_damage_whole_container(container);
213 if (view && !wl_list_empty(&view->saved_buffers)) {
214 struct sway_saved_buffer *saved_buf;
215 wl_list_for_each(saved_buf, &view->saved_buffers, link) {
216 struct wlr_box box = {
217 .x = container->current.content_x - view->saved_geometry.x + saved_buf->x,
218 .y = container->current.content_y - view->saved_geometry.y + saved_buf->y,
219 .width = saved_buf->width,
220 .height = saved_buf->height,
221 };
222 desktop_damage_box(&box);
223 }
224 }
225
226 // There are separate children lists for each instruction state, the 231 // There are separate children lists for each instruction state, the
227 // container's current state and the container's pending state 232 // container's current state and the container's pending state
228 // (ie. con->children). The list itself needs to be freed here. 233 // (ie. con->children). The list itself needs to be freed here.
@@ -232,46 +237,449 @@ static void apply_container_state(struct sway_container *container,
232 237
233 memcpy(&container->current, state, sizeof(struct sway_container_state)); 238 memcpy(&container->current, state, sizeof(struct sway_container_state));
234 239
235 if (view && !wl_list_empty(&view->saved_buffers)) { 240 if (view) {
236 if (!container->node.destroying || container->node.ntxnrefs == 1) { 241 if (view->saved_surface_tree) {
237 view_remove_saved_buffer(view); 242 if (!container->node.destroying || container->node.ntxnrefs == 1) {
243 view_remove_saved_buffer(view);
244 }
245 }
246
247 // If the view hasn't responded to the configure, center it within
248 // the container. This is important for fullscreen views which
249 // refuse to resize to the size of the output.
250 if (view->surface) {
251 view_center_and_clip_surface(view);
252 }
253 }
254}
255
256static void arrange_title_bar(struct sway_container *con,
257 int x, int y, int width, int height) {
258 container_update(con);
259
260 bool has_title_bar = height > 0;
261 wlr_scene_node_set_enabled(&con->title_bar.tree->node, has_title_bar);
262 if (!has_title_bar) {
263 return;
264 }
265
266 wlr_scene_node_set_position(&con->title_bar.tree->node, x, y);
267
268 con->title_width = width;
269 container_arrange_title_bar(con);
270}
271
272static void disable_container(struct sway_container *con) {
273 if (con->view) {
274 wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree);
275 } else {
276 for (int i = 0; i < con->current.children->length; i++) {
277 struct sway_container *child = con->current.children->items[i];
278
279 wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree);
280
281 disable_container(child);
282 }
283 }
284}
285
286static void arrange_container(struct sway_container *con,
287 int width, int height, bool title_bar, int gaps);
288
289static void arrange_children(enum sway_container_layout layout, list_t *children,
290 struct sway_container *active, struct wlr_scene_tree *content,
291 int width, int height, int gaps) {
292 int title_bar_height = container_titlebar_height();
293
294 if (layout == L_TABBED) {
295 struct sway_container *first = children->length == 1 ?
296 ((struct sway_container *)children->items[0]) : NULL;
297 if (config->hide_lone_tab && first && first->view &&
298 first->current.border != B_NORMAL) {
299 title_bar_height = 0;
300 }
301
302 double w = (double) width / children->length;
303 int title_offset = 0;
304 for (int i = 0; i < children->length; i++) {
305 struct sway_container *child = children->items[i];
306 bool activated = child == active;
307 int next_title_offset = round(w * i + w);
308
309 arrange_title_bar(child, title_offset, -title_bar_height,
310 next_title_offset - title_offset, title_bar_height);
311 wlr_scene_node_set_enabled(&child->border.tree->node, activated);
312 wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height);
313 wlr_scene_node_reparent(&child->scene_tree->node, content);
314
315 if (activated) {
316 arrange_container(child, width, height - title_bar_height,
317 false, 0);
318 } else {
319 disable_container(child);
320 }
321
322 title_offset = next_title_offset;
238 } 323 }
324 } else if (layout == L_STACKED) {
325 struct sway_container *first = children->length == 1 ?
326 ((struct sway_container *)children->items[0]) : NULL;
327 if (config->hide_lone_tab && first && first->view &&
328 first->current.border != B_NORMAL) {
329 title_bar_height = 0;
330 }
331
332 int title_height = title_bar_height * children->length;
333
334 int y = 0;
335 for (int i = 0; i < children->length; i++) {
336 struct sway_container *child = children->items[i];
337 bool activated = child == active;
338
339 arrange_title_bar(child, 0, y - title_height, width, title_bar_height);
340 wlr_scene_node_set_enabled(&child->border.tree->node, activated);
341 wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height);
342 wlr_scene_node_reparent(&child->scene_tree->node, content);
343
344 if (activated) {
345 arrange_container(child, width, height - title_height,
346 false, 0);
347 } else {
348 disable_container(child);
349 }
350
351 y += title_bar_height;
352 }
353 } else if (layout == L_VERT) {
354 int off = 0;
355 for (int i = 0; i < children->length; i++) {
356 struct sway_container *child = children->items[i];
357 int cheight = child->current.height;
358
359 wlr_scene_node_set_enabled(&child->border.tree->node, true);
360 wlr_scene_node_set_position(&child->scene_tree->node, 0, off);
361 wlr_scene_node_reparent(&child->scene_tree->node, content);
362 arrange_container(child, width, cheight, true, gaps);
363 off += cheight + gaps;
364 }
365 } else if (layout == L_HORIZ) {
366 int off = 0;
367 for (int i = 0; i < children->length; i++) {
368 struct sway_container *child = children->items[i];
369 int cwidth = child->current.width;
370
371 wlr_scene_node_set_enabled(&child->border.tree->node, true);
372 wlr_scene_node_set_position(&child->scene_tree->node, off, 0);
373 wlr_scene_node_reparent(&child->scene_tree->node, content);
374 arrange_container(child, cwidth, height, true, gaps);
375 off += cwidth + gaps;
376 }
377 } else {
378 sway_assert(false, "unreachable");
379 }
380}
381
382static void arrange_container(struct sway_container *con,
383 int width, int height, bool title_bar, int gaps) {
384 // this container might have previously been in the scratchpad,
385 // make sure it's enabled for viewing
386 wlr_scene_node_set_enabled(&con->scene_tree->node, true);
387
388 if (con->output_handler) {
389 wlr_scene_buffer_set_dest_size(con->output_handler, width, height);
390 }
391
392 if (con->view) {
393 int border_top = container_titlebar_height();
394 int border_width = con->current.border_thickness;
395
396 if (title_bar && con->current.border != B_NORMAL) {
397 wlr_scene_node_set_enabled(&con->title_bar.tree->node, false);
398 wlr_scene_node_set_enabled(&con->border.top->node, true);
399 } else {
400 wlr_scene_node_set_enabled(&con->border.top->node, false);
401 }
402
403 if (con->current.border == B_NORMAL) {
404 if (title_bar) {
405 arrange_title_bar(con, 0, 0, width, border_top);
406 } else {
407 border_top = 0;
408 // should be handled by the parent container
409 }
410 } else if (con->current.border == B_PIXEL) {
411 container_update(con);
412 border_top = title_bar && con->current.border_top ? border_width : 0;
413 } else if (con->current.border == B_NONE) {
414 container_update(con);
415 border_top = 0;
416 border_width = 0;
417 } else if (con->current.border == B_CSD) {
418 border_top = 0;
419 border_width = 0;
420 } else {
421 sway_assert(false, "unreachable");
422 }
423
424 int border_bottom = con->current.border_bottom ? border_width : 0;
425 int border_left = con->current.border_left ? border_width : 0;
426 int border_right = con->current.border_right ? border_width : 0;
427
428 wlr_scene_rect_set_size(con->border.top, width, border_top);
429 wlr_scene_rect_set_size(con->border.bottom, width, border_bottom);
430 wlr_scene_rect_set_size(con->border.left,
431 border_left, height - border_top - border_bottom);
432 wlr_scene_rect_set_size(con->border.right,
433 border_right, height - border_top - border_bottom);
434
435 wlr_scene_node_set_position(&con->border.top->node, 0, 0);
436 wlr_scene_node_set_position(&con->border.bottom->node,
437 0, height - border_bottom);
438 wlr_scene_node_set_position(&con->border.left->node,
439 0, border_top);
440 wlr_scene_node_set_position(&con->border.right->node,
441 width - border_right, border_top);
442
443 // make sure to reparent, it's possible that the client just came out of
444 // fullscreen mode where the parent of the surface is not the container
445 wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree);
446 wlr_scene_node_set_position(&con->view->scene_tree->node,
447 border_left, border_top);
448 } else {
449 // make sure to disable the title bar if the parent is not managing it
450 if (title_bar) {
451 wlr_scene_node_set_enabled(&con->title_bar.tree->node, false);
452 }
453
454 arrange_children(con->current.layout, con->current.children,
455 con->current.focused_inactive_child, con->content_tree,
456 width, height, gaps);
239 } 457 }
458}
459
460static int container_get_gaps(struct sway_container *con) {
461 struct sway_workspace *ws = con->current.workspace;
462 struct sway_container *temp = con;
463 while (temp) {
464 enum sway_container_layout layout;
465 if (temp->current.parent) {
466 layout = temp->current.parent->current.layout;
467 } else {
468 layout = ws->current.layout;
469 }
470 if (layout == L_TABBED || layout == L_STACKED) {
471 return 0;
472 }
473 temp = temp->pending.parent;
474 }
475 return ws->gaps_inner;
476}
240 477
241 // Damage the new location 478static void arrange_fullscreen(struct wlr_scene_tree *tree,
242 desktop_damage_whole_container(container); 479 struct sway_container *fs, struct sway_workspace *ws,
243 if (view && view->surface) { 480 int width, int height) {
244 struct wlr_surface *surface = view->surface; 481 struct wlr_scene_node *fs_node;
245 struct wlr_box box = { 482 if (fs->view) {
246 .x = container->current.content_x - view->geometry.x, 483 fs_node = &fs->view->scene_tree->node;
247 .y = container->current.content_y - view->geometry.y, 484
248 .width = surface->current.width, 485 // if we only care about the view, disable any decorations
249 .height = surface->current.height, 486 wlr_scene_node_set_enabled(&fs->scene_tree->node, false);
250 }; 487 } else {
251 desktop_damage_box(&box); 488 fs_node = &fs->scene_tree->node;
489 arrange_container(fs, width, height, true, container_get_gaps(fs));
252 } 490 }
253 491
254 // If the view hasn't responded to the configure, center it within 492 wlr_scene_node_reparent(fs_node, tree);
255 // the container. This is important for fullscreen views which 493 wlr_scene_node_lower_to_bottom(fs_node);
256 // refuse to resize to the size of the output. 494 wlr_scene_node_set_position(fs_node, 0, 0);
257 if (view && view->surface) { 495}
258 if (view->geometry.width < container->current.content_width) { 496
259 container->surface_x = container->current.content_x + 497static void arrange_workspace_floating(struct sway_workspace *ws) {
260 (container->current.content_width - view->geometry.width) / 2; 498 for (int i = 0; i < ws->current.floating->length; i++) {
499 struct sway_container *floater = ws->current.floating->items[i];
500 struct wlr_scene_tree *layer = root->layers.floating;
501
502 if (floater->current.fullscreen_mode != FULLSCREEN_NONE) {
503 continue;
504 }
505
506 if (root->fullscreen_global) {
507 if (container_is_transient_for(floater, root->fullscreen_global)) {
508 layer = root->layers.fullscreen_global;
509 }
261 } else { 510 } else {
262 container->surface_x = container->current.content_x; 511 for (int i = 0; i < root->outputs->length; i++) {
512 struct sway_output *output = root->outputs->items[i];
513 struct sway_workspace *active = output->current.active_workspace;
514
515 if (active && active->fullscreen &&
516 container_is_transient_for(floater, active->fullscreen)) {
517 layer = root->layers.fullscreen;
518 }
519 }
520 }
521
522 wlr_scene_node_reparent(&floater->scene_tree->node, layer);
523 wlr_scene_node_set_position(&floater->scene_tree->node,
524 floater->current.x, floater->current.y);
525 wlr_scene_node_set_enabled(&floater->scene_tree->node, true);
526
527 arrange_container(floater, floater->current.width, floater->current.height,
528 true, ws->gaps_inner);
529 }
530}
531
532static void arrange_workspace_tiling(struct sway_workspace *ws,
533 int width, int height) {
534 arrange_children(ws->current.layout, ws->current.tiling,
535 ws->current.focused_inactive_child, ws->layers.tiling,
536 width, height, ws->gaps_inner);
537}
538
539static void disable_workspace(struct sway_workspace *ws) {
540 // if any containers were just moved to a disabled workspace it will
541 // have the parent of the old workspace. Move the workspace so that it won't
542 // be shown.
543 for (int i = 0; i < ws->current.tiling->length; i++) {
544 struct sway_container *child = ws->current.tiling->items[i];
545
546 wlr_scene_node_reparent(&child->scene_tree->node, ws->layers.tiling);
547 disable_container(child);
548 }
549
550 for (int i = 0; i < ws->current.floating->length; i++) {
551 struct sway_container *floater = ws->current.floating->items[i];
552 wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating);
553 disable_container(floater);
554 wlr_scene_node_set_enabled(&floater->scene_tree->node, false);
555 }
556}
557
558static void arrange_output(struct sway_output *output, int width, int height) {
559 for (int i = 0; i < output->current.workspaces->length; i++) {
560 struct sway_workspace *child = output->current.workspaces->items[i];
561
562 bool activated = output->current.active_workspace == child;
563
564 wlr_scene_node_reparent(&child->layers.tiling->node, output->layers.tiling);
565 wlr_scene_node_reparent(&child->layers.fullscreen->node, output->layers.fullscreen);
566
567 for (int i = 0; i < child->current.floating->length; i++) {
568 struct sway_container *floater = child->current.floating->items[i];
569 wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating);
570 wlr_scene_node_set_enabled(&floater->scene_tree->node, activated);
263 } 571 }
264 if (view->geometry.height < container->current.content_height) { 572
265 container->surface_y = container->current.content_y + 573 if (activated) {
266 (container->current.content_height - view->geometry.height) / 2; 574 struct sway_container *fs = child->current.fullscreen;
575 wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs);
576 wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs);
577
578 arrange_workspace_floating(child);
579
580 wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs);
581 wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs);
582 wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs);
583
584 if (fs) {
585 wlr_scene_rect_set_size(output->fullscreen_background, width, height);
586
587 arrange_fullscreen(child->layers.fullscreen, fs, child,
588 width, height);
589 } else {
590 struct wlr_box *area = &output->usable_area;
591 struct side_gaps *gaps = &child->current_gaps;
592
593 wlr_scene_node_set_position(&child->layers.tiling->node,
594 gaps->left + area->x, gaps->top + area->y);
595
596 arrange_workspace_tiling(child,
597 area->width - gaps->left - gaps->right,
598 area->height - gaps->top - gaps->bottom);
599 }
267 } else { 600 } else {
268 container->surface_y = container->current.content_y; 601 wlr_scene_node_set_enabled(&child->layers.tiling->node, false);
602 wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false);
603
604 disable_workspace(child);
269 } 605 }
270 } 606 }
607}
608
609static void arrange_popup(struct wlr_scene_tree *popup) {
610 struct wlr_scene_node *node;
611 wl_list_for_each(node, &popup->children, link) {
612 struct sway_xdg_popup *popup = scene_descriptor_try_get(node,
613 SWAY_SCENE_DESC_POPUP);
614
615 // the popup layer may have popups from layer_shell surfaces, in this
616 // case those don't have a scene descriptor, so lets skip those here.
617 if (popup) {
618 struct wlr_scene_tree *tree = popup->view->content_tree;
619
620 int lx, ly;
621 wlr_scene_node_coords(&tree->node, &lx, &ly);
622 wlr_scene_node_set_position(&popup->scene_tree->node, lx, ly);
623 }
624 }
625}
626
627static void arrange_root(struct sway_root *root) {
628 struct sway_container *fs = root->fullscreen_global;
629
630 wlr_scene_node_set_enabled(&root->layers.shell_background->node, !fs);
631 wlr_scene_node_set_enabled(&root->layers.shell_bottom->node, !fs);
632 wlr_scene_node_set_enabled(&root->layers.tiling->node, !fs);
633 wlr_scene_node_set_enabled(&root->layers.floating->node, !fs);
634 wlr_scene_node_set_enabled(&root->layers.shell_top->node, !fs);
635 wlr_scene_node_set_enabled(&root->layers.fullscreen->node, !fs);
636
637 // hide all contents in the scratchpad
638 for (int i = 0; i < root->scratchpad->length; i++) {
639 struct sway_container *con = root->scratchpad->items[i];
640
641 wlr_scene_node_set_enabled(&con->scene_tree->node, false);
642 }
643
644 if (fs) {
645 for (int i = 0; i < root->outputs->length; i++) {
646 struct sway_output *output = root->outputs->items[i];
647 struct sway_workspace *ws = output->current.active_workspace;
648
649 if (ws) {
650 arrange_workspace_floating(ws);
651 }
652 }
271 653
272 if (!container->node.destroying) { 654 arrange_fullscreen(root->layers.fullscreen_global, fs, NULL,
273 container_discover_outputs(container); 655 root->width, root->height);
656 } else {
657 for (int i = 0; i < root->outputs->length; i++) {
658 struct sway_output *output = root->outputs->items[i];
659
660 wlr_scene_output_set_position(output->scene_output, output->lx, output->ly);
661
662 wlr_scene_node_reparent(&output->layers.shell_background->node, root->layers.shell_background);
663 wlr_scene_node_reparent(&output->layers.shell_bottom->node, root->layers.shell_bottom);
664 wlr_scene_node_reparent(&output->layers.tiling->node, root->layers.tiling);
665 wlr_scene_node_reparent(&output->layers.shell_top->node, root->layers.shell_top);
666 wlr_scene_node_reparent(&output->layers.shell_overlay->node, root->layers.shell_overlay);
667 wlr_scene_node_reparent(&output->layers.fullscreen->node, root->layers.fullscreen);
668 wlr_scene_node_reparent(&output->layers.session_lock->node, root->layers.session_lock);
669
670 wlr_scene_node_set_position(&output->layers.shell_background->node, output->lx, output->ly);
671 wlr_scene_node_set_position(&output->layers.shell_bottom->node, output->lx, output->ly);
672 wlr_scene_node_set_position(&output->layers.tiling->node, output->lx, output->ly);
673 wlr_scene_node_set_position(&output->layers.fullscreen->node, output->lx, output->ly);
674 wlr_scene_node_set_position(&output->layers.shell_top->node, output->lx, output->ly);
675 wlr_scene_node_set_position(&output->layers.shell_overlay->node, output->lx, output->ly);
676 wlr_scene_node_set_position(&output->layers.session_lock->node, output->lx, output->ly);
677
678 arrange_output(output, output->width, output->height);
679 }
274 } 680 }
681
682 arrange_popup(root->layers.popup);
275} 683}
276 684
277/** 685/**
@@ -313,74 +721,29 @@ static void transaction_apply(struct sway_transaction *transaction) {
313 721
314 node->instruction = NULL; 722 node->instruction = NULL;
315 } 723 }
316
317 cursor_rebase_all();
318} 724}
319 725
320static void transaction_commit(struct sway_transaction *transaction); 726static void transaction_commit_pending(void);
321
322// Return true if both transactions operate on the same nodes
323static bool transaction_same_nodes(struct sway_transaction *a,
324 struct sway_transaction *b) {
325 if (a->instructions->length != b->instructions->length) {
326 return false;
327 }
328 for (int i = 0; i < a->instructions->length; ++i) {
329 struct sway_transaction_instruction *a_inst = a->instructions->items[i];
330 struct sway_transaction_instruction *b_inst = b->instructions->items[i];
331 if (a_inst->node != b_inst->node) {
332 return false;
333 }
334 }
335 return true;
336}
337 727
338static void transaction_progress_queue(void) { 728static void transaction_progress(void) {
339 if (!server.transactions->length) { 729 if (!server.queued_transaction) {
340 return; 730 return;
341 } 731 }
342 // Only the first transaction in the queue is committed, so that's the one 732 if (server.queued_transaction->num_waiting > 0) {
343 // we try to process.
344 struct sway_transaction *transaction = server.transactions->items[0];
345 if (transaction->num_waiting) {
346 return; 733 return;
347 } 734 }
348 transaction_apply(transaction); 735 transaction_apply(server.queued_transaction);
349 transaction_destroy(transaction); 736 arrange_root(root);
350 list_del(server.transactions, 0); 737 cursor_rebase_all();
738 transaction_destroy(server.queued_transaction);
739 server.queued_transaction = NULL;
351 740
352 if (server.transactions->length == 0) { 741 if (!server.pending_transaction) {
353 // The transaction queue is empty, so we're done. 742 sway_idle_inhibit_v1_check_active();
354 sway_idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
355 return; 743 return;
356 } 744 }
357 745
358 // If there's a bunch of consecutive transactions which all apply to the 746 transaction_commit_pending();
359 // same views, skip all except the last one.
360 while (server.transactions->length >= 2) {
361 struct sway_transaction *txn = server.transactions->items[0];
362 struct sway_transaction *dup = NULL;
363
364 for (int i = 1; i < server.transactions->length; i++) {
365 struct sway_transaction *maybe_dup = server.transactions->items[i];
366 if (transaction_same_nodes(txn, maybe_dup)) {
367 dup = maybe_dup;
368 break;
369 }
370 }
371
372 if (dup) {
373 list_del(server.transactions, 0);
374 transaction_destroy(txn);
375 } else {
376 break;
377 }
378 }
379
380 // We again commit the first transaction in the queue to process it.
381 transaction = server.transactions->items[0];
382 transaction_commit(transaction);
383 transaction_progress_queue();
384} 747}
385 748
386static int handle_timeout(void *data) { 749static int handle_timeout(void *data) {
@@ -388,7 +751,7 @@ static int handle_timeout(void *data) {
388 sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)", 751 sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)",
389 transaction, transaction->num_waiting); 752 transaction, transaction->num_waiting);
390 transaction->num_waiting = 0; 753 transaction->num_waiting = 0;
391 transaction_progress_queue(); 754 transaction_progress();
392 return 0; 755 return 0;
393} 756}
394 757
@@ -400,6 +763,9 @@ static bool should_configure(struct sway_node *node,
400 if (node->destroying) { 763 if (node->destroying) {
401 return false; 764 return false;
402 } 765 }
766 if (!instruction->server_request) {
767 return false;
768 }
403 struct sway_container_state *cstate = &node->sway_container->current; 769 struct sway_container_state *cstate = &node->sway_container->current;
404 struct sway_container_state *istate = &instruction->container_state; 770 struct sway_container_state *istate = &instruction->container_state;
405#if HAVE_XWAYLAND 771#if HAVE_XWAYLAND
@@ -431,28 +797,24 @@ static void transaction_commit(struct sway_transaction *transaction) {
431 struct sway_transaction_instruction *instruction = 797 struct sway_transaction_instruction *instruction =
432 transaction->instructions->items[i]; 798 transaction->instructions->items[i];
433 struct sway_node *node = instruction->node; 799 struct sway_node *node = instruction->node;
800 bool hidden = node_is_view(node) && !node->destroying &&
801 !view_is_visible(node->sway_container->view);
434 if (should_configure(node, instruction)) { 802 if (should_configure(node, instruction)) {
435 instruction->serial = view_configure(node->sway_container->view, 803 instruction->serial = view_configure(node->sway_container->view,
436 instruction->container_state.content_x, 804 instruction->container_state.content_x,
437 instruction->container_state.content_y, 805 instruction->container_state.content_y,
438 instruction->container_state.content_width, 806 instruction->container_state.content_width,
439 instruction->container_state.content_height); 807 instruction->container_state.content_height);
440 ++transaction->num_waiting; 808 if (!hidden) {
441 809 instruction->waiting = true;
442 // From here on we are rendering a saved buffer of the view, which 810 ++transaction->num_waiting;
443 // means we can send a frame done event to make the client redraw it 811 }
444 // as soon as possible. Additionally, this is required if a view is 812
445 // mapping and its default geometry doesn't intersect an output. 813 view_send_frame_done(node->sway_container->view);
446 struct timespec now; 814 }
447 clock_gettime(CLOCK_MONOTONIC, &now); 815 if (!hidden && node_is_view(node) &&
448 wlr_surface_send_frame_done( 816 !node->sway_container->view->saved_surface_tree) {
449 node->sway_container->view->surface, &now);
450 }
451 if (node_is_view(node) && wl_list_empty(&node->sway_container->view->saved_buffers)) {
452 view_save_buffer(node->sway_container->view); 817 view_save_buffer(node->sway_container->view);
453 memcpy(&node->sway_container->view->saved_geometry,
454 &node->sway_container->view->geometry,
455 sizeof(struct wlr_box));
456 } 818 }
457 node->instruction = instruction; 819 node->instruction = instruction;
458 } 820 }
@@ -483,6 +845,17 @@ static void transaction_commit(struct sway_transaction *transaction) {
483 } 845 }
484} 846}
485 847
848static void transaction_commit_pending(void) {
849 if (server.queued_transaction) {
850 return;
851 }
852 struct sway_transaction *transaction = server.pending_transaction;
853 server.pending_transaction = NULL;
854 server.queued_transaction = transaction;
855 transaction_commit(transaction);
856 transaction_progress();
857}
858
486static void set_instruction_ready( 859static void set_instruction_ready(
487 struct sway_transaction_instruction *instruction) { 860 struct sway_transaction_instruction *instruction) {
488 struct sway_transaction *transaction = instruction->transaction; 861 struct sway_transaction *transaction = instruction->transaction;
@@ -501,25 +874,28 @@ static void set_instruction_ready(
501 } 874 }
502 875
503 // If the transaction has timed out then its num_waiting will be 0 already. 876 // If the transaction has timed out then its num_waiting will be 0 already.
504 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 877 if (instruction->waiting && transaction->num_waiting > 0 &&
878 --transaction->num_waiting == 0) {
505 sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction); 879 sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction);
506 wl_event_source_timer_update(transaction->timer, 0); 880 wl_event_source_timer_update(transaction->timer, 0);
507 } 881 }
508 882
509 instruction->node->instruction = NULL; 883 instruction->node->instruction = NULL;
510 transaction_progress_queue(); 884 transaction_progress();
511} 885}
512 886
513void transaction_notify_view_ready_by_serial(struct sway_view *view, 887bool transaction_notify_view_ready_by_serial(struct sway_view *view,
514 uint32_t serial) { 888 uint32_t serial) {
515 struct sway_transaction_instruction *instruction = 889 struct sway_transaction_instruction *instruction =
516 view->container->node.instruction; 890 view->container->node.instruction;
517 if (instruction != NULL && instruction->serial == serial) { 891 if (instruction != NULL && instruction->serial == serial) {
518 set_instruction_ready(instruction); 892 set_instruction_ready(instruction);
893 return true;
519 } 894 }
895 return false;
520} 896}
521 897
522void transaction_notify_view_ready_by_geometry(struct sway_view *view, 898bool transaction_notify_view_ready_by_geometry(struct sway_view *view,
523 double x, double y, int width, int height) { 899 double x, double y, int width, int height) {
524 struct sway_transaction_instruction *instruction = 900 struct sway_transaction_instruction *instruction =
525 view->container->node.instruction; 901 view->container->node.instruction;
@@ -529,39 +905,37 @@ void transaction_notify_view_ready_by_geometry(struct sway_view *view,
529 instruction->container_state.content_width == width && 905 instruction->container_state.content_width == width &&
530 instruction->container_state.content_height == height) { 906 instruction->container_state.content_height == height) {
531 set_instruction_ready(instruction); 907 set_instruction_ready(instruction);
908 return true;
532 } 909 }
910 return false;
533} 911}
534 912
535void transaction_notify_view_ready_immediately(struct sway_view *view) { 913static void _transaction_commit_dirty(bool server_request) {
536 struct sway_transaction_instruction *instruction =
537 view->container->node.instruction;
538 if (instruction != NULL) {
539 set_instruction_ready(instruction);
540 }
541}
542
543void transaction_commit_dirty(void) {
544 if (!server.dirty_nodes->length) { 914 if (!server.dirty_nodes->length) {
545 return; 915 return;
546 } 916 }
547 struct sway_transaction *transaction = transaction_create(); 917
548 if (!transaction) { 918 if (!server.pending_transaction) {
549 return; 919 server.pending_transaction = transaction_create();
920 if (!server.pending_transaction) {
921 return;
922 }
550 } 923 }
924
551 for (int i = 0; i < server.dirty_nodes->length; ++i) { 925 for (int i = 0; i < server.dirty_nodes->length; ++i) {
552 struct sway_node *node = server.dirty_nodes->items[i]; 926 struct sway_node *node = server.dirty_nodes->items[i];
553 transaction_add_node(transaction, node); 927 transaction_add_node(server.pending_transaction, node, server_request);
554 node->dirty = false; 928 node->dirty = false;
555 } 929 }
556 server.dirty_nodes->length = 0; 930 server.dirty_nodes->length = 0;
557 931
558 list_add(server.transactions, transaction); 932 transaction_commit_pending();
933}
559 934
560 // We only commit the first transaction added to the queue. 935void transaction_commit_dirty(void) {
561 if (server.transactions->length == 1) { 936 _transaction_commit_dirty(true);
562 transaction_commit(transaction); 937}
563 // Attempting to progress the queue here is useful 938
564 // if the transaction has nothing to wait for. 939void transaction_commit_dirty_client(void) {
565 transaction_progress_queue(); 940 _transaction_commit_dirty(false);
566 }
567} 941}
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 667fb9e5..7cdd97c8 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -7,7 +7,7 @@
7#include <wlr/util/edges.h> 7#include <wlr/util/edges.h>
8#include "log.h" 8#include "log.h"
9#include "sway/decoration.h" 9#include "sway/decoration.h"
10#include "sway/desktop.h" 10#include "sway/scene_descriptor.h"
11#include "sway/desktop/transaction.h" 11#include "sway/desktop/transaction.h"
12#include "sway/input/cursor.h" 12#include "sway/input/cursor.h"
13#include "sway/input/input-manager.h" 13#include "sway/input/input-manager.h"
@@ -19,64 +19,44 @@
19#include "sway/tree/workspace.h" 19#include "sway/tree/workspace.h"
20#include "sway/xdg_decoration.h" 20#include "sway/xdg_decoration.h"
21 21
22static const struct sway_view_child_impl popup_impl;
23
24static void popup_get_root_coords(struct sway_view_child *child,
25 int *root_sx, int *root_sy) {
26 struct sway_xdg_popup *popup = (struct sway_xdg_popup *)child;
27 struct wlr_xdg_surface *surface = popup->wlr_xdg_surface;
28
29 int x_offset = -child->view->geometry.x - surface->geometry.x;
30 int y_offset = -child->view->geometry.y - surface->geometry.y;
31
32 wlr_xdg_popup_get_toplevel_coords(surface->popup,
33 x_offset + surface->popup->geometry.x,
34 y_offset + surface->popup->geometry.y,
35 root_sx, root_sy);
36}
37
38static void popup_destroy(struct sway_view_child *child) {
39 if (!sway_assert(child->impl == &popup_impl,
40 "Expected an xdg_shell popup")) {
41 return;
42 }
43 struct sway_xdg_popup *popup = (struct sway_xdg_popup *)child;
44 wl_list_remove(&popup->new_popup.link);
45 wl_list_remove(&popup->destroy.link);
46 free(popup);
47}
48
49static const struct sway_view_child_impl popup_impl = {
50 .get_root_coords = popup_get_root_coords,
51 .destroy = popup_destroy,
52};
53
54static struct sway_xdg_popup *popup_create( 22static struct sway_xdg_popup *popup_create(
55 struct wlr_xdg_popup *wlr_popup, struct sway_view *view); 23 struct wlr_xdg_popup *wlr_popup, struct sway_view *view,
24 struct wlr_scene_tree *parent);
56 25
57static void popup_handle_new_popup(struct wl_listener *listener, void *data) { 26static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
58 struct sway_xdg_popup *popup = 27 struct sway_xdg_popup *popup =
59 wl_container_of(listener, popup, new_popup); 28 wl_container_of(listener, popup, new_popup);
60 struct wlr_xdg_popup *wlr_popup = data; 29 struct wlr_xdg_popup *wlr_popup = data;
61 popup_create(wlr_popup, popup->child.view); 30 popup_create(wlr_popup, popup->view, popup->xdg_surface_tree);
62} 31}
63 32
64static void popup_handle_destroy(struct wl_listener *listener, void *data) { 33static void popup_handle_destroy(struct wl_listener *listener, void *data) {
65 struct sway_xdg_popup *popup = wl_container_of(listener, popup, destroy); 34 struct sway_xdg_popup *popup = wl_container_of(listener, popup, destroy);
66 view_child_destroy(&popup->child); 35
36 wl_list_remove(&popup->new_popup.link);
37 wl_list_remove(&popup->destroy.link);
38 wl_list_remove(&popup->surface_commit.link);
39 wlr_scene_node_destroy(&popup->scene_tree->node);
40 free(popup);
67} 41}
68 42
69static void popup_unconstrain(struct sway_xdg_popup *popup) { 43static void popup_unconstrain(struct sway_xdg_popup *popup) {
70 struct sway_view *view = popup->child.view; 44 struct sway_view *view = popup->view;
71 struct wlr_xdg_popup *wlr_popup = popup->wlr_xdg_surface->popup; 45 struct wlr_xdg_popup *wlr_popup = popup->wlr_xdg_popup;
72 46
73 struct sway_output *output = view->container->workspace->output; 47 struct sway_workspace *workspace = view->container->pending.workspace;
48 if (!workspace) {
49 // is null if in the scratchpad
50 return;
51 }
52
53 struct sway_output *output = workspace->output;
74 54
75 // the output box expressed in the coordinate system of the toplevel parent 55 // the output box expressed in the coordinate system of the toplevel parent
76 // of the popup 56 // of the popup
77 struct wlr_box output_toplevel_sx_box = { 57 struct wlr_box output_toplevel_sx_box = {
78 .x = output->lx - view->container->content_x, 58 .x = output->lx - view->container->pending.content_x + view->geometry.x,
79 .y = output->ly - view->container->content_y, 59 .y = output->ly - view->container->pending.content_y + view->geometry.y,
80 .width = output->width, 60 .width = output->width,
81 .height = output->height, 61 .height = output->height,
82 }; 62 };
@@ -84,32 +64,62 @@ static void popup_unconstrain(struct sway_xdg_popup *popup) {
84 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); 64 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box);
85} 65}
86 66
87static struct sway_xdg_popup *popup_create( 67static void popup_handle_surface_commit(struct wl_listener *listener, void *data) {
88 struct wlr_xdg_popup *wlr_popup, struct sway_view *view) { 68 struct sway_xdg_popup *popup = wl_container_of(listener, popup, surface_commit);
69 if (popup->wlr_xdg_popup->base->initial_commit) {
70 popup_unconstrain(popup);
71 }
72}
73
74static struct sway_xdg_popup *popup_create(struct wlr_xdg_popup *wlr_popup,
75 struct sway_view *view, struct wlr_scene_tree *parent) {
89 struct wlr_xdg_surface *xdg_surface = wlr_popup->base; 76 struct wlr_xdg_surface *xdg_surface = wlr_popup->base;
90 77
91 struct sway_xdg_popup *popup = 78 struct sway_xdg_popup *popup = calloc(1, sizeof(struct sway_xdg_popup));
92 calloc(1, sizeof(struct sway_xdg_popup)); 79 if (!popup) {
93 if (popup == NULL) {
94 return NULL; 80 return NULL;
95 } 81 }
96 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface);
97 popup->wlr_xdg_surface = xdg_surface;
98 82
83 popup->wlr_xdg_popup = wlr_popup;
84 popup->view = view;
85
86 popup->scene_tree = wlr_scene_tree_create(parent);
87 if (!popup->scene_tree) {
88 free(popup);
89 return NULL;
90 }
91
92 popup->xdg_surface_tree = wlr_scene_xdg_surface_create(
93 popup->scene_tree, xdg_surface);
94 if (!popup->xdg_surface_tree) {
95 wlr_scene_node_destroy(&popup->scene_tree->node);
96 free(popup);
97 return NULL;
98 }
99
100 if (!scene_descriptor_assign(&popup->scene_tree->node,
101 SWAY_SCENE_DESC_POPUP, popup)) {
102 sway_log(SWAY_ERROR, "Failed to allocate a popup scene descriptor");
103 wlr_scene_node_destroy(&popup->scene_tree->node);
104 free(popup);
105 return NULL;
106 }
107
108 popup->wlr_xdg_popup = xdg_surface->popup;
109 struct sway_xdg_shell_view *shell_view =
110 wl_container_of(view, shell_view, view);
111 xdg_surface->data = shell_view;
112
113 wl_signal_add(&xdg_surface->surface->events.commit, &popup->surface_commit);
114 popup->surface_commit.notify = popup_handle_surface_commit;
99 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup); 115 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup);
100 popup->new_popup.notify = popup_handle_new_popup; 116 popup->new_popup.notify = popup_handle_new_popup;
101 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy); 117 wl_signal_add(&wlr_popup->events.destroy, &popup->destroy);
102 popup->destroy.notify = popup_handle_destroy; 118 popup->destroy.notify = popup_handle_destroy;
103 119
104 wl_signal_add(&xdg_surface->events.map, &popup->child.surface_map);
105 wl_signal_add(&xdg_surface->events.unmap, &popup->child.surface_unmap);
106
107 popup_unconstrain(popup);
108
109 return popup; 120 return popup;
110} 121}
111 122
112
113static struct sway_xdg_shell_view *xdg_shell_view_from_view( 123static struct sway_xdg_shell_view *xdg_shell_view_from_view(
114 struct sway_view *view) { 124 struct sway_view *view) {
115 if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL, 125 if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL,
@@ -122,7 +132,7 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view(
122static void get_constraints(struct sway_view *view, double *min_width, 132static void get_constraints(struct sway_view *view, double *min_width,
123 double *max_width, double *min_height, double *max_height) { 133 double *max_width, double *min_height, double *max_height) {
124 struct wlr_xdg_toplevel_state *state = 134 struct wlr_xdg_toplevel_state *state =
125 &view->wlr_xdg_surface->toplevel->current; 135 &view->wlr_xdg_toplevel->current;
126 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN; 136 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
127 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX; 137 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
128 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN; 138 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
@@ -136,9 +146,9 @@ static const char *get_string_prop(struct sway_view *view,
136 } 146 }
137 switch (prop) { 147 switch (prop) {
138 case VIEW_PROP_TITLE: 148 case VIEW_PROP_TITLE:
139 return view->wlr_xdg_surface->toplevel->title; 149 return view->wlr_xdg_toplevel->title;
140 case VIEW_PROP_APP_ID: 150 case VIEW_PROP_APP_ID:
141 return view->wlr_xdg_surface->toplevel->app_id; 151 return view->wlr_xdg_toplevel->app_id;
142 default: 152 default:
143 return NULL; 153 return NULL;
144 } 154 }
@@ -151,50 +161,52 @@ static uint32_t configure(struct sway_view *view, double lx, double ly,
151 if (xdg_shell_view == NULL) { 161 if (xdg_shell_view == NULL) {
152 return 0; 162 return 0;
153 } 163 }
154 return wlr_xdg_toplevel_set_size(view->wlr_xdg_surface, width, height); 164 return wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel,
165 width, height);
155} 166}
156 167
157static void set_activated(struct sway_view *view, bool activated) { 168static void set_activated(struct sway_view *view, bool activated) {
158 if (xdg_shell_view_from_view(view) == NULL) { 169 if (xdg_shell_view_from_view(view) == NULL) {
159 return; 170 return;
160 } 171 }
161 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 172 wlr_xdg_toplevel_set_activated(view->wlr_xdg_toplevel, activated);
162 if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
163 wlr_xdg_toplevel_set_activated(surface, activated);
164 }
165} 173}
166 174
167static void set_tiled(struct sway_view *view, bool tiled) { 175static void set_tiled(struct sway_view *view, bool tiled) {
168 if (xdg_shell_view_from_view(view) == NULL) { 176 if (xdg_shell_view_from_view(view) == NULL) {
169 return; 177 return;
170 } 178 }
171 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 179 if (wl_resource_get_version(view->wlr_xdg_toplevel->resource) >=
172 enum wlr_edges edges = WLR_EDGE_NONE; 180 XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) {
173 if (tiled) { 181 enum wlr_edges edges = WLR_EDGE_NONE;
174 edges = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | 182 if (tiled) {
175 WLR_EDGE_BOTTOM; 183 edges = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP |
184 WLR_EDGE_BOTTOM;
185 }
186 wlr_xdg_toplevel_set_tiled(view->wlr_xdg_toplevel, edges);
187 } else {
188 // The version is too low for the tiled state; configure as maximized instead
189 // to stop the client from drawing decorations outside of the toplevel geometry.
190 wlr_xdg_toplevel_set_maximized(view->wlr_xdg_toplevel, tiled);
176 } 191 }
177 wlr_xdg_toplevel_set_tiled(surface, edges);
178} 192}
179 193
180static void set_fullscreen(struct sway_view *view, bool fullscreen) { 194static void set_fullscreen(struct sway_view *view, bool fullscreen) {
181 if (xdg_shell_view_from_view(view) == NULL) { 195 if (xdg_shell_view_from_view(view) == NULL) {
182 return; 196 return;
183 } 197 }
184 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 198 wlr_xdg_toplevel_set_fullscreen(view->wlr_xdg_toplevel, fullscreen);
185 wlr_xdg_toplevel_set_fullscreen(surface, fullscreen);
186} 199}
187 200
188static void set_resizing(struct sway_view *view, bool resizing) { 201static void set_resizing(struct sway_view *view, bool resizing) {
189 if (xdg_shell_view_from_view(view) == NULL) { 202 if (xdg_shell_view_from_view(view) == NULL) {
190 return; 203 return;
191 } 204 }
192 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 205 wlr_xdg_toplevel_set_resizing(view->wlr_xdg_toplevel, resizing);
193 wlr_xdg_toplevel_set_resizing(surface, resizing);
194} 206}
195 207
196static bool wants_floating(struct sway_view *view) { 208static bool wants_floating(struct sway_view *view) {
197 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_surface->toplevel; 209 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel;
198 struct wlr_xdg_toplevel_state *state = &toplevel->current; 210 struct wlr_xdg_toplevel_state *state = &toplevel->current;
199 return (state->min_width != 0 && state->min_height != 0 211 return (state->min_width != 0 && state->min_height != 0
200 && (state->min_width == state->max_width 212 && (state->min_width == state->max_width
@@ -202,35 +214,17 @@ static bool wants_floating(struct sway_view *view) {
202 || toplevel->parent; 214 || toplevel->parent;
203} 215}
204 216
205static void for_each_surface(struct sway_view *view,
206 wlr_surface_iterator_func_t iterator, void *user_data) {
207 if (xdg_shell_view_from_view(view) == NULL) {
208 return;
209 }
210 wlr_xdg_surface_for_each_surface(view->wlr_xdg_surface, iterator,
211 user_data);
212}
213
214static void for_each_popup_surface(struct sway_view *view,
215 wlr_surface_iterator_func_t iterator, void *user_data) {
216 if (xdg_shell_view_from_view(view) == NULL) {
217 return;
218 }
219 wlr_xdg_surface_for_each_popup_surface(view->wlr_xdg_surface, iterator,
220 user_data);
221}
222
223static bool is_transient_for(struct sway_view *child, 217static bool is_transient_for(struct sway_view *child,
224 struct sway_view *ancestor) { 218 struct sway_view *ancestor) {
225 if (xdg_shell_view_from_view(child) == NULL) { 219 if (xdg_shell_view_from_view(child) == NULL) {
226 return false; 220 return false;
227 } 221 }
228 struct wlr_xdg_surface *surface = child->wlr_xdg_surface; 222 struct wlr_xdg_toplevel *toplevel = child->wlr_xdg_toplevel;
229 while (surface && surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 223 while (toplevel) {
230 if (surface->toplevel->parent == ancestor->wlr_xdg_surface) { 224 if (toplevel->parent == ancestor->wlr_xdg_toplevel) {
231 return true; 225 return true;
232 } 226 }
233 surface = surface->toplevel->parent; 227 toplevel = toplevel->parent;
234 } 228 }
235 return false; 229 return false;
236} 230}
@@ -239,17 +233,13 @@ static void _close(struct sway_view *view) {
239 if (xdg_shell_view_from_view(view) == NULL) { 233 if (xdg_shell_view_from_view(view) == NULL) {
240 return; 234 return;
241 } 235 }
242 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 236 wlr_xdg_toplevel_send_close(view->wlr_xdg_toplevel);
243 if (surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL
244 && surface->toplevel) {
245 wlr_xdg_toplevel_send_close(surface);
246 }
247} 237}
248 238
249static void close_popups(struct sway_view *view) { 239static void close_popups(struct sway_view *view) {
250 struct wlr_xdg_popup *popup, *tmp; 240 struct wlr_xdg_popup *popup, *tmp;
251 wl_list_for_each_safe(popup, tmp, &view->wlr_xdg_surface->popups, link) { 241 wl_list_for_each_safe(popup, tmp, &view->wlr_xdg_toplevel->base->popups, link) {
252 wlr_xdg_popup_destroy(popup->base); 242 wlr_xdg_popup_destroy(popup);
253 } 243 }
254} 244}
255 245
@@ -271,8 +261,6 @@ static const struct sway_view_impl view_impl = {
271 .set_fullscreen = set_fullscreen, 261 .set_fullscreen = set_fullscreen,
272 .set_resizing = set_resizing, 262 .set_resizing = set_resizing,
273 .wants_floating = wants_floating, 263 .wants_floating = wants_floating,
274 .for_each_surface = for_each_surface,
275 .for_each_popup_surface = for_each_popup_surface,
276 .is_transient_for = is_transient_for, 264 .is_transient_for = is_transient_for,
277 .close = _close, 265 .close = _close,
278 .close_popups = close_popups, 266 .close_popups = close_popups,
@@ -283,7 +271,20 @@ static void handle_commit(struct wl_listener *listener, void *data) {
283 struct sway_xdg_shell_view *xdg_shell_view = 271 struct sway_xdg_shell_view *xdg_shell_view =
284 wl_container_of(listener, xdg_shell_view, commit); 272 wl_container_of(listener, xdg_shell_view, commit);
285 struct sway_view *view = &xdg_shell_view->view; 273 struct sway_view *view = &xdg_shell_view->view;
286 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_surface; 274 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_toplevel->base;
275
276 if (xdg_surface->initial_commit) {
277 if (view->xdg_decoration != NULL) {
278 set_xdg_decoration_mode(view->xdg_decoration);
279 }
280 // XXX: https://github.com/swaywm/sway/issues/2176
281 wlr_xdg_surface_schedule_configure(xdg_surface);
282 return;
283 }
284
285 if (!xdg_surface->surface->mapped) {
286 return;
287 }
287 288
288 struct wlr_box new_geo; 289 struct wlr_box new_geo;
289 wlr_xdg_surface_get_geometry(xdg_surface, &new_geo); 290 wlr_xdg_surface_get_geometry(xdg_surface, &new_geo);
@@ -293,22 +294,35 @@ static void handle_commit(struct wl_listener *listener, void *data) {
293 new_geo.y != view->geometry.y; 294 new_geo.y != view->geometry.y;
294 295
295 if (new_size) { 296 if (new_size) {
296 // The view has unexpectedly sent a new size 297 // The client changed its surface size in this commit. For floating
297 desktop_damage_view(view); 298 // containers, we resize the container to match. For tiling containers,
298 view_update_size(view, new_geo.width, new_geo.height); 299 // we only recenter the surface.
299 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box)); 300 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
300 desktop_damage_view(view); 301 if (container_is_floating(view->container)) {
301 transaction_commit_dirty(); 302 view_update_size(view);
303 // Only set the toplevel size the current container actually has a size.
304 if (view->container->current.width) {
305 wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel, view->geometry.width,
306 view->geometry.height);
307 }
308 transaction_commit_dirty_client();
309 }
310
311 view_center_and_clip_surface(view);
302 } 312 }
303 313
304 if (view->container->node.instruction) { 314 if (view->container->node.instruction) {
305 transaction_notify_view_ready_by_serial(view, 315 bool successful = transaction_notify_view_ready_by_serial(view,
306 xdg_surface->configure_serial); 316 xdg_surface->current.configure_serial);
307 } else if (new_size) { 317
308 transaction_notify_view_ready_immediately(view); 318 // If we saved the view and this commit isn't what we're looking for
319 // that means the user will never actually see the buffers submitted to
320 // us here. Just send frame done events to these surfaces so they can
321 // commit another time for us.
322 if (view->saved_surface_tree && !successful) {
323 view_send_frame_done(view);
324 }
309 } 325 }
310
311 view_damage_from(view);
312} 326}
313 327
314static void handle_set_title(struct wl_listener *listener, void *data) { 328static void handle_set_title(struct wl_listener *listener, void *data) {
@@ -330,31 +344,42 @@ static void handle_new_popup(struct wl_listener *listener, void *data) {
330 struct sway_xdg_shell_view *xdg_shell_view = 344 struct sway_xdg_shell_view *xdg_shell_view =
331 wl_container_of(listener, xdg_shell_view, new_popup); 345 wl_container_of(listener, xdg_shell_view, new_popup);
332 struct wlr_xdg_popup *wlr_popup = data; 346 struct wlr_xdg_popup *wlr_popup = data;
333 popup_create(wlr_popup, &xdg_shell_view->view); 347
348 struct sway_xdg_popup *popup = popup_create(wlr_popup,
349 &xdg_shell_view->view, root->layers.popup);
350 if (!popup) {
351 return;
352 }
353
354 int lx, ly;
355 wlr_scene_node_coords(&popup->view->content_tree->node, &lx, &ly);
356 wlr_scene_node_set_position(&popup->scene_tree->node, lx, ly);
357}
358
359static void handle_request_maximize(struct wl_listener *listener, void *data) {
360 struct sway_xdg_shell_view *xdg_shell_view =
361 wl_container_of(listener, xdg_shell_view, request_maximize);
362 struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel;
363 wlr_xdg_surface_schedule_configure(toplevel->base);
334} 364}
335 365
336static void handle_request_fullscreen(struct wl_listener *listener, void *data) { 366static void handle_request_fullscreen(struct wl_listener *listener, void *data) {
337 struct sway_xdg_shell_view *xdg_shell_view = 367 struct sway_xdg_shell_view *xdg_shell_view =
338 wl_container_of(listener, xdg_shell_view, request_fullscreen); 368 wl_container_of(listener, xdg_shell_view, request_fullscreen);
339 struct wlr_xdg_toplevel_set_fullscreen_event *e = data; 369 struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel;
340 struct wlr_xdg_surface *xdg_surface =
341 xdg_shell_view->view.wlr_xdg_surface;
342 struct sway_view *view = &xdg_shell_view->view; 370 struct sway_view *view = &xdg_shell_view->view;
343 371
344 if (!sway_assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL, 372 if (!toplevel->base->surface->mapped) {
345 "xdg_shell requested fullscreen of surface with role %i",
346 xdg_surface->role)) {
347 return;
348 }
349 if (!xdg_surface->mapped) {
350 return; 373 return;
351 } 374 }
352 375
353 struct sway_container *container = view->container; 376 struct sway_container *container = view->container;
354 if (e->fullscreen && e->output && e->output->data) { 377 struct wlr_xdg_toplevel_requested *req = &toplevel->requested;
355 struct sway_output *output = e->output->data; 378 if (req->fullscreen && req->fullscreen_output && req->fullscreen_output->data) {
379 struct sway_output *output = req->fullscreen_output->data;
356 struct sway_workspace *ws = output_get_active_workspace(output); 380 struct sway_workspace *ws = output_get_active_workspace(output);
357 if (ws && !container_is_scratchpad_hidden(container)) { 381 if (ws && !container_is_scratchpad_hidden(container) &&
382 container->pending.workspace != ws) {
358 if (container_is_floating(container)) { 383 if (container_is_floating(container)) {
359 workspace_add_floating(ws, container); 384 workspace_add_floating(ws, container);
360 } else { 385 } else {
@@ -363,22 +388,18 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
363 } 388 }
364 } 389 }
365 390
366 container_set_fullscreen(container, e->fullscreen); 391 container_set_fullscreen(container, req->fullscreen);
367 392
368 arrange_root(); 393 arrange_root();
369 transaction_commit_dirty(); 394 transaction_commit_dirty();
370} 395}
371 396
372static void handle_request_maximize(struct wl_listener *listener, void *data) {
373 struct wlr_xdg_surface *surface = data;
374 wlr_xdg_surface_schedule_configure(surface);
375}
376
377static void handle_request_move(struct wl_listener *listener, void *data) { 397static void handle_request_move(struct wl_listener *listener, void *data) {
378 struct sway_xdg_shell_view *xdg_shell_view = 398 struct sway_xdg_shell_view *xdg_shell_view =
379 wl_container_of(listener, xdg_shell_view, request_move); 399 wl_container_of(listener, xdg_shell_view, request_move);
380 struct sway_view *view = &xdg_shell_view->view; 400 struct sway_view *view = &xdg_shell_view->view;
381 if (!container_is_floating(view->container)) { 401 if (!container_is_floating(view->container) ||
402 view->container->pending.fullscreen_mode) {
382 return; 403 return;
383 } 404 }
384 struct wlr_xdg_toplevel_move_event *e = data; 405 struct wlr_xdg_toplevel_move_event *e = data;
@@ -413,10 +434,9 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
413 434
414 view_unmap(view); 435 view_unmap(view);
415 436
416 wl_list_remove(&xdg_shell_view->commit.link);
417 wl_list_remove(&xdg_shell_view->new_popup.link); 437 wl_list_remove(&xdg_shell_view->new_popup.link);
418 wl_list_remove(&xdg_shell_view->request_fullscreen.link);
419 wl_list_remove(&xdg_shell_view->request_maximize.link); 438 wl_list_remove(&xdg_shell_view->request_maximize.link);
439 wl_list_remove(&xdg_shell_view->request_fullscreen.link);
420 wl_list_remove(&xdg_shell_view->request_move.link); 440 wl_list_remove(&xdg_shell_view->request_move.link);
421 wl_list_remove(&xdg_shell_view->request_resize.link); 441 wl_list_remove(&xdg_shell_view->request_resize.link);
422 wl_list_remove(&xdg_shell_view->set_title.link); 442 wl_list_remove(&xdg_shell_view->set_title.link);
@@ -427,62 +447,61 @@ static void handle_map(struct wl_listener *listener, void *data) {
427 struct sway_xdg_shell_view *xdg_shell_view = 447 struct sway_xdg_shell_view *xdg_shell_view =
428 wl_container_of(listener, xdg_shell_view, map); 448 wl_container_of(listener, xdg_shell_view, map);
429 struct sway_view *view = &xdg_shell_view->view; 449 struct sway_view *view = &xdg_shell_view->view;
430 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_surface; 450 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel;
431 451
432 view->natural_width = view->wlr_xdg_surface->geometry.width; 452 view->natural_width = toplevel->base->current.geometry.width;
433 view->natural_height = view->wlr_xdg_surface->geometry.height; 453 view->natural_height = toplevel->base->current.geometry.height;
434 if (!view->natural_width && !view->natural_height) { 454 if (!view->natural_width && !view->natural_height) {
435 view->natural_width = view->wlr_xdg_surface->surface->current.width; 455 view->natural_width = toplevel->base->surface->current.width;
436 view->natural_height = view->wlr_xdg_surface->surface->current.height; 456 view->natural_height = toplevel->base->surface->current.height;
437 } 457 }
438 458
439 bool csd = false; 459 bool csd = false;
440 460
441 if (!view->xdg_decoration) { 461 if (view->xdg_decoration) {
462 enum wlr_xdg_toplevel_decoration_v1_mode mode =
463 view->xdg_decoration->wlr_xdg_decoration->requested_mode;
464 csd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
465 } else {
442 struct sway_server_decoration *deco = 466 struct sway_server_decoration *deco =
443 decoration_from_surface(xdg_surface->surface); 467 decoration_from_surface(toplevel->base->surface);
444 csd = !deco || deco->wlr_server_decoration->mode == 468 csd = !deco || deco->wlr_server_decoration->mode ==
445 WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; 469 WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT;
446
447 } 470 }
448 471
449 view_map(view, view->wlr_xdg_surface->surface, 472 view_map(view, toplevel->base->surface,
450 xdg_surface->toplevel->client_pending.fullscreen, 473 toplevel->requested.fullscreen,
451 xdg_surface->toplevel->client_pending.fullscreen_output, 474 toplevel->requested.fullscreen_output,
452 csd); 475 csd);
453 476
454 transaction_commit_dirty(); 477 transaction_commit_dirty();
455 478
456 xdg_shell_view->commit.notify = handle_commit;
457 wl_signal_add(&xdg_surface->surface->events.commit,
458 &xdg_shell_view->commit);
459
460 xdg_shell_view->new_popup.notify = handle_new_popup; 479 xdg_shell_view->new_popup.notify = handle_new_popup;
461 wl_signal_add(&xdg_surface->events.new_popup, 480 wl_signal_add(&toplevel->base->events.new_popup,
462 &xdg_shell_view->new_popup); 481 &xdg_shell_view->new_popup);
463 482
464 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen;
465 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen,
466 &xdg_shell_view->request_fullscreen);
467
468 xdg_shell_view->request_maximize.notify = handle_request_maximize; 483 xdg_shell_view->request_maximize.notify = handle_request_maximize;
469 wl_signal_add(&xdg_surface->toplevel->events.request_maximize, 484 wl_signal_add(&toplevel->events.request_maximize,
470 &xdg_shell_view->request_maximize); 485 &xdg_shell_view->request_maximize);
471 486
487 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen;
488 wl_signal_add(&toplevel->events.request_fullscreen,
489 &xdg_shell_view->request_fullscreen);
490
472 xdg_shell_view->request_move.notify = handle_request_move; 491 xdg_shell_view->request_move.notify = handle_request_move;
473 wl_signal_add(&xdg_surface->toplevel->events.request_move, 492 wl_signal_add(&toplevel->events.request_move,
474 &xdg_shell_view->request_move); 493 &xdg_shell_view->request_move);
475 494
476 xdg_shell_view->request_resize.notify = handle_request_resize; 495 xdg_shell_view->request_resize.notify = handle_request_resize;
477 wl_signal_add(&xdg_surface->toplevel->events.request_resize, 496 wl_signal_add(&toplevel->events.request_resize,
478 &xdg_shell_view->request_resize); 497 &xdg_shell_view->request_resize);
479 498
480 xdg_shell_view->set_title.notify = handle_set_title; 499 xdg_shell_view->set_title.notify = handle_set_title;
481 wl_signal_add(&xdg_surface->toplevel->events.set_title, 500 wl_signal_add(&toplevel->events.set_title,
482 &xdg_shell_view->set_title); 501 &xdg_shell_view->set_title);
483 502
484 xdg_shell_view->set_app_id.notify = handle_set_app_id; 503 xdg_shell_view->set_app_id.notify = handle_set_app_id;
485 wl_signal_add(&xdg_surface->toplevel->events.set_app_id, 504 wl_signal_add(&toplevel->events.set_app_id,
486 &xdg_shell_view->set_app_id); 505 &xdg_shell_view->set_app_id);
487} 506}
488 507
@@ -496,7 +515,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
496 wl_list_remove(&xdg_shell_view->destroy.link); 515 wl_list_remove(&xdg_shell_view->destroy.link);
497 wl_list_remove(&xdg_shell_view->map.link); 516 wl_list_remove(&xdg_shell_view->map.link);
498 wl_list_remove(&xdg_shell_view->unmap.link); 517 wl_list_remove(&xdg_shell_view->unmap.link);
499 view->wlr_xdg_surface = NULL; 518 wl_list_remove(&xdg_shell_view->commit.link);
519 view->wlr_xdg_toplevel = NULL;
500 if (view->xdg_decoration) { 520 if (view->xdg_decoration) {
501 view->xdg_decoration->view = NULL; 521 view->xdg_decoration->view = NULL;
502 } 522 }
@@ -508,17 +528,12 @@ struct sway_view *view_from_wlr_xdg_surface(
508 return xdg_surface->data; 528 return xdg_surface->data;
509} 529}
510 530
511void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { 531void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data) {
512 struct wlr_xdg_surface *xdg_surface = data; 532 struct wlr_xdg_toplevel *xdg_toplevel = data;
513
514 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
515 sway_log(SWAY_DEBUG, "New xdg_shell popup");
516 return;
517 }
518 533
519 sway_log(SWAY_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'", 534 sway_log(SWAY_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'",
520 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id); 535 xdg_toplevel->title, xdg_toplevel->app_id);
521 wlr_xdg_surface_ping(xdg_surface); 536 wlr_xdg_surface_ping(xdg_toplevel->base);
522 537
523 struct sway_xdg_shell_view *xdg_shell_view = 538 struct sway_xdg_shell_view *xdg_shell_view =
524 calloc(1, sizeof(struct sway_xdg_shell_view)); 539 calloc(1, sizeof(struct sway_xdg_shell_view));
@@ -526,17 +541,26 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
526 return; 541 return;
527 } 542 }
528 543
529 view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl); 544 if (!view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl)) {
530 xdg_shell_view->view.wlr_xdg_surface = xdg_surface; 545 free(xdg_shell_view);
546 return;
547 }
548 xdg_shell_view->view.wlr_xdg_toplevel = xdg_toplevel;
531 549
532 xdg_shell_view->map.notify = handle_map; 550 xdg_shell_view->map.notify = handle_map;
533 wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); 551 wl_signal_add(&xdg_toplevel->base->surface->events.map, &xdg_shell_view->map);
534 552
535 xdg_shell_view->unmap.notify = handle_unmap; 553 xdg_shell_view->unmap.notify = handle_unmap;
536 wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); 554 wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &xdg_shell_view->unmap);
555
556 xdg_shell_view->commit.notify = handle_commit;
557 wl_signal_add(&xdg_toplevel->base->surface->events.commit,
558 &xdg_shell_view->commit);
537 559
538 xdg_shell_view->destroy.notify = handle_destroy; 560 xdg_shell_view->destroy.notify = handle_destroy;
539 wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); 561 wl_signal_add(&xdg_toplevel->events.destroy, &xdg_shell_view->destroy);
562
563 wlr_scene_xdg_surface_create(xdg_shell_view->view.content_tree, xdg_toplevel->base);
540 564
541 xdg_surface->data = xdg_shell_view; 565 xdg_toplevel->base->data = xdg_shell_view;
542} 566}
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index e1a2e463..9f3f4d5f 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -5,16 +5,20 @@
5#include <wayland-server-core.h> 5#include <wayland-server-core.h>
6#include <wlr/types/wlr_output_layout.h> 6#include <wlr/types/wlr_output_layout.h>
7#include <wlr/types/wlr_output.h> 7#include <wlr/types/wlr_output.h>
8#include <wlr/types/wlr_xdg_activation_v1.h>
9#include <wlr/types/wlr_scene.h>
8#include <wlr/xwayland.h> 10#include <wlr/xwayland.h>
11#include <xcb/xcb_icccm.h>
9#include "log.h" 12#include "log.h"
10#include "sway/desktop.h"
11#include "sway/desktop/transaction.h" 13#include "sway/desktop/transaction.h"
12#include "sway/input/cursor.h" 14#include "sway/input/cursor.h"
13#include "sway/input/input-manager.h" 15#include "sway/input/input-manager.h"
14#include "sway/input/seat.h" 16#include "sway/input/seat.h"
15#include "sway/output.h" 17#include "sway/output.h"
18#include "sway/scene_descriptor.h"
16#include "sway/tree/arrange.h" 19#include "sway/tree/arrange.h"
17#include "sway/tree/container.h" 20#include "sway/tree/container.h"
21#include "sway/server.h"
18#include "sway/tree/view.h" 22#include "sway/tree/view.h"
19#include "sway/tree/workspace.h" 23#include "sway/tree/workspace.h"
20 24
@@ -42,29 +46,12 @@ static void unmanaged_handle_request_configure(struct wl_listener *listener,
42 ev->width, ev->height); 46 ev->width, ev->height);
43} 47}
44 48
45static void unmanaged_handle_commit(struct wl_listener *listener, void *data) {
46 struct sway_xwayland_unmanaged *surface =
47 wl_container_of(listener, surface, commit);
48 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
49
50 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly,
51 false);
52}
53
54static void unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) { 49static void unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) {
55 struct sway_xwayland_unmanaged *surface = 50 struct sway_xwayland_unmanaged *surface =
56 wl_container_of(listener, surface, set_geometry); 51 wl_container_of(listener, surface, set_geometry);
57 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 52 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
58 53
59 if (xsurface->x != surface->lx || xsurface->y != surface->ly) { 54 wlr_scene_node_set_position(&surface->surface_scene->buffer->node, xsurface->x, xsurface->y);
60 // Surface has moved
61 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly,
62 true);
63 surface->lx = xsurface->x;
64 surface->ly = xsurface->y;
65 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly,
66 true);
67 }
68} 55}
69 56
70static void unmanaged_handle_map(struct wl_listener *listener, void *data) { 57static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
@@ -72,17 +59,18 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
72 wl_container_of(listener, surface, map); 59 wl_container_of(listener, surface, map);
73 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 60 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
74 61
75 wl_list_insert(root->xwayland_unmanaged.prev, &surface->link); 62 surface->surface_scene = wlr_scene_surface_create(root->layers.unmanaged,
76 63 xsurface->surface);
77 wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry);
78 surface->set_geometry.notify = unmanaged_handle_set_geometry;
79 64
80 wl_signal_add(&xsurface->surface->events.commit, &surface->commit); 65 if (surface->surface_scene) {
81 surface->commit.notify = unmanaged_handle_commit; 66 scene_descriptor_assign(&surface->surface_scene->buffer->node,
67 SWAY_SCENE_DESC_XWAYLAND_UNMANAGED, surface);
68 wlr_scene_node_set_position(&surface->surface_scene->buffer->node,
69 xsurface->x, xsurface->y);
82 70
83 surface->lx = xsurface->x; 71 wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry);
84 surface->ly = xsurface->y; 72 surface->set_geometry.notify = unmanaged_handle_set_geometry;
85 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true); 73 }
86 74
87 if (wlr_xwayland_or_surface_wants_focus(xsurface)) { 75 if (wlr_xwayland_or_surface_wants_focus(xsurface)) {
88 struct sway_seat *seat = input_manager_current_seat(); 76 struct sway_seat *seat = input_manager_current_seat();
@@ -96,23 +84,22 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
96 struct sway_xwayland_unmanaged *surface = 84 struct sway_xwayland_unmanaged *surface =
97 wl_container_of(listener, surface, unmap); 85 wl_container_of(listener, surface, unmap);
98 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 86 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
99 desktop_damage_surface(xsurface->surface, xsurface->x, xsurface->y, true); 87
100 wl_list_remove(&surface->link); 88 if (surface->surface_scene) {
101 wl_list_remove(&surface->set_geometry.link); 89 wl_list_remove(&surface->set_geometry.link);
102 wl_list_remove(&surface->commit.link); 90
91 wlr_scene_node_destroy(&surface->surface_scene->buffer->node);
92 surface->surface_scene = NULL;
93 }
103 94
104 struct sway_seat *seat = input_manager_current_seat(); 95 struct sway_seat *seat = input_manager_current_seat();
105 if (seat->wlr_seat->keyboard_state.focused_surface == xsurface->surface) { 96 if (seat->wlr_seat->keyboard_state.focused_surface == xsurface->surface) {
106 // This simply returns focus to the parent surface if there's one available. 97 // This simply returns focus to the parent surface if there's one available.
107 // This seems to handle JetBrains issues. 98 // This seems to handle JetBrains issues.
108 if (xsurface->parent && xsurface->parent->surface && 99 if (xsurface->parent && xsurface->parent->surface
109 wlr_surface_is_xwayland_surface(xsurface->parent->surface)) { 100 && wlr_xwayland_or_surface_wants_focus(xsurface->parent)) {
110 struct wlr_xwayland_surface *next_surface = 101 seat_set_focus_surface(seat, xsurface->parent->surface, false);
111 wlr_xwayland_surface_from_wlr_surface(xsurface->parent->surface); 102 return;
112 if (wlr_xwayland_or_surface_wants_focus(next_surface)) {
113 seat_set_focus_surface(seat, xsurface->parent->surface, false);
114 return;
115 }
116 } 103 }
117 104
118 // Restore focus 105 // Restore focus
@@ -125,18 +112,53 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
125 } 112 }
126} 113}
127 114
115static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) {
116 struct sway_xwayland_unmanaged *surface =
117 wl_container_of(listener, surface, request_activate);
118 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
119 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
120 return;
121 }
122 struct sway_seat *seat = input_manager_current_seat();
123 struct sway_container *focus = seat_get_focused_container(seat);
124 if (focus && focus->view && focus->view->pid != xsurface->pid) {
125 return;
126 }
127
128 seat_set_focus_surface(seat, xsurface->surface, false);
129}
130
131static void unmanaged_handle_associate(struct wl_listener *listener, void *data) {
132 struct sway_xwayland_unmanaged *surface =
133 wl_container_of(listener, surface, associate);
134 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
135 wl_signal_add(&xsurface->surface->events.map, &surface->map);
136 surface->map.notify = unmanaged_handle_map;
137 wl_signal_add(&xsurface->surface->events.unmap, &surface->unmap);
138 surface->unmap.notify = unmanaged_handle_unmap;
139}
140
141static void unmanaged_handle_dissociate(struct wl_listener *listener, void *data) {
142 struct sway_xwayland_unmanaged *surface =
143 wl_container_of(listener, surface, dissociate);
144 wl_list_remove(&surface->map.link);
145 wl_list_remove(&surface->unmap.link);
146}
147
128static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) { 148static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) {
129 struct sway_xwayland_unmanaged *surface = 149 struct sway_xwayland_unmanaged *surface =
130 wl_container_of(listener, surface, destroy); 150 wl_container_of(listener, surface, destroy);
131 wl_list_remove(&surface->request_configure.link); 151 wl_list_remove(&surface->request_configure.link);
132 wl_list_remove(&surface->map.link); 152 wl_list_remove(&surface->associate.link);
133 wl_list_remove(&surface->unmap.link); 153 wl_list_remove(&surface->dissociate.link);
134 wl_list_remove(&surface->destroy.link); 154 wl_list_remove(&surface->destroy.link);
135 wl_list_remove(&surface->override_redirect.link); 155 wl_list_remove(&surface->override_redirect.link);
156 wl_list_remove(&surface->request_activate.link);
136 free(surface); 157 free(surface);
137} 158}
138 159
139static void handle_map(struct wl_listener *listener, void *data); 160static void handle_map(struct wl_listener *listener, void *data);
161static void handle_associate(struct wl_listener *listener, void *data);
140 162
141struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface); 163struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface);
142 164
@@ -145,14 +167,22 @@ static void unmanaged_handle_override_redirect(struct wl_listener *listener, voi
145 wl_container_of(listener, surface, override_redirect); 167 wl_container_of(listener, surface, override_redirect);
146 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 168 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
147 169
148 bool mapped = xsurface->mapped; 170 bool associated = xsurface->surface != NULL;
171 bool mapped = associated && xsurface->surface->mapped;
149 if (mapped) { 172 if (mapped) {
150 unmanaged_handle_unmap(&surface->unmap, NULL); 173 unmanaged_handle_unmap(&surface->unmap, NULL);
151 } 174 }
175 if (associated) {
176 unmanaged_handle_dissociate(&surface->dissociate, NULL);
177 }
152 178
153 unmanaged_handle_destroy(&surface->destroy, NULL); 179 unmanaged_handle_destroy(&surface->destroy, NULL);
154 xsurface->data = NULL; 180 xsurface->data = NULL;
181
155 struct sway_xwayland_view *xwayland_view = create_xwayland_view(xsurface); 182 struct sway_xwayland_view *xwayland_view = create_xwayland_view(xsurface);
183 if (associated) {
184 handle_associate(&xwayland_view->associate, NULL);
185 }
156 if (mapped) { 186 if (mapped) {
157 handle_map(&xwayland_view->map, xsurface); 187 handle_map(&xwayland_view->map, xsurface);
158 } 188 }
@@ -172,14 +202,16 @@ static struct sway_xwayland_unmanaged *create_unmanaged(
172 wl_signal_add(&xsurface->events.request_configure, 202 wl_signal_add(&xsurface->events.request_configure,
173 &surface->request_configure); 203 &surface->request_configure);
174 surface->request_configure.notify = unmanaged_handle_request_configure; 204 surface->request_configure.notify = unmanaged_handle_request_configure;
175 wl_signal_add(&xsurface->events.map, &surface->map); 205 wl_signal_add(&xsurface->events.associate, &surface->associate);
176 surface->map.notify = unmanaged_handle_map; 206 surface->associate.notify = unmanaged_handle_associate;
177 wl_signal_add(&xsurface->events.unmap, &surface->unmap); 207 wl_signal_add(&xsurface->events.dissociate, &surface->dissociate);
178 surface->unmap.notify = unmanaged_handle_unmap; 208 surface->dissociate.notify = unmanaged_handle_dissociate;
179 wl_signal_add(&xsurface->events.destroy, &surface->destroy); 209 wl_signal_add(&xsurface->events.destroy, &surface->destroy);
180 surface->destroy.notify = unmanaged_handle_destroy; 210 surface->destroy.notify = unmanaged_handle_destroy;
181 wl_signal_add(&xsurface->events.set_override_redirect, &surface->override_redirect); 211 wl_signal_add(&xsurface->events.set_override_redirect, &surface->override_redirect);
182 surface->override_redirect.notify = unmanaged_handle_override_redirect; 212 surface->override_redirect.notify = unmanaged_handle_override_redirect;
213 wl_signal_add(&xsurface->events.request_activate, &surface->request_activate);
214 surface->request_activate.notify = unmanaged_handle_request_activate;
183 215
184 return surface; 216 return surface;
185} 217}
@@ -258,6 +290,7 @@ static void set_activated(struct sway_view *view, bool activated) {
258 } 290 }
259 291
260 wlr_xwayland_surface_activate(surface, activated); 292 wlr_xwayland_surface_activate(surface, activated);
293 wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE);
261} 294}
262 295
263static void set_tiled(struct sway_view *view, bool tiled) { 296static void set_tiled(struct sway_view *view, bool tiled) {
@@ -297,7 +330,7 @@ static bool wants_floating(struct sway_view *view) {
297 } 330 }
298 } 331 }
299 332
300 struct wlr_xwayland_surface_size_hints *size_hints = surface->size_hints; 333 xcb_size_hints_t *size_hints = surface->size_hints;
301 if (size_hints != NULL && 334 if (size_hints != NULL &&
302 size_hints->min_width > 0 && size_hints->min_height > 0 && 335 size_hints->min_width > 0 && size_hints->min_height > 0 &&
303 (size_hints->max_width == size_hints->min_width || 336 (size_hints->max_width == size_hints->min_width ||
@@ -351,7 +384,7 @@ static void destroy(struct sway_view *view) {
351static void get_constraints(struct sway_view *view, double *min_width, 384static void get_constraints(struct sway_view *view, double *min_width,
352 double *max_width, double *min_height, double *max_height) { 385 double *max_width, double *min_height, double *max_height) {
353 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; 386 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface;
354 struct wlr_xwayland_surface_size_hints *size_hints = surface->size_hints; 387 xcb_size_hints_t *size_hints = surface->size_hints;
355 388
356 if (size_hints == NULL) { 389 if (size_hints == NULL) {
357 *min_width = DBL_MIN; 390 *min_width = DBL_MIN;
@@ -381,17 +414,6 @@ static const struct sway_view_impl view_impl = {
381 .destroy = destroy, 414 .destroy = destroy,
382}; 415};
383 416
384static void get_geometry(struct sway_view *view, struct wlr_box *box) {
385 box->x = box->y = 0;
386 if (view->surface) {
387 box->width = view->surface->current.width;
388 box->height = view->surface->current.height;
389 } else {
390 box->width = 0;
391 box->height = 0;
392 }
393}
394
395static void handle_commit(struct wl_listener *listener, void *data) { 417static void handle_commit(struct wl_listener *listener, void *data) {
396 struct sway_xwayland_view *xwayland_view = 418 struct sway_xwayland_view *xwayland_view =
397 wl_container_of(listener, xwayland_view, commit); 419 wl_container_of(listener, xwayland_view, commit);
@@ -399,33 +421,38 @@ static void handle_commit(struct wl_listener *listener, void *data) {
399 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 421 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
400 struct wlr_surface_state *state = &xsurface->surface->current; 422 struct wlr_surface_state *state = &xsurface->surface->current;
401 423
424 struct wlr_box new_geo = {0};
425 new_geo.width = state->width;
426 new_geo.height = state->height;
427
428 bool new_size = new_geo.width != view->geometry.width ||
429 new_geo.height != view->geometry.height;
430
431 if (new_size) {
432 // The client changed its surface size in this commit. For floating
433 // containers, we resize the container to match. For tiling containers,
434 // we only recenter the surface.
435 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
436 if (container_is_floating(view->container)) {
437 view_update_size(view);
438 transaction_commit_dirty_client();
439 }
440
441 view_center_and_clip_surface(view);
442 }
443
402 if (view->container->node.instruction) { 444 if (view->container->node.instruction) {
403 get_geometry(view, &view->geometry); 445 bool successful = transaction_notify_view_ready_by_geometry(view,
404 transaction_notify_view_ready_by_geometry(view,
405 xsurface->x, xsurface->y, state->width, state->height); 446 xsurface->x, xsurface->y, state->width, state->height);
406 } else { 447
407 struct wlr_box new_geo; 448 // If we saved the view and this commit isn't what we're looking for
408 get_geometry(view, &new_geo); 449 // that means the user will never actually see the buffers submitted to
409 450 // us here. Just send frame done events to these surfaces so they can
410 if ((new_geo.width != view->geometry.width || 451 // commit another time for us.
411 new_geo.height != view->geometry.height || 452 if (view->saved_surface_tree && !successful) {
412 new_geo.x != view->geometry.x || 453 view_send_frame_done(view);
413 new_geo.y != view->geometry.y)) {
414 // The view has unexpectedly sent a new size
415 // eg. The Firefox "Save As" dialog when downloading a file
416 desktop_damage_view(view);
417 view_update_size(view, new_geo.width, new_geo.height);
418 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
419 desktop_damage_view(view);
420 transaction_commit_dirty();
421 transaction_notify_view_ready_by_geometry(view,
422 xsurface->x, xsurface->y, new_geo.width, new_geo.height);
423 } else {
424 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
425 } 454 }
426 } 455 }
427
428 view_damage_from(view);
429} 456}
430 457
431static void handle_destroy(struct wl_listener *listener, void *data) { 458static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -438,6 +465,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
438 wl_list_remove(&xwayland_view->commit.link); 465 wl_list_remove(&xwayland_view->commit.link);
439 } 466 }
440 467
468 xwayland_view->view.wlr_xwayland_surface = NULL;
469
441 wl_list_remove(&xwayland_view->destroy.link); 470 wl_list_remove(&xwayland_view->destroy.link);
442 wl_list_remove(&xwayland_view->request_configure.link); 471 wl_list_remove(&xwayland_view->request_configure.link);
443 wl_list_remove(&xwayland_view->request_fullscreen.link); 472 wl_list_remove(&xwayland_view->request_fullscreen.link);
@@ -448,11 +477,12 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
448 wl_list_remove(&xwayland_view->set_title.link); 477 wl_list_remove(&xwayland_view->set_title.link);
449 wl_list_remove(&xwayland_view->set_class.link); 478 wl_list_remove(&xwayland_view->set_class.link);
450 wl_list_remove(&xwayland_view->set_role.link); 479 wl_list_remove(&xwayland_view->set_role.link);
480 wl_list_remove(&xwayland_view->set_startup_id.link);
451 wl_list_remove(&xwayland_view->set_window_type.link); 481 wl_list_remove(&xwayland_view->set_window_type.link);
452 wl_list_remove(&xwayland_view->set_hints.link); 482 wl_list_remove(&xwayland_view->set_hints.link);
453 wl_list_remove(&xwayland_view->set_decorations.link); 483 wl_list_remove(&xwayland_view->set_decorations.link);
454 wl_list_remove(&xwayland_view->map.link); 484 wl_list_remove(&xwayland_view->associate.link);
455 wl_list_remove(&xwayland_view->unmap.link); 485 wl_list_remove(&xwayland_view->dissociate.link);
456 wl_list_remove(&xwayland_view->override_redirect.link); 486 wl_list_remove(&xwayland_view->override_redirect.link);
457 view_begin_destroy(&xwayland_view->view); 487 view_begin_destroy(&xwayland_view->view);
458} 488}
@@ -466,16 +496,28 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
466 return; 496 return;
467 } 497 }
468 498
499 wl_list_remove(&xwayland_view->commit.link);
500 wl_list_remove(&xwayland_view->surface_tree_destroy.link);
501
502 if (xwayland_view->surface_tree) {
503 wlr_scene_node_destroy(&xwayland_view->surface_tree->node);
504 xwayland_view->surface_tree = NULL;
505 }
506
469 view_unmap(view); 507 view_unmap(view);
508}
470 509
471 wl_list_remove(&xwayland_view->commit.link); 510static void handle_surface_tree_destroy(struct wl_listener *listener, void *data) {
511 struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view,
512 surface_tree_destroy);
513 xwayland_view->surface_tree = NULL;
472} 514}
473 515
474static void handle_map(struct wl_listener *listener, void *data) { 516static void handle_map(struct wl_listener *listener, void *data) {
475 struct sway_xwayland_view *xwayland_view = 517 struct sway_xwayland_view *xwayland_view =
476 wl_container_of(listener, xwayland_view, map); 518 wl_container_of(listener, xwayland_view, map);
477 struct wlr_xwayland_surface *xsurface = data;
478 struct sway_view *view = &xwayland_view->view; 519 struct sway_view *view = &xwayland_view->view;
520 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
479 521
480 view->natural_width = xsurface->width; 522 view->natural_width = xsurface->width;
481 view->natural_height = xsurface->height; 523 view->natural_height = xsurface->height;
@@ -488,23 +530,42 @@ static void handle_map(struct wl_listener *listener, void *data) {
488 // Put it back into the tree 530 // Put it back into the tree
489 view_map(view, xsurface->surface, xsurface->fullscreen, NULL, false); 531 view_map(view, xsurface->surface, xsurface->fullscreen, NULL, false);
490 532
533 xwayland_view->surface_tree = wlr_scene_subsurface_tree_create(
534 xwayland_view->view.content_tree, xsurface->surface);
535
536 if (xwayland_view->surface_tree) {
537 xwayland_view->surface_tree_destroy.notify = handle_surface_tree_destroy;
538 wl_signal_add(&xwayland_view->surface_tree->node.events.destroy,
539 &xwayland_view->surface_tree_destroy);
540 }
541
491 transaction_commit_dirty(); 542 transaction_commit_dirty();
492} 543}
493 544
545static void handle_dissociate(struct wl_listener *listener, void *data);
546
494static void handle_override_redirect(struct wl_listener *listener, void *data) { 547static void handle_override_redirect(struct wl_listener *listener, void *data) {
495 struct sway_xwayland_view *xwayland_view = 548 struct sway_xwayland_view *xwayland_view =
496 wl_container_of(listener, xwayland_view, override_redirect); 549 wl_container_of(listener, xwayland_view, override_redirect);
497 struct wlr_xwayland_surface *xsurface = data;
498 struct sway_view *view = &xwayland_view->view; 550 struct sway_view *view = &xwayland_view->view;
551 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
499 552
500 bool mapped = xsurface->mapped; 553 bool associated = xsurface->surface != NULL;
554 bool mapped = associated && xsurface->surface->mapped;
501 if (mapped) { 555 if (mapped) {
502 handle_unmap(&xwayland_view->unmap, NULL); 556 handle_unmap(&xwayland_view->unmap, NULL);
503 } 557 }
558 if (associated) {
559 handle_dissociate(&xwayland_view->dissociate, NULL);
560 }
504 561
505 handle_destroy(&xwayland_view->destroy, view); 562 handle_destroy(&xwayland_view->destroy, view);
506 xsurface->data = NULL; 563 xsurface->data = NULL;
564
507 struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface); 565 struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface);
566 if (associated) {
567 unmanaged_handle_associate(&unmanaged->associate, NULL);
568 }
508 if (mapped) { 569 if (mapped) {
509 unmanaged_handle_map(&unmanaged->map, xsurface); 570 unmanaged_handle_map(&unmanaged->map, xsurface);
510 } 571 }
@@ -516,7 +577,7 @@ static void handle_request_configure(struct wl_listener *listener, void *data) {
516 struct wlr_xwayland_surface_configure_event *ev = data; 577 struct wlr_xwayland_surface_configure_event *ev = data;
517 struct sway_view *view = &xwayland_view->view; 578 struct sway_view *view = &xwayland_view->view;
518 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 579 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
519 if (!xsurface->mapped) { 580 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
520 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, 581 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y,
521 ev->width, ev->height); 582 ev->width, ev->height);
522 return; 583 return;
@@ -527,10 +588,10 @@ static void handle_request_configure(struct wl_listener *listener, void *data) {
527 view->natural_height = ev->height; 588 view->natural_height = ev->height;
528 container_floating_resize_and_center(view->container); 589 container_floating_resize_and_center(view->container);
529 590
530 configure(view, view->container->content_x, 591 configure(view, view->container->pending.content_x,
531 view->container->content_y, 592 view->container->pending.content_y,
532 view->container->content_width, 593 view->container->pending.content_width,
533 view->container->content_height); 594 view->container->pending.content_height);
534 node_set_dirty(&view->container->node); 595 node_set_dirty(&view->container->node);
535 } else { 596 } else {
536 configure(view, view->container->current.content_x, 597 configure(view, view->container->current.content_x,
@@ -545,7 +606,7 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
545 wl_container_of(listener, xwayland_view, request_fullscreen); 606 wl_container_of(listener, xwayland_view, request_fullscreen);
546 struct sway_view *view = &xwayland_view->view; 607 struct sway_view *view = &xwayland_view->view;
547 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 608 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
548 if (!xsurface->mapped) { 609 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
549 return; 610 return;
550 } 611 }
551 container_set_fullscreen(view->container, xsurface->fullscreen); 612 container_set_fullscreen(view->container, xsurface->fullscreen);
@@ -559,7 +620,7 @@ static void handle_request_minimize(struct wl_listener *listener, void *data) {
559 wl_container_of(listener, xwayland_view, request_minimize); 620 wl_container_of(listener, xwayland_view, request_minimize);
560 struct sway_view *view = &xwayland_view->view; 621 struct sway_view *view = &xwayland_view->view;
561 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 622 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
562 if (!xsurface->mapped) { 623 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
563 return; 624 return;
564 } 625 }
565 626
@@ -574,10 +635,11 @@ static void handle_request_move(struct wl_listener *listener, void *data) {
574 wl_container_of(listener, xwayland_view, request_move); 635 wl_container_of(listener, xwayland_view, request_move);
575 struct sway_view *view = &xwayland_view->view; 636 struct sway_view *view = &xwayland_view->view;
576 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 637 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
577 if (!xsurface->mapped) { 638 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
578 return; 639 return;
579 } 640 }
580 if (!container_is_floating(view->container)) { 641 if (!container_is_floating(view->container) ||
642 view->container->pending.fullscreen_mode) {
581 return; 643 return;
582 } 644 }
583 struct sway_seat *seat = input_manager_current_seat(); 645 struct sway_seat *seat = input_manager_current_seat();
@@ -589,7 +651,7 @@ static void handle_request_resize(struct wl_listener *listener, void *data) {
589 wl_container_of(listener, xwayland_view, request_resize); 651 wl_container_of(listener, xwayland_view, request_resize);
590 struct sway_view *view = &xwayland_view->view; 652 struct sway_view *view = &xwayland_view->view;
591 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 653 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
592 if (!xsurface->mapped) { 654 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
593 return; 655 return;
594 } 656 }
595 if (!container_is_floating(view->container)) { 657 if (!container_is_floating(view->container)) {
@@ -605,10 +667,10 @@ static void handle_request_activate(struct wl_listener *listener, void *data) {
605 wl_container_of(listener, xwayland_view, request_activate); 667 wl_container_of(listener, xwayland_view, request_activate);
606 struct sway_view *view = &xwayland_view->view; 668 struct sway_view *view = &xwayland_view->view;
607 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 669 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
608 if (!xsurface->mapped) { 670 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
609 return; 671 return;
610 } 672 }
611 view_request_activate(view); 673 view_request_activate(view, NULL);
612 674
613 transaction_commit_dirty(); 675 transaction_commit_dirty();
614} 676}
@@ -618,7 +680,7 @@ static void handle_set_title(struct wl_listener *listener, void *data) {
618 wl_container_of(listener, xwayland_view, set_title); 680 wl_container_of(listener, xwayland_view, set_title);
619 struct sway_view *view = &xwayland_view->view; 681 struct sway_view *view = &xwayland_view->view;
620 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 682 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
621 if (!xsurface->mapped) { 683 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
622 return; 684 return;
623 } 685 }
624 view_update_title(view, false); 686 view_update_title(view, false);
@@ -630,7 +692,7 @@ static void handle_set_class(struct wl_listener *listener, void *data) {
630 wl_container_of(listener, xwayland_view, set_class); 692 wl_container_of(listener, xwayland_view, set_class);
631 struct sway_view *view = &xwayland_view->view; 693 struct sway_view *view = &xwayland_view->view;
632 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 694 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
633 if (!xsurface->mapped) { 695 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
634 return; 696 return;
635 } 697 }
636 view_execute_criteria(view); 698 view_execute_criteria(view);
@@ -641,18 +703,43 @@ static void handle_set_role(struct wl_listener *listener, void *data) {
641 wl_container_of(listener, xwayland_view, set_role); 703 wl_container_of(listener, xwayland_view, set_role);
642 struct sway_view *view = &xwayland_view->view; 704 struct sway_view *view = &xwayland_view->view;
643 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 705 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
644 if (!xsurface->mapped) { 706 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
645 return; 707 return;
646 } 708 }
647 view_execute_criteria(view); 709 view_execute_criteria(view);
648} 710}
649 711
712static void handle_set_startup_id(struct wl_listener *listener, void *data) {
713 struct sway_xwayland_view *xwayland_view =
714 wl_container_of(listener, xwayland_view, set_startup_id);
715 struct sway_view *view = &xwayland_view->view;
716 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
717 if (xsurface->startup_id == NULL) {
718 return;
719 }
720
721 struct wlr_xdg_activation_token_v1 *token =
722 wlr_xdg_activation_v1_find_token(
723 server.xdg_activation_v1, xsurface->startup_id);
724 if (token == NULL) {
725 // Tried to activate with an unknown or expired token
726 return;
727 }
728
729 struct launcher_ctx *ctx = token->data;
730 if (token->data == NULL) {
731 // TODO: support external launchers in X
732 return;
733 }
734 view_assign_ctx(view, ctx);
735}
736
650static void handle_set_window_type(struct wl_listener *listener, void *data) { 737static void handle_set_window_type(struct wl_listener *listener, void *data) {
651 struct sway_xwayland_view *xwayland_view = 738 struct sway_xwayland_view *xwayland_view =
652 wl_container_of(listener, xwayland_view, set_window_type); 739 wl_container_of(listener, xwayland_view, set_window_type);
653 struct sway_view *view = &xwayland_view->view; 740 struct sway_view *view = &xwayland_view->view;
654 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 741 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
655 if (!xsurface->mapped) { 742 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
656 return; 743 return;
657 } 744 }
658 view_execute_criteria(view); 745 view_execute_criteria(view);
@@ -663,20 +750,39 @@ static void handle_set_hints(struct wl_listener *listener, void *data) {
663 wl_container_of(listener, xwayland_view, set_hints); 750 wl_container_of(listener, xwayland_view, set_hints);
664 struct sway_view *view = &xwayland_view->view; 751 struct sway_view *view = &xwayland_view->view;
665 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 752 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
666 if (!xsurface->mapped) { 753 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
667 return; 754 return;
668 } 755 }
669 if (!xsurface->hints_urgency && view->urgent_timer) { 756 const bool hints_urgency = xcb_icccm_wm_hints_get_urgency(xsurface->hints);
757 if (!hints_urgency && view->urgent_timer) {
670 // The view is in the timeout period. We'll ignore the request to 758 // The view is in the timeout period. We'll ignore the request to
671 // unset urgency so that the view remains urgent until the timer clears 759 // unset urgency so that the view remains urgent until the timer clears
672 // it. 760 // it.
673 return; 761 return;
674 } 762 }
675 if (view->allow_request_urgent) { 763 if (view->allow_request_urgent) {
676 view_set_urgent(view, (bool)xsurface->hints_urgency); 764 view_set_urgent(view, hints_urgency);
677 } 765 }
678} 766}
679 767
768static void handle_associate(struct wl_listener *listener, void *data) {
769 struct sway_xwayland_view *xwayland_view =
770 wl_container_of(listener, xwayland_view, associate);
771 struct wlr_xwayland_surface *xsurface =
772 xwayland_view->view.wlr_xwayland_surface;
773 wl_signal_add(&xsurface->surface->events.unmap, &xwayland_view->unmap);
774 xwayland_view->unmap.notify = handle_unmap;
775 wl_signal_add(&xsurface->surface->events.map, &xwayland_view->map);
776 xwayland_view->map.notify = handle_map;
777}
778
779static void handle_dissociate(struct wl_listener *listener, void *data) {
780 struct sway_xwayland_view *xwayland_view =
781 wl_container_of(listener, xwayland_view, dissociate);
782 wl_list_remove(&xwayland_view->map.link);
783 wl_list_remove(&xwayland_view->unmap.link);
784}
785
680struct sway_view *view_from_wlr_xwayland_surface( 786struct sway_view *view_from_wlr_xwayland_surface(
681 struct wlr_xwayland_surface *xsurface) { 787 struct wlr_xwayland_surface *xsurface) {
682 return xsurface->data; 788 return xsurface->data;
@@ -692,7 +798,10 @@ struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsu
692 return NULL; 798 return NULL;
693 } 799 }
694 800
695 view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl); 801 if (!view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl)) {
802 free(xwayland_view);
803 return NULL;
804 }
696 xwayland_view->view.wlr_xwayland_surface = xsurface; 805 xwayland_view->view.wlr_xwayland_surface = xsurface;
697 806
698 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy); 807 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy);
@@ -731,6 +840,10 @@ struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsu
731 wl_signal_add(&xsurface->events.set_role, &xwayland_view->set_role); 840 wl_signal_add(&xsurface->events.set_role, &xwayland_view->set_role);
732 xwayland_view->set_role.notify = handle_set_role; 841 xwayland_view->set_role.notify = handle_set_role;
733 842
843 wl_signal_add(&xsurface->events.set_startup_id,
844 &xwayland_view->set_startup_id);
845 xwayland_view->set_startup_id.notify = handle_set_startup_id;
846
734 wl_signal_add(&xsurface->events.set_window_type, 847 wl_signal_add(&xsurface->events.set_window_type,
735 &xwayland_view->set_window_type); 848 &xwayland_view->set_window_type);
736 xwayland_view->set_window_type.notify = handle_set_window_type; 849 xwayland_view->set_window_type.notify = handle_set_window_type;
@@ -742,11 +855,11 @@ struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsu
742 &xwayland_view->set_decorations); 855 &xwayland_view->set_decorations);
743 xwayland_view->set_decorations.notify = handle_set_decorations; 856 xwayland_view->set_decorations.notify = handle_set_decorations;
744 857
745 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); 858 wl_signal_add(&xsurface->events.associate, &xwayland_view->associate);
746 xwayland_view->unmap.notify = handle_unmap; 859 xwayland_view->associate.notify = handle_associate;
747 860
748 wl_signal_add(&xsurface->events.map, &xwayland_view->map); 861 wl_signal_add(&xsurface->events.dissociate, &xwayland_view->dissociate);
749 xwayland_view->map.notify = handle_map; 862 xwayland_view->dissociate.notify = handle_dissociate;
750 863
751 wl_signal_add(&xsurface->events.set_override_redirect, 864 wl_signal_add(&xsurface->events.set_override_redirect,
752 &xwayland_view->override_redirect); 865 &xwayland_view->override_redirect);
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index fa604426..404c1eed 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -4,10 +4,10 @@
4#include <libevdev/libevdev.h> 4#include <libevdev/libevdev.h>
5#include <linux/input-event-codes.h> 5#include <linux/input-event-codes.h>
6#include <errno.h> 6#include <errno.h>
7#include <time.h>
7#include <strings.h> 8#include <strings.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_cursor.h> 9#include <wlr/types/wlr_cursor.h>
10#include <wlr/types/wlr_idle.h> 10#include <wlr/types/wlr_cursor_shape_v1.h>
11#include <wlr/types/wlr_pointer.h> 11#include <wlr/types/wlr_pointer.h>
12#include <wlr/types/wlr_touch.h> 12#include <wlr/types/wlr_touch.h>
13#include <wlr/types/wlr_tablet_v2.h> 13#include <wlr/types/wlr_tablet_v2.h>
@@ -19,56 +19,22 @@
19#include "log.h" 19#include "log.h"
20#include "util.h" 20#include "util.h"
21#include "sway/commands.h" 21#include "sway/commands.h"
22#include "sway/desktop.h"
23#include "sway/desktop/transaction.h"
24#include "sway/input/cursor.h" 22#include "sway/input/cursor.h"
25#include "sway/input/keyboard.h" 23#include "sway/input/keyboard.h"
26#include "sway/input/tablet.h" 24#include "sway/input/tablet.h"
27#include "sway/layers.h" 25#include "sway/layers.h"
28#include "sway/output.h" 26#include "sway/output.h"
27#include "sway/scene_descriptor.h"
29#include "sway/tree/container.h" 28#include "sway/tree/container.h"
30#include "sway/tree/root.h" 29#include "sway/tree/root.h"
31#include "sway/tree/view.h" 30#include "sway/tree/view.h"
32#include "sway/tree/workspace.h" 31#include "sway/tree/workspace.h"
33#include "wlr-layer-shell-unstable-v1-protocol.h" 32#include "wlr-layer-shell-unstable-v1-protocol.h"
34 33
35static struct wlr_surface *layer_surface_at(struct sway_output *output, 34static uint32_t get_current_time_msec(void) {
36 struct wl_list *layer, double ox, double oy, double *sx, double *sy) { 35 struct timespec now;
37 struct sway_layer_surface *sway_layer; 36 clock_gettime(CLOCK_MONOTONIC, &now);
38 wl_list_for_each_reverse(sway_layer, layer, link) { 37 return now.tv_sec * 1000 + now.tv_nsec / 1000000;
39 double _sx = ox - sway_layer->geo.x;
40 double _sy = oy - sway_layer->geo.y;
41 struct wlr_surface *sub = wlr_layer_surface_v1_surface_at(
42 sway_layer->layer_surface, _sx, _sy, sx, sy);
43 if (sub) {
44 return sub;
45 }
46 }
47 return NULL;
48}
49
50static bool surface_is_xdg_popup(struct wlr_surface *surface) {
51 if (wlr_surface_is_xdg_surface(surface)) {
52 struct wlr_xdg_surface *xdg_surface =
53 wlr_xdg_surface_from_wlr_surface(surface);
54 return xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP;
55 }
56 return false;
57}
58
59static struct wlr_surface *layer_surface_popup_at(struct sway_output *output,
60 struct wl_list *layer, double ox, double oy, double *sx, double *sy) {
61 struct sway_layer_surface *sway_layer;
62 wl_list_for_each_reverse(sway_layer, layer, link) {
63 double _sx = ox - sway_layer->geo.x;
64 double _sy = oy - sway_layer->geo.y;
65 struct wlr_surface *sub = wlr_layer_surface_v1_surface_at(
66 sway_layer->layer_surface, _sx, _sy, sx, sy);
67 if (sub && surface_is_xdg_popup(sub)) {
68 return sub;
69 }
70 }
71 return NULL;
72} 38}
73 39
74/** 40/**
@@ -78,116 +44,101 @@ static struct wlr_surface *layer_surface_popup_at(struct sway_output *output,
78struct sway_node *node_at_coords( 44struct sway_node *node_at_coords(
79 struct sway_seat *seat, double lx, double ly, 45 struct sway_seat *seat, double lx, double ly,
80 struct wlr_surface **surface, double *sx, double *sy) { 46 struct wlr_surface **surface, double *sx, double *sy) {
81 // check for unmanaged views first 47 struct wlr_scene_node *scene_node = NULL;
82#if HAVE_XWAYLAND 48
83 struct wl_list *unmanaged = &root->xwayland_unmanaged; 49 struct wlr_scene_node *node;
84 struct sway_xwayland_unmanaged *unmanaged_surface; 50 wl_list_for_each_reverse(node, &root->layer_tree->children, link) {
85 wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) { 51 struct wlr_scene_tree *layer = wlr_scene_tree_from_node(node);
86 struct wlr_xwayland_surface *xsurface = 52
87 unmanaged_surface->wlr_xwayland_surface; 53 bool non_interactive = scene_descriptor_try_get(&layer->node,
88 54 SWAY_SCENE_DESC_NON_INTERACTIVE);
89 double _sx = lx - unmanaged_surface->lx; 55 if (non_interactive) {
90 double _sy = ly - unmanaged_surface->ly; 56 continue;
91 if (wlr_surface_point_accepts_input(xsurface->surface, _sx, _sy)) { 57 }
92 *surface = xsurface->surface; 58
93 *sx = _sx; 59 scene_node = wlr_scene_node_at(&layer->node, lx, ly, sx, sy);
94 *sy = _sy; 60 if (scene_node) {
95 return NULL; 61 break;
96 } 62 }
97 } 63 }
64
65 if (scene_node) {
66 // determine what wlr_surface we clicked on
67 if (scene_node->type == WLR_SCENE_NODE_BUFFER) {
68 struct wlr_scene_buffer *scene_buffer =
69 wlr_scene_buffer_from_node(scene_node);
70 struct wlr_scene_surface *scene_surface =
71 wlr_scene_surface_try_from_buffer(scene_buffer);
72
73 if (scene_surface) {
74 *surface = scene_surface->surface;
75 }
76 }
77
78 // determine what container we clicked on
79 struct wlr_scene_node *current = scene_node;
80 while (true) {
81 struct sway_container *con = scene_descriptor_try_get(current,
82 SWAY_SCENE_DESC_CONTAINER);
83
84 if (!con) {
85 struct sway_view *view = scene_descriptor_try_get(current,
86 SWAY_SCENE_DESC_VIEW);
87 if (view) {
88 con = view->container;
89 }
90 }
91
92 if (!con) {
93 struct sway_xdg_popup *popup =
94 scene_descriptor_try_get(current, SWAY_SCENE_DESC_POPUP);
95 if (popup) {
96 con = popup->view->container;
97 }
98 }
99
100 if (con && (!con->view || con->view->surface)) {
101 return &con->node;
102 }
103
104 if (scene_descriptor_try_get(current, SWAY_SCENE_DESC_LAYER_SHELL)) {
105 // We don't want to feed through the current workspace on
106 // layer shells
107 return NULL;
108 }
109
110#if HAVE_XWAYLAND
111 if (scene_descriptor_try_get(current, SWAY_SCENE_DESC_XWAYLAND_UNMANAGED)) {
112 return NULL;
113 }
98#endif 114#endif
99 // find the output the cursor is on 115
116 if (!current->parent) {
117 break;
118 }
119
120 current = &current->parent->node;
121 }
122 }
123
124 // if we aren't on a container, determine what workspace we are on
100 struct wlr_output *wlr_output = wlr_output_layout_output_at( 125 struct wlr_output *wlr_output = wlr_output_layout_output_at(
101 root->output_layout, lx, ly); 126 root->output_layout, lx, ly);
102 if (wlr_output == NULL) { 127 if (wlr_output == NULL) {
103 return NULL; 128 return NULL;
104 } 129 }
130
105 struct sway_output *output = wlr_output->data; 131 struct sway_output *output = wlr_output->data;
106 if (!output || !output->enabled) { 132 if (!output || !output->enabled) {
107 // output is being destroyed or is being enabled 133 // output is being destroyed or is being enabled
108 return NULL; 134 return NULL;
109 } 135 }
110 double ox = lx, oy = ly;
111 wlr_output_layout_output_coords(root->output_layout, wlr_output, &ox, &oy);
112 136
113 if (root->fullscreen_global) {
114 // Try fullscreen container
115 struct sway_container *con = tiling_container_at(
116 &root->fullscreen_global->node, lx, ly, surface, sx, sy);
117 if (con) {
118 return &con->node;
119 }
120 return NULL;
121 }
122
123 // find the focused workspace on the output for this seat
124 struct sway_workspace *ws = output_get_active_workspace(output); 137 struct sway_workspace *ws = output_get_active_workspace(output);
125 if (!ws) { 138 if (!ws) {
126 return NULL; 139 return NULL;
127 } 140 }
128 141
129 if ((*surface = layer_surface_at(output,
130 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY],
131 ox, oy, sx, sy))) {
132 return NULL;
133 }
134 if (ws->fullscreen) {
135 // Try transient containers
136 for (int i = 0; i < ws->floating->length; ++i) {
137 struct sway_container *floater = ws->floating->items[i];
138 if (container_is_transient_for(floater, ws->fullscreen)) {
139 struct sway_container *con = tiling_container_at(
140 &floater->node, lx, ly, surface, sx, sy);
141 if (con) {
142 return &con->node;
143 }
144 }
145 }
146 // Try fullscreen container
147 struct sway_container *con =
148 tiling_container_at(&ws->fullscreen->node, lx, ly, surface, sx, sy);
149 if (con) {
150 return &con->node;
151 }
152 return NULL;
153 }
154 if ((*surface = layer_surface_popup_at(output,
155 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
156 ox, oy, sx, sy))) {
157 return NULL;
158 }
159 if ((*surface = layer_surface_popup_at(output,
160 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
161 ox, oy, sx, sy))) {
162 return NULL;
163 }
164 if ((*surface = layer_surface_popup_at(output,
165 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
166 ox, oy, sx, sy))) {
167 return NULL;
168 }
169 if ((*surface = layer_surface_at(output,
170 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
171 ox, oy, sx, sy))) {
172 return NULL;
173 }
174
175 struct sway_container *c;
176 if ((c = container_at(ws, lx, ly, surface, sx, sy))) {
177 return &c->node;
178 }
179
180 if ((*surface = layer_surface_at(output,
181 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM],
182 ox, oy, sx, sy))) {
183 return NULL;
184 }
185 if ((*surface = layer_surface_at(output,
186 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND],
187 ox, oy, sx, sy))) {
188 return NULL;
189 }
190
191 return &ws->node; 142 return &ws->node;
192} 143}
193 144
@@ -213,7 +164,7 @@ void cursor_update_image(struct sway_cursor *cursor,
213 // Try a node's resize edge 164 // Try a node's resize edge
214 enum wlr_edges edge = find_resize_edge(node->sway_container, NULL, cursor); 165 enum wlr_edges edge = find_resize_edge(node->sway_container, NULL, cursor);
215 if (edge == WLR_EDGE_NONE) { 166 if (edge == WLR_EDGE_NONE) {
216 cursor_set_image(cursor, "left_ptr", NULL); 167 cursor_set_image(cursor, "default", NULL);
217 } else if (container_is_floating(node->sway_container)) { 168 } else if (container_is_floating(node->sway_container)) {
218 cursor_set_image(cursor, wlr_xcursor_get_resize_name(edge), NULL); 169 cursor_set_image(cursor, wlr_xcursor_get_resize_name(edge), NULL);
219 } else { 170 } else {
@@ -224,12 +175,12 @@ void cursor_update_image(struct sway_cursor *cursor,
224 } 175 }
225 } 176 }
226 } else { 177 } else {
227 cursor_set_image(cursor, "left_ptr", NULL); 178 cursor_set_image(cursor, "default", NULL);
228 } 179 }
229} 180}
230 181
231static void cursor_hide(struct sway_cursor *cursor) { 182static void cursor_hide(struct sway_cursor *cursor) {
232 wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); 183 wlr_cursor_unset_image(cursor->cursor);
233 cursor->hidden = true; 184 cursor->hidden = true;
234 wlr_seat_pointer_notify_clear_focus(cursor->seat->wlr_seat); 185 wlr_seat_pointer_notify_clear_focus(cursor->seat->wlr_seat);
235} 186}
@@ -341,7 +292,7 @@ void cursor_unhide(struct sway_cursor *cursor) {
341 wl_event_source_timer_update(cursor->hide_source, cursor_get_timeout(cursor)); 292 wl_event_source_timer_update(cursor->hide_source, cursor_get_timeout(cursor));
342} 293}
343 294
344static void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, 295void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
345 struct wlr_input_device *device, double dx, double dy, 296 struct wlr_input_device *device, double dx, double dy,
346 double dx_unaccel, double dy_unaccel) { 297 double dx_unaccel, double dy_unaccel) {
347 wlr_relative_pointer_manager_v1_send_relative_motion( 298 wlr_relative_pointer_manager_v1_send_relative_motion(
@@ -378,30 +329,29 @@ static void pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
378static void handle_pointer_motion_relative( 329static void handle_pointer_motion_relative(
379 struct wl_listener *listener, void *data) { 330 struct wl_listener *listener, void *data) {
380 struct sway_cursor *cursor = wl_container_of(listener, cursor, motion); 331 struct sway_cursor *cursor = wl_container_of(listener, cursor, motion);
381 struct wlr_event_pointer_motion *e = data; 332 struct wlr_pointer_motion_event *e = data;
382 cursor_handle_activity_from_device(cursor, e->device); 333 cursor_handle_activity_from_device(cursor, &e->pointer->base);
383 334
384 pointer_motion(cursor, e->time_msec, e->device, e->delta_x, e->delta_y, 335 pointer_motion(cursor, e->time_msec, &e->pointer->base, e->delta_x,
385 e->unaccel_dx, e->unaccel_dy); 336 e->delta_y, e->unaccel_dx, e->unaccel_dy);
386 transaction_commit_dirty();
387} 337}
388 338
389static void handle_pointer_motion_absolute( 339static void handle_pointer_motion_absolute(
390 struct wl_listener *listener, void *data) { 340 struct wl_listener *listener, void *data) {
391 struct sway_cursor *cursor = 341 struct sway_cursor *cursor =
392 wl_container_of(listener, cursor, motion_absolute); 342 wl_container_of(listener, cursor, motion_absolute);
393 struct wlr_event_pointer_motion_absolute *event = data; 343 struct wlr_pointer_motion_absolute_event *event = data;
394 cursor_handle_activity_from_device(cursor, event->device); 344 cursor_handle_activity_from_device(cursor, &event->pointer->base);
395 345
396 double lx, ly; 346 double lx, ly;
397 wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, 347 wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->pointer->base,
398 event->x, event->y, &lx, &ly); 348 event->x, event->y, &lx, &ly);
399 349
400 double dx = lx - cursor->cursor->x; 350 double dx = lx - cursor->cursor->x;
401 double dy = ly - cursor->cursor->y; 351 double dy = ly - cursor->cursor->y;
402 352
403 pointer_motion(cursor, event->time_msec, event->device, dx, dy, dx, dy); 353 pointer_motion(cursor, event->time_msec, &event->pointer->base, dx, dy,
404 transaction_commit_dirty(); 354 dx, dy);
405} 355}
406 356
407void dispatch_cursor_button(struct sway_cursor *cursor, 357void dispatch_cursor_button(struct sway_cursor *cursor,
@@ -416,7 +366,7 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
416 366
417static void handle_pointer_button(struct wl_listener *listener, void *data) { 367static void handle_pointer_button(struct wl_listener *listener, void *data) {
418 struct sway_cursor *cursor = wl_container_of(listener, cursor, button); 368 struct sway_cursor *cursor = wl_container_of(listener, cursor, button);
419 struct wlr_event_pointer_button *event = data; 369 struct wlr_pointer_button_event *event = data;
420 370
421 if (event->state == WLR_BUTTON_PRESSED) { 371 if (event->state == WLR_BUTTON_PRESSED) {
422 cursor->pressed_button_count++; 372 cursor->pressed_button_count++;
@@ -428,23 +378,21 @@ static void handle_pointer_button(struct wl_listener *listener, void *data) {
428 } 378 }
429 } 379 }
430 380
431 cursor_handle_activity_from_device(cursor, event->device); 381 cursor_handle_activity_from_device(cursor, &event->pointer->base);
432 dispatch_cursor_button(cursor, event->device, 382 dispatch_cursor_button(cursor, &event->pointer->base,
433 event->time_msec, event->button, event->state); 383 event->time_msec, event->button, event->state);
434 transaction_commit_dirty();
435} 384}
436 385
437void dispatch_cursor_axis(struct sway_cursor *cursor, 386void dispatch_cursor_axis(struct sway_cursor *cursor,
438 struct wlr_event_pointer_axis *event) { 387 struct wlr_pointer_axis_event *event) {
439 seatop_pointer_axis(cursor->seat, event); 388 seatop_pointer_axis(cursor->seat, event);
440} 389}
441 390
442static void handle_pointer_axis(struct wl_listener *listener, void *data) { 391static void handle_pointer_axis(struct wl_listener *listener, void *data) {
443 struct sway_cursor *cursor = wl_container_of(listener, cursor, axis); 392 struct sway_cursor *cursor = wl_container_of(listener, cursor, axis);
444 struct wlr_event_pointer_axis *event = data; 393 struct wlr_pointer_axis_event *event = data;
445 cursor_handle_activity_from_device(cursor, event->device); 394 cursor_handle_activity_from_device(cursor, &event->pointer->base);
446 dispatch_cursor_axis(cursor, event); 395 dispatch_cursor_axis(cursor, event);
447 transaction_commit_dirty();
448} 396}
449 397
450static void handle_pointer_frame(struct wl_listener *listener, void *data) { 398static void handle_pointer_frame(struct wl_listener *listener, void *data) {
@@ -454,97 +402,76 @@ static void handle_pointer_frame(struct wl_listener *listener, void *data) {
454 402
455static void handle_touch_down(struct wl_listener *listener, void *data) { 403static void handle_touch_down(struct wl_listener *listener, void *data) {
456 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_down); 404 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_down);
457 struct wlr_event_touch_down *event = data; 405 struct wlr_touch_down_event *event = data;
458 cursor_handle_activity_from_device(cursor, event->device); 406 cursor_handle_activity_from_device(cursor, &event->touch->base);
459 cursor_hide(cursor); 407 cursor_hide(cursor);
460 408
461 struct sway_seat *seat = cursor->seat; 409 struct sway_seat *seat = cursor->seat;
462 struct wlr_seat *wlr_seat = seat->wlr_seat;
463 struct wlr_surface *surface = NULL;
464 410
465 double lx, ly; 411 double lx, ly;
466 wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, 412 wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->touch->base,
467 event->x, event->y, &lx, &ly); 413 event->x, event->y, &lx, &ly);
468 double sx, sy;
469 struct sway_node *focused_node = node_at_coords(seat, lx, ly, &surface, &sx, &sy);
470 414
471 seat->touch_id = event->touch_id; 415 seat->touch_id = event->touch_id;
472 seat->touch_x = lx; 416 seat->touch_x = lx;
473 seat->touch_y = ly; 417 seat->touch_y = ly;
474 418
475 if (surface && wlr_surface_accepts_touch(wlr_seat, surface)) { 419 seatop_touch_down(seat, event, lx, ly);
476 if (seat_is_input_allowed(seat, surface)) { 420}
477 wlr_seat_touch_notify_down(wlr_seat, surface, event->time_msec,
478 event->touch_id, sx, sy);
479 421
480 if (focused_node) { 422static void handle_touch_up(struct wl_listener *listener, void *data) {
481 seat_set_focus(seat, focused_node); 423 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_up);
482 } 424 struct wlr_touch_up_event *event = data;
425 cursor_handle_activity_from_device(cursor, &event->touch->base);
426
427 struct sway_seat *seat = cursor->seat;
428
429 if (cursor->simulating_pointer_from_touch) {
430 if (cursor->pointer_touch_id == cursor->seat->touch_id) {
431 cursor->pointer_touch_up = true;
432 dispatch_cursor_button(cursor, &event->touch->base,
433 event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED);
483 } 434 }
484 } else if (!cursor->simulating_pointer_from_touch && 435 } else {
485 (!surface || seat_is_input_allowed(seat, surface))) { 436 seatop_touch_up(seat, event);
486 // Fallback to cursor simulation.
487 // The pointer_touch_id state is needed, so drags are not aborted when over
488 // a surface supporting touch and multi touch events don't interfere.
489 cursor->simulating_pointer_from_touch = true;
490 cursor->pointer_touch_id = seat->touch_id;
491 double dx, dy;
492 dx = lx - cursor->cursor->x;
493 dy = ly - cursor->cursor->y;
494 pointer_motion(cursor, event->time_msec, event->device, dx, dy, dx, dy);
495 dispatch_cursor_button(cursor, event->device, event->time_msec,
496 BTN_LEFT, WLR_BUTTON_PRESSED);
497 wlr_seat_pointer_notify_frame(wlr_seat);
498 transaction_commit_dirty();
499 } 437 }
500} 438}
501 439
502static void handle_touch_up(struct wl_listener *listener, void *data) { 440static void handle_touch_cancel(struct wl_listener *listener, void *data) {
503 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_up); 441 struct sway_cursor *cursor = wl_container_of(listener, cursor, touch_cancel);
504 struct wlr_event_touch_up *event = data; 442 struct wlr_touch_cancel_event *event = data;
505 cursor_handle_activity_from_device(cursor, event->device); 443 cursor_handle_activity_from_device(cursor, &event->touch->base);
506 444
507 struct wlr_seat *wlr_seat = cursor->seat->wlr_seat; 445 struct sway_seat *seat = cursor->seat;
508 446
509 if (cursor->simulating_pointer_from_touch) { 447 if (cursor->simulating_pointer_from_touch) {
510 if (cursor->pointer_touch_id == cursor->seat->touch_id) { 448 if (cursor->pointer_touch_id == cursor->seat->touch_id) {
511 cursor->simulating_pointer_from_touch = false; 449 cursor->pointer_touch_up = true;
512 dispatch_cursor_button(cursor, event->device, event->time_msec, 450 dispatch_cursor_button(cursor, &event->touch->base,
513 BTN_LEFT, WLR_BUTTON_RELEASED); 451 event->time_msec, BTN_LEFT, WLR_BUTTON_RELEASED);
514 wlr_seat_pointer_notify_frame(wlr_seat);
515 transaction_commit_dirty();
516 } 452 }
517 } else { 453 } else {
518 wlr_seat_touch_notify_up(wlr_seat, event->time_msec, event->touch_id); 454 seatop_touch_cancel(seat, event);
519 } 455 }
520} 456}
521 457
522static void handle_touch_motion(struct wl_listener *listener, void *data) { 458static void handle_touch_motion(struct wl_listener *listener, void *data) {
523 struct sway_cursor *cursor = 459 struct sway_cursor *cursor =
524 wl_container_of(listener, cursor, touch_motion); 460 wl_container_of(listener, cursor, touch_motion);
525 struct wlr_event_touch_motion *event = data; 461 struct wlr_touch_motion_event *event = data;
526 cursor_handle_activity_from_device(cursor, event->device); 462 cursor_handle_activity_from_device(cursor, &event->touch->base);
527 463
528 struct sway_seat *seat = cursor->seat; 464 struct sway_seat *seat = cursor->seat;
529 struct wlr_seat *wlr_seat = seat->wlr_seat;
530 struct wlr_surface *surface = NULL;
531 465
532 double lx, ly; 466 double lx, ly;
533 wlr_cursor_absolute_to_layout_coords(cursor->cursor, event->device, 467 wlr_cursor_absolute_to_layout_coords(cursor->cursor, &event->touch->base,
534 event->x, event->y, &lx, &ly); 468 event->x, event->y, &lx, &ly);
535 double sx, sy;
536 node_at_coords(cursor->seat, lx, ly, &surface, &sx, &sy);
537 469
538 if (seat->touch_id == event->touch_id) { 470 if (seat->touch_id == event->touch_id) {
539 seat->touch_x = lx; 471 seat->touch_x = lx;
540 seat->touch_y = ly; 472 seat->touch_y = ly;
541 473
542 struct sway_drag_icon *drag_icon; 474 drag_icons_update_position(seat);
543 wl_list_for_each(drag_icon, &root->drag_icons, link) {
544 if (drag_icon->seat == seat) {
545 drag_icon_update_position(drag_icon);
546 }
547 }
548 } 475 }
549 476
550 if (cursor->simulating_pointer_from_touch) { 477 if (cursor->simulating_pointer_from_touch) {
@@ -552,12 +479,29 @@ static void handle_touch_motion(struct wl_listener *listener, void *data) {
552 double dx, dy; 479 double dx, dy;
553 dx = lx - cursor->cursor->x; 480 dx = lx - cursor->cursor->x;
554 dy = ly - cursor->cursor->y; 481 dy = ly - cursor->cursor->y;
555 pointer_motion(cursor, event->time_msec, event->device, dx, dy, dx, dy); 482 pointer_motion(cursor, event->time_msec, &event->touch->base,
556 transaction_commit_dirty(); 483 dx, dy, dx, dy);
484 }
485 } else {
486 seatop_touch_motion(seat, event, lx, ly);
487 }
488}
489
490static void handle_touch_frame(struct wl_listener *listener, void *data) {
491 struct sway_cursor *cursor =
492 wl_container_of(listener, cursor, touch_frame);
493
494 struct wlr_seat *wlr_seat = cursor->seat->wlr_seat;
495
496 if (cursor->simulating_pointer_from_touch) {
497 wlr_seat_pointer_notify_frame(wlr_seat);
498
499 if (cursor->pointer_touch_up) {
500 cursor->pointer_touch_up = false;
501 cursor->simulating_pointer_from_touch = false;
557 } 502 }
558 } else if (surface) { 503 } else {
559 wlr_seat_touch_notify_motion(wlr_seat, event->time_msec, 504 wlr_seat_touch_notify_frame(wlr_seat);
560 event->touch_id, sx, sy);
561 } 505 }
562} 506}
563 507
@@ -574,14 +518,15 @@ static void apply_mapping_from_region(struct wlr_input_device *device,
574 double x1 = region->x1, x2 = region->x2; 518 double x1 = region->x1, x2 = region->x2;
575 double y1 = region->y1, y2 = region->y2; 519 double y1 = region->y1, y2 = region->y2;
576 520
577 if (region->mm) { 521 if (region->mm && device->type == WLR_INPUT_DEVICE_TABLET_TOOL) {
578 if (device->width_mm == 0 || device->height_mm == 0) { 522 struct wlr_tablet *tablet = wlr_tablet_from_input_device(device);
523 if (tablet->width_mm == 0 || tablet->height_mm == 0) {
579 return; 524 return;
580 } 525 }
581 x1 /= device->width_mm; 526 x1 /= tablet->width_mm;
582 x2 /= device->width_mm; 527 x2 /= tablet->width_mm;
583 y1 /= device->height_mm; 528 y1 /= tablet->height_mm;
584 y2 /= device->height_mm; 529 y2 /= tablet->height_mm;
585 } 530 }
586 531
587 *x = apply_mapping_from_coord(x1, x2, *x); 532 *x = apply_mapping_from_coord(x1, x2, *x);
@@ -639,14 +584,12 @@ static void handle_tablet_tool_position(struct sway_cursor *cursor,
639 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); 584 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool);
640 pointer_motion(cursor, time_msec, input_device->wlr_device, dx, dy, dx, dy); 585 pointer_motion(cursor, time_msec, input_device->wlr_device, dx, dy, dx, dy);
641 } 586 }
642
643 transaction_commit_dirty();
644} 587}
645 588
646static void handle_tool_axis(struct wl_listener *listener, void *data) { 589static void handle_tool_axis(struct wl_listener *listener, void *data) {
647 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis); 590 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_axis);
648 struct wlr_event_tablet_tool_axis *event = data; 591 struct wlr_tablet_tool_axis_event *event = data;
649 cursor_handle_activity_from_device(cursor, event->device); 592 cursor_handle_activity_from_device(cursor, &event->tablet->base);
650 593
651 struct sway_tablet_tool *sway_tool = event->tool->data; 594 struct sway_tablet_tool *sway_tool = event->tool->data;
652 if (!sway_tool) { 595 if (!sway_tool) {
@@ -701,8 +644,8 @@ static void handle_tool_axis(struct wl_listener *listener, void *data) {
701 644
702static void handle_tool_tip(struct wl_listener *listener, void *data) { 645static void handle_tool_tip(struct wl_listener *listener, void *data) {
703 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip); 646 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_tip);
704 struct wlr_event_tablet_tool_tip *event = data; 647 struct wlr_tablet_tool_tip_event *event = data;
705 cursor_handle_activity_from_device(cursor, event->device); 648 cursor_handle_activity_from_device(cursor, &event->tablet->base);
706 649
707 struct sway_tablet_tool *sway_tool = event->tool->data; 650 struct sway_tablet_tool *sway_tool = event->tool->data;
708 struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2; 651 struct wlr_tablet_v2_tablet *tablet_v2 = sway_tool->tablet->tablet_v2;
@@ -717,10 +660,9 @@ static void handle_tool_tip(struct wl_listener *listener, void *data) {
717 if (cursor->simulating_pointer_from_tool_tip && 660 if (cursor->simulating_pointer_from_tool_tip &&
718 event->state == WLR_TABLET_TOOL_TIP_UP) { 661 event->state == WLR_TABLET_TOOL_TIP_UP) {
719 cursor->simulating_pointer_from_tool_tip = false; 662 cursor->simulating_pointer_from_tool_tip = false;
720 dispatch_cursor_button(cursor, event->device, event->time_msec, 663 dispatch_cursor_button(cursor, &event->tablet->base, event->time_msec,
721 BTN_LEFT, WLR_BUTTON_RELEASED); 664 BTN_LEFT, WLR_BUTTON_RELEASED);
722 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 665 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
723 transaction_commit_dirty();
724 } else if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) { 666 } else if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) {
725 // If we started holding the tool tip down on a surface that accepts 667 // If we started holding the tool tip down on a surface that accepts
726 // tablet v2, we should notify that surface if it gets released over a 668 // tablet v2, we should notify that surface if it gets released over a
@@ -730,10 +672,9 @@ static void handle_tool_tip(struct wl_listener *listener, void *data) {
730 WLR_TABLET_TOOL_TIP_UP); 672 WLR_TABLET_TOOL_TIP_UP);
731 } else { 673 } else {
732 cursor->simulating_pointer_from_tool_tip = true; 674 cursor->simulating_pointer_from_tool_tip = true;
733 dispatch_cursor_button(cursor, event->device, event->time_msec, 675 dispatch_cursor_button(cursor, &event->tablet->base,
734 BTN_LEFT, WLR_BUTTON_PRESSED); 676 event->time_msec, BTN_LEFT, WLR_BUTTON_PRESSED);
735 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 677 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
736 transaction_commit_dirty();
737 } 678 }
738 } else { 679 } else {
739 seatop_tablet_tool_tip(seat, sway_tool, event->time_msec, event->state); 680 seatop_tablet_tool_tip(seat, sway_tool, event->time_msec, event->state);
@@ -754,12 +695,13 @@ static struct sway_tablet *get_tablet_for_device(struct sway_cursor *cursor,
754static void handle_tool_proximity(struct wl_listener *listener, void *data) { 695static void handle_tool_proximity(struct wl_listener *listener, void *data) {
755 struct sway_cursor *cursor = 696 struct sway_cursor *cursor =
756 wl_container_of(listener, cursor, tool_proximity); 697 wl_container_of(listener, cursor, tool_proximity);
757 struct wlr_event_tablet_tool_proximity *event = data; 698 struct wlr_tablet_tool_proximity_event *event = data;
758 cursor_handle_activity_from_device(cursor, event->device); 699 cursor_handle_activity_from_device(cursor, &event->tablet->base);
759 700
760 struct wlr_tablet_tool *tool = event->tool; 701 struct wlr_tablet_tool *tool = event->tool;
761 if (!tool->data) { 702 if (!tool->data) {
762 struct sway_tablet *tablet = get_tablet_for_device(cursor, event->device); 703 struct sway_tablet *tablet = get_tablet_for_device(cursor,
704 &event->tablet->base);
763 if (!tablet) { 705 if (!tablet) {
764 sway_log(SWAY_ERROR, "no tablet for tablet tool"); 706 sway_log(SWAY_ERROR, "no tablet for tablet tool");
765 return; 707 return;
@@ -784,8 +726,8 @@ static void handle_tool_proximity(struct wl_listener *listener, void *data) {
784 726
785static void handle_tool_button(struct wl_listener *listener, void *data) { 727static void handle_tool_button(struct wl_listener *listener, void *data) {
786 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button); 728 struct sway_cursor *cursor = wl_container_of(listener, cursor, tool_button);
787 struct wlr_event_tablet_tool_button *event = data; 729 struct wlr_tablet_tool_button_event *event = data;
788 cursor_handle_activity_from_device(cursor, event->device); 730 cursor_handle_activity_from_device(cursor, &event->tablet->base);
789 731
790 struct sway_tablet_tool *sway_tool = event->tool->data; 732 struct sway_tablet_tool *sway_tool = event->tool->data;
791 if (!sway_tool) { 733 if (!sway_tool) {
@@ -800,32 +742,71 @@ static void handle_tool_button(struct wl_listener *listener, void *data) {
800 node_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y, 742 node_at_coords(cursor->seat, cursor->cursor->x, cursor->cursor->y,
801 &surface, &sx, &sy); 743 &surface, &sx, &sy);
802 744
803 if (!surface || !wlr_surface_accepts_tablet_v2(tablet_v2, surface)) { 745 // TODO: floating resize should support graphics tablet events
746 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(cursor->seat->wlr_seat);
747 uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
748 bool mod_pressed = modifiers & config->floating_mod;
749
750 bool surface_supports_tablet_events =
751 surface && wlr_surface_accepts_tablet_v2(tablet_v2, surface);
752
753 // Simulate pointer when:
754 // 1. The modifier key is pressed, OR
755 // 2. The surface under the cursor does not support tablet events.
756 bool should_simulate_pointer = mod_pressed || !surface_supports_tablet_events;
757
758 // Similar to tool tip, we need to selectively simulate mouse events, but we
759 // want to make sure that it is always consistent. Because all tool buttons
760 // currently map to BTN_RIGHT, we need to keep count of how many tool
761 // buttons are currently pressed down so we can send consistent events.
762 //
763 // The logic follows:
764 // - If we are already simulating the pointer, we should continue to do so
765 // until at least no tool button is held down.
766 // - If we should simulate the pointer and no tool button is currently held
767 // down, begin simulating the pointer.
768 // - If neither of the above are true, send the tablet events.
769 if ((cursor->tool_buttons > 0 && cursor->simulating_pointer_from_tool_button)
770 || (cursor->tool_buttons == 0 && should_simulate_pointer)) {
771 cursor->simulating_pointer_from_tool_button = true;
772
804 // TODO: the user may want to configure which tool buttons are mapped to 773 // TODO: the user may want to configure which tool buttons are mapped to
805 // which simulated pointer buttons 774 // which simulated pointer buttons
806 switch (event->state) { 775 switch (event->state) {
807 case WLR_BUTTON_PRESSED: 776 case WLR_BUTTON_PRESSED:
808 if (cursor->tool_buttons == 0) { 777 if (cursor->tool_buttons == 0) {
809 dispatch_cursor_button(cursor, event->device, 778 dispatch_cursor_button(cursor, &event->tablet->base,
810 event->time_msec, BTN_RIGHT, event->state); 779 event->time_msec, BTN_RIGHT, event->state);
811 } 780 }
812 cursor->tool_buttons++;
813 break; 781 break;
814 case WLR_BUTTON_RELEASED: 782 case WLR_BUTTON_RELEASED:
815 if (cursor->tool_buttons == 1) { 783 if (cursor->tool_buttons <= 1) {
816 dispatch_cursor_button(cursor, event->device, 784 dispatch_cursor_button(cursor, &event->tablet->base,
817 event->time_msec, BTN_RIGHT, event->state); 785 event->time_msec, BTN_RIGHT, event->state);
818 } 786 }
819 cursor->tool_buttons--;
820 break; 787 break;
821 } 788 }
822 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 789 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
823 transaction_commit_dirty(); 790 } else {
824 return; 791 cursor->simulating_pointer_from_tool_button = false;
792
793 wlr_tablet_v2_tablet_tool_notify_button(sway_tool->tablet_v2_tool,
794 event->button, (enum zwp_tablet_pad_v2_button_state)event->state);
825 } 795 }
826 796
827 wlr_tablet_v2_tablet_tool_notify_button(sway_tool->tablet_v2_tool, 797 // Update tool button count.
828 event->button, (enum zwp_tablet_pad_v2_button_state)event->state); 798 switch (event->state) {
799 case WLR_BUTTON_PRESSED:
800 cursor->tool_buttons++;
801 break;
802 case WLR_BUTTON_RELEASED:
803 if (cursor->tool_buttons == 0) {
804 sway_log(SWAY_ERROR, "inconsistent tablet tool button events");
805 } else {
806 cursor->tool_buttons--;
807 }
808 break;
809 }
829} 810}
830 811
831static void check_constraint_region(struct sway_cursor *cursor) { 812static void check_constraint_region(struct sway_cursor *cursor) {
@@ -837,8 +818,8 @@ static void check_constraint_region(struct sway_cursor *cursor) {
837 818
838 struct sway_container *con = view->container; 819 struct sway_container *con = view->container;
839 820
840 double sx = cursor->cursor->x - con->content_x + view->geometry.x; 821 double sx = cursor->cursor->x - con->pending.content_x + view->geometry.x;
841 double sy = cursor->cursor->y - con->content_y + view->geometry.y; 822 double sy = cursor->cursor->y - con->pending.content_y + view->geometry.y;
842 823
843 if (!pixman_region32_contains_point(region, 824 if (!pixman_region32_contains_point(region,
844 floor(sx), floor(sy), NULL)) { 825 floor(sx), floor(sy), NULL)) {
@@ -849,8 +830,8 @@ static void check_constraint_region(struct sway_cursor *cursor) {
849 double sy = (boxes[0].y1 + boxes[0].y2) / 2.; 830 double sy = (boxes[0].y1 + boxes[0].y2) / 2.;
850 831
851 wlr_cursor_warp_closest(cursor->cursor, NULL, 832 wlr_cursor_warp_closest(cursor->cursor, NULL,
852 sx + con->content_x - view->geometry.x, 833 sx + con->pending.content_x - view->geometry.x,
853 sy + con->content_y - view->geometry.y); 834 sy + con->pending.content_y - view->geometry.y);
854 835
855 cursor_rebase(cursor); 836 cursor_rebase(cursor);
856 } 837 }
@@ -911,59 +892,68 @@ static void handle_request_pointer_set_cursor(struct wl_listener *listener,
911 event->hotspot_y, focused_client); 892 event->hotspot_y, focused_client);
912} 893}
913 894
895static void handle_pointer_hold_begin(struct wl_listener *listener, void *data) {
896 struct sway_cursor *cursor = wl_container_of(
897 listener, cursor, hold_begin);
898 struct wlr_pointer_hold_begin_event *event = data;
899 cursor_handle_activity_from_device(cursor, &event->pointer->base);
900 seatop_hold_begin(cursor->seat, event);
901}
902
903static void handle_pointer_hold_end(struct wl_listener *listener, void *data) {
904 struct sway_cursor *cursor = wl_container_of(
905 listener, cursor, hold_end);
906 struct wlr_pointer_hold_end_event *event = data;
907 cursor_handle_activity_from_device(cursor, &event->pointer->base);
908 seatop_hold_end(cursor->seat, event);
909}
910
914static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) { 911static void handle_pointer_pinch_begin(struct wl_listener *listener, void *data) {
915 struct sway_cursor *cursor = wl_container_of( 912 struct sway_cursor *cursor = wl_container_of(
916 listener, cursor, pinch_begin); 913 listener, cursor, pinch_begin);
917 struct wlr_event_pointer_pinch_begin *event = data; 914 struct wlr_pointer_pinch_begin_event *event = data;
918 wlr_pointer_gestures_v1_send_pinch_begin( 915 cursor_handle_activity_from_device(cursor, &event->pointer->base);
919 cursor->pointer_gestures, cursor->seat->wlr_seat, 916 seatop_pinch_begin(cursor->seat, event);
920 event->time_msec, event->fingers);
921} 917}
922 918
923static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) { 919static void handle_pointer_pinch_update(struct wl_listener *listener, void *data) {
924 struct sway_cursor *cursor = wl_container_of( 920 struct sway_cursor *cursor = wl_container_of(
925 listener, cursor, pinch_update); 921 listener, cursor, pinch_update);
926 struct wlr_event_pointer_pinch_update *event = data; 922 struct wlr_pointer_pinch_update_event *event = data;
927 wlr_pointer_gestures_v1_send_pinch_update( 923 cursor_handle_activity_from_device(cursor, &event->pointer->base);
928 cursor->pointer_gestures, cursor->seat->wlr_seat, 924 seatop_pinch_update(cursor->seat, event);
929 event->time_msec, event->dx, event->dy,
930 event->scale, event->rotation);
931} 925}
932 926
933static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) { 927static void handle_pointer_pinch_end(struct wl_listener *listener, void *data) {
934 struct sway_cursor *cursor = wl_container_of( 928 struct sway_cursor *cursor = wl_container_of(
935 listener, cursor, pinch_end); 929 listener, cursor, pinch_end);
936 struct wlr_event_pointer_pinch_end *event = data; 930 struct wlr_pointer_pinch_end_event *event = data;
937 wlr_pointer_gestures_v1_send_pinch_end( 931 cursor_handle_activity_from_device(cursor, &event->pointer->base);
938 cursor->pointer_gestures, cursor->seat->wlr_seat, 932 seatop_pinch_end(cursor->seat, event);
939 event->time_msec, event->cancelled);
940} 933}
941 934
942static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) { 935static void handle_pointer_swipe_begin(struct wl_listener *listener, void *data) {
943 struct sway_cursor *cursor = wl_container_of( 936 struct sway_cursor *cursor = wl_container_of(
944 listener, cursor, swipe_begin); 937 listener, cursor, swipe_begin);
945 struct wlr_event_pointer_swipe_begin *event = data; 938 struct wlr_pointer_swipe_begin_event *event = data;
946 wlr_pointer_gestures_v1_send_swipe_begin( 939 cursor_handle_activity_from_device(cursor, &event->pointer->base);
947 cursor->pointer_gestures, cursor->seat->wlr_seat, 940 seatop_swipe_begin(cursor->seat, event);
948 event->time_msec, event->fingers);
949} 941}
950 942
951static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) { 943static void handle_pointer_swipe_update(struct wl_listener *listener, void *data) {
952 struct sway_cursor *cursor = wl_container_of( 944 struct sway_cursor *cursor = wl_container_of(
953 listener, cursor, swipe_update); 945 listener, cursor, swipe_update);
954 struct wlr_event_pointer_swipe_update *event = data; 946 struct wlr_pointer_swipe_update_event *event = data;
955 wlr_pointer_gestures_v1_send_swipe_update( 947 cursor_handle_activity_from_device(cursor, &event->pointer->base);
956 cursor->pointer_gestures, cursor->seat->wlr_seat, 948 seatop_swipe_update(cursor->seat, event);
957 event->time_msec, event->dx, event->dy);
958} 949}
959 950
960static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) { 951static void handle_pointer_swipe_end(struct wl_listener *listener, void *data) {
961 struct sway_cursor *cursor = wl_container_of( 952 struct sway_cursor *cursor = wl_container_of(
962 listener, cursor, swipe_end); 953 listener, cursor, swipe_end);
963 struct wlr_event_pointer_swipe_end *event = data; 954 struct wlr_pointer_swipe_end_event *event = data;
964 wlr_pointer_gestures_v1_send_swipe_end( 955 cursor_handle_activity_from_device(cursor, &event->pointer->base);
965 cursor->pointer_gestures, cursor->seat->wlr_seat, 956 seatop_swipe_end(cursor->seat, event);
966 event->time_msec, event->cancelled);
967} 957}
968 958
969static void handle_image_surface_destroy(struct wl_listener *listener, 959static void handle_image_surface_destroy(struct wl_listener *listener,
@@ -1002,10 +992,9 @@ void cursor_set_image(struct sway_cursor *cursor, const char *image,
1002 } 992 }
1003 993
1004 if (!image) { 994 if (!image) {
1005 wlr_cursor_set_image(cursor->cursor, NULL, 0, 0, 0, 0, 0, 0); 995 wlr_cursor_unset_image(cursor->cursor);
1006 } else if (!current_image || strcmp(current_image, image) != 0) { 996 } else if (!current_image || strcmp(current_image, image) != 0) {
1007 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, image, 997 wlr_cursor_set_xcursor(cursor->cursor, cursor->xcursor_manager, image);
1008 cursor->cursor);
1009 } 998 }
1010} 999}
1011 1000
@@ -1037,6 +1026,8 @@ void sway_cursor_destroy(struct sway_cursor *cursor) {
1037 wl_event_source_remove(cursor->hide_source); 1026 wl_event_source_remove(cursor->hide_source);
1038 1027
1039 wl_list_remove(&cursor->image_surface_destroy.link); 1028 wl_list_remove(&cursor->image_surface_destroy.link);
1029 wl_list_remove(&cursor->hold_begin.link);
1030 wl_list_remove(&cursor->hold_end.link);
1040 wl_list_remove(&cursor->pinch_begin.link); 1031 wl_list_remove(&cursor->pinch_begin.link);
1041 wl_list_remove(&cursor->pinch_update.link); 1032 wl_list_remove(&cursor->pinch_update.link);
1042 wl_list_remove(&cursor->pinch_end.link); 1033 wl_list_remove(&cursor->pinch_end.link);
@@ -1050,7 +1041,9 @@ void sway_cursor_destroy(struct sway_cursor *cursor) {
1050 wl_list_remove(&cursor->frame.link); 1041 wl_list_remove(&cursor->frame.link);
1051 wl_list_remove(&cursor->touch_down.link); 1042 wl_list_remove(&cursor->touch_down.link);
1052 wl_list_remove(&cursor->touch_up.link); 1043 wl_list_remove(&cursor->touch_up.link);
1044 wl_list_remove(&cursor->touch_cancel.link);
1053 wl_list_remove(&cursor->touch_motion.link); 1045 wl_list_remove(&cursor->touch_motion.link);
1046 wl_list_remove(&cursor->touch_frame.link);
1054 wl_list_remove(&cursor->tool_axis.link); 1047 wl_list_remove(&cursor->tool_axis.link);
1055 wl_list_remove(&cursor->tool_tip.link); 1048 wl_list_remove(&cursor->tool_tip.link);
1056 wl_list_remove(&cursor->tool_button.link); 1049 wl_list_remove(&cursor->tool_button.link);
@@ -1085,19 +1078,24 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
1085 wl_list_init(&cursor->image_surface_destroy.link); 1078 wl_list_init(&cursor->image_surface_destroy.link);
1086 cursor->image_surface_destroy.notify = handle_image_surface_destroy; 1079 cursor->image_surface_destroy.notify = handle_image_surface_destroy;
1087 1080
1088 cursor->pointer_gestures = wlr_pointer_gestures_v1_create(server.wl_display); 1081 wl_signal_add(&wlr_cursor->events.hold_begin, &cursor->hold_begin);
1089 cursor->pinch_begin.notify = handle_pointer_pinch_begin; 1082 cursor->hold_begin.notify = handle_pointer_hold_begin;
1083 wl_signal_add(&wlr_cursor->events.hold_end, &cursor->hold_end);
1084 cursor->hold_end.notify = handle_pointer_hold_end;
1085
1090 wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin); 1086 wl_signal_add(&wlr_cursor->events.pinch_begin, &cursor->pinch_begin);
1091 cursor->pinch_update.notify = handle_pointer_pinch_update; 1087 cursor->pinch_begin.notify = handle_pointer_pinch_begin;
1092 wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update); 1088 wl_signal_add(&wlr_cursor->events.pinch_update, &cursor->pinch_update);
1093 cursor->pinch_end.notify = handle_pointer_pinch_end; 1089 cursor->pinch_update.notify = handle_pointer_pinch_update;
1094 wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end); 1090 wl_signal_add(&wlr_cursor->events.pinch_end, &cursor->pinch_end);
1095 cursor->swipe_begin.notify = handle_pointer_swipe_begin; 1091 cursor->pinch_end.notify = handle_pointer_pinch_end;
1092
1096 wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin); 1093 wl_signal_add(&wlr_cursor->events.swipe_begin, &cursor->swipe_begin);
1097 cursor->swipe_update.notify = handle_pointer_swipe_update; 1094 cursor->swipe_begin.notify = handle_pointer_swipe_begin;
1098 wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update); 1095 wl_signal_add(&wlr_cursor->events.swipe_update, &cursor->swipe_update);
1099 cursor->swipe_end.notify = handle_pointer_swipe_end; 1096 cursor->swipe_update.notify = handle_pointer_swipe_update;
1100 wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end); 1097 wl_signal_add(&wlr_cursor->events.swipe_end, &cursor->swipe_end);
1098 cursor->swipe_end.notify = handle_pointer_swipe_end;
1101 1099
1102 // input events 1100 // input events
1103 wl_signal_add(&wlr_cursor->events.motion, &cursor->motion); 1101 wl_signal_add(&wlr_cursor->events.motion, &cursor->motion);
@@ -1122,10 +1120,16 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat) {
1122 wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up); 1120 wl_signal_add(&wlr_cursor->events.touch_up, &cursor->touch_up);
1123 cursor->touch_up.notify = handle_touch_up; 1121 cursor->touch_up.notify = handle_touch_up;
1124 1122
1123 wl_signal_add(&wlr_cursor->events.touch_cancel, &cursor->touch_cancel);
1124 cursor->touch_cancel.notify = handle_touch_cancel;
1125
1125 wl_signal_add(&wlr_cursor->events.touch_motion, 1126 wl_signal_add(&wlr_cursor->events.touch_motion,
1126 &cursor->touch_motion); 1127 &cursor->touch_motion);
1127 cursor->touch_motion.notify = handle_touch_motion; 1128 cursor->touch_motion.notify = handle_touch_motion;
1128 1129
1130 wl_signal_add(&wlr_cursor->events.touch_frame, &cursor->touch_frame);
1131 cursor->touch_frame.notify = handle_touch_frame;
1132
1129 wl_signal_add(&wlr_cursor->events.tablet_tool_axis, 1133 wl_signal_add(&wlr_cursor->events.tablet_tool_axis,
1130 &cursor->tool_axis); 1134 &cursor->tool_axis);
1131 cursor->tool_axis.notify = handle_tool_axis; 1135 cursor->tool_axis.notify = handle_tool_axis;
@@ -1170,8 +1174,8 @@ void cursor_warp_to_container(struct sway_cursor *cursor,
1170 return; 1174 return;
1171 } 1175 }
1172 1176
1173 double x = container->x + container->width / 2.0; 1177 double x = container->pending.x + container->pending.width / 2.0;
1174 double y = container->y + container->height / 2.0; 1178 double y = container->pending.y + container->pending.height / 2.0;
1175 1179
1176 wlr_cursor_warp(cursor->cursor, NULL, x, y); 1180 wlr_cursor_warp(cursor->cursor, NULL, x, y);
1177 cursor_unhide(cursor); 1181 cursor_unhide(cursor);
@@ -1211,11 +1215,7 @@ uint32_t get_mouse_bindsym(const char *name, char **error) {
1211 // Get event code from name 1215 // Get event code from name
1212 int code = libevdev_event_code_from_name(EV_KEY, name); 1216 int code = libevdev_event_code_from_name(EV_KEY, name);
1213 if (code == -1) { 1217 if (code == -1) {
1214 size_t len = snprintf(NULL, 0, "Unknown event %s", name) + 1; 1218 *error = format_str("Unknown event %s", name);
1215 *error = malloc(len);
1216 if (*error) {
1217 snprintf(*error, len, "Unknown event %s", name);
1218 }
1219 return 0; 1219 return 0;
1220 } 1220 }
1221 return code; 1221 return code;
@@ -1237,13 +1237,8 @@ uint32_t get_mouse_bindcode(const char *name, char **error) {
1237 } 1237 }
1238 const char *event = libevdev_event_code_get_name(EV_KEY, code); 1238 const char *event = libevdev_event_code_get_name(EV_KEY, code);
1239 if (!event || strncmp(event, "BTN_", strlen("BTN_")) != 0) { 1239 if (!event || strncmp(event, "BTN_", strlen("BTN_")) != 0) {
1240 size_t len = snprintf(NULL, 0, "Event code %d (%s) is not a button", 1240 *error = format_str("Event code %d (%s) is not a button",
1241 code, event ? event : "(null)") + 1; 1241 code, event ? event : "(null)");
1242 *error = malloc(len);
1243 if (*error) {
1244 snprintf(*error, len, "Event code %d (%s) is not a button",
1245 code, event ? event : "(null)");
1246 }
1247 return 0; 1242 return 0;
1248 } 1243 }
1249 return code; 1244 return code;
@@ -1276,16 +1271,19 @@ const char *get_mouse_button_name(uint32_t button) {
1276static void warp_to_constraint_cursor_hint(struct sway_cursor *cursor) { 1271static void warp_to_constraint_cursor_hint(struct sway_cursor *cursor) {
1277 struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint; 1272 struct wlr_pointer_constraint_v1 *constraint = cursor->active_constraint;
1278 1273
1279 if (constraint->current.committed & 1274 if (constraint->current.cursor_hint.enabled) {
1280 WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) {
1281 double sx = constraint->current.cursor_hint.x; 1275 double sx = constraint->current.cursor_hint.x;
1282 double sy = constraint->current.cursor_hint.y; 1276 double sy = constraint->current.cursor_hint.y;
1283 1277
1284 struct sway_view *view = view_from_wlr_surface(constraint->surface); 1278 struct sway_view *view = view_from_wlr_surface(constraint->surface);
1279 if (!view) {
1280 return;
1281 }
1282
1285 struct sway_container *con = view->container; 1283 struct sway_container *con = view->container;
1286 1284
1287 double lx = sx + con->content_x - view->geometry.x; 1285 double lx = sx + con->pending.content_x - view->geometry.x;
1288 double ly = sy + con->content_y - view->geometry.y; 1286 double ly = sy + con->pending.content_y - view->geometry.y;
1289 1287
1290 wlr_cursor_warp(cursor->cursor, NULL, lx, ly); 1288 wlr_cursor_warp(cursor->cursor, NULL, lx, ly);
1291 1289
@@ -1332,12 +1330,9 @@ void handle_pointer_constraint(struct wl_listener *listener, void *data) {
1332 sway_constraint->destroy.notify = handle_constraint_destroy; 1330 sway_constraint->destroy.notify = handle_constraint_destroy;
1333 wl_signal_add(&constraint->events.destroy, &sway_constraint->destroy); 1331 wl_signal_add(&constraint->events.destroy, &sway_constraint->destroy);
1334 1332
1335 struct sway_node *focus = seat_get_focus(seat); 1333 struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface;
1336 if (focus && focus->type == N_CONTAINER && focus->sway_container->view) { 1334 if (surface && surface == constraint->surface) {
1337 struct wlr_surface *surface = focus->sway_container->view->surface; 1335 sway_cursor_constrain(seat->cursor, constraint);
1338 if (surface == constraint->surface) {
1339 sway_cursor_constrain(seat->cursor, constraint);
1340 }
1341 } 1336 }
1342} 1337}
1343 1338
@@ -1395,3 +1390,26 @@ void sway_cursor_constrain(struct sway_cursor *cursor,
1395 wl_signal_add(&constraint->surface->events.commit, 1390 wl_signal_add(&constraint->surface->events.commit,
1396 &cursor->constraint_commit); 1391 &cursor->constraint_commit);
1397} 1392}
1393
1394void handle_request_set_cursor_shape(struct wl_listener *listener, void *data) {
1395 const struct wlr_cursor_shape_manager_v1_request_set_shape_event *event = data;
1396 struct sway_seat *seat = event->seat_client->seat->data;
1397
1398 if (!seatop_allows_set_cursor(seat)) {
1399 return;
1400 }
1401
1402 struct wl_client *focused_client = NULL;
1403 struct wlr_surface *focused_surface = seat->wlr_seat->pointer_state.focused_surface;
1404 if (focused_surface != NULL) {
1405 focused_client = wl_resource_get_client(focused_surface->resource);
1406 }
1407
1408 // TODO: check cursor mode
1409 if (focused_client == NULL || event->seat_client->client != focused_client) {
1410 sway_log(SWAY_DEBUG, "denying request to set cursor from unfocused client");
1411 return;
1412 }
1413
1414 cursor_set_image(seat->cursor, wlr_cursor_shape_v1_name(event->shape), focused_client);
1415}
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index f04a8ce0..c1bbdde0 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -3,10 +3,9 @@
3#include <stdio.h> 3#include <stdio.h>
4#include <string.h> 4#include <string.h>
5#include <math.h> 5#include <math.h>
6#include <wlr/backend/libinput.h> 6#include <wlr/config.h>
7#include <wlr/types/wlr_cursor.h> 7#include <wlr/types/wlr_cursor.h>
8#include <wlr/types/wlr_keyboard_group.h> 8#include <wlr/types/wlr_keyboard_group.h>
9#include <wlr/types/wlr_input_inhibitor.h>
10#include <wlr/types/wlr_virtual_keyboard_v1.h> 9#include <wlr/types/wlr_virtual_keyboard_v1.h>
11#include <wlr/types/wlr_virtual_pointer_v1.h> 10#include <wlr/types/wlr_virtual_pointer_v1.h>
12#include "sway/config.h" 11#include "sway/config.h"
@@ -22,6 +21,10 @@
22#include "list.h" 21#include "list.h"
23#include "log.h" 22#include "log.h"
24 23
24#if WLR_HAS_LIBINPUT_BACKEND
25#include <wlr/backend/libinput.h>
26#endif
27
25#define DEFAULT_SEAT "seat0" 28#define DEFAULT_SEAT "seat0"
26 29
27struct input_config *current_input_config = NULL; 30struct input_config *current_input_config = NULL;
@@ -76,20 +79,13 @@ char *input_device_get_identifier(struct wlr_input_device *device) {
76 } 79 }
77 } 80 }
78 81
79 const char *fmt = "%d:%d:%s"; 82 char *identifier = format_str("%d:%d:%s", vendor, product, name);
80 int len = snprintf(NULL, 0, fmt, vendor, product, name) + 1;
81 char *identifier = malloc(len);
82 if (!identifier) {
83 sway_log(SWAY_ERROR, "Unable to allocate unique input device name");
84 return NULL;
85 }
86
87 snprintf(identifier, len, fmt, vendor, product, name);
88 free(name); 83 free(name);
89 return identifier; 84 return identifier;
90} 85}
91 86
92static bool device_is_touchpad(struct sway_input_device *device) { 87static bool device_is_touchpad(struct sway_input_device *device) {
88#if WLR_HAS_LIBINPUT_BACKEND
93 if (device->wlr_device->type != WLR_INPUT_DEVICE_POINTER || 89 if (device->wlr_device->type != WLR_INPUT_DEVICE_POINTER ||
94 !wlr_input_device_is_libinput(device->wlr_device)) { 90 !wlr_input_device_is_libinput(device->wlr_device)) {
95 return false; 91 return false;
@@ -99,6 +95,9 @@ static bool device_is_touchpad(struct sway_input_device *device) {
99 wlr_libinput_get_device_handle(device->wlr_device); 95 wlr_libinput_get_device_handle(device->wlr_device);
100 96
101 return libinput_device_config_tap_get_finger_count(libinput_device) > 0; 97 return libinput_device_config_tap_get_finger_count(libinput_device) > 0;
98#else
99 return false;
100#endif
102} 101}
103 102
104const char *input_device_get_type(struct sway_input_device *device) { 103const char *input_device_get_type(struct sway_input_device *device) {
@@ -236,7 +235,11 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
236 235
237 apply_input_type_config(input_device); 236 apply_input_type_config(input_device);
238 237
239 sway_input_configure_libinput_device(input_device); 238#if WLR_HAS_LIBINPUT_BACKEND
239 bool config_changed = sway_input_configure_libinput_device(input_device);
240#else
241 bool config_changed = false;
242#endif
240 243
241 wl_signal_add(&device->events.destroy, &input_device->device_destroy); 244 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
242 input_device->device_destroy.notify = handle_device_destroy; 245 input_device->device_destroy.notify = handle_device_destroy;
@@ -274,29 +277,9 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
274 } 277 }
275 278
276 ipc_event_input("added", input_device); 279 ipc_event_input("added", input_device);
277}
278 280
279static void handle_inhibit_activate(struct wl_listener *listener, void *data) { 281 if (config_changed) {
280 struct sway_input_manager *input_manager = wl_container_of( 282 ipc_event_input("libinput_config", input_device);
281 listener, input_manager, inhibit_activate);
282 struct sway_seat *seat;
283 wl_list_for_each(seat, &input_manager->seats, link) {
284 seat_set_exclusive_client(seat, input_manager->inhibit->active_client);
285 }
286}
287
288static void handle_inhibit_deactivate(struct wl_listener *listener, void *data) {
289 struct sway_input_manager *input_manager = wl_container_of(
290 listener, input_manager, inhibit_deactivate);
291 struct sway_seat *seat;
292 wl_list_for_each(seat, &input_manager->seats, link) {
293 seat_set_exclusive_client(seat, NULL);
294 struct sway_node *previous = seat_get_focus(seat);
295 if (previous) {
296 // Hack to get seat to re-focus the return value of get_focus
297 seat_set_focus(seat, NULL);
298 seat_set_focus(seat, previous);
299 }
300 } 283 }
301} 284}
302 285
@@ -377,7 +360,7 @@ void handle_virtual_keyboard(struct wl_listener *listener, void *data) {
377 struct sway_input_manager *input_manager = 360 struct sway_input_manager *input_manager =
378 wl_container_of(listener, input_manager, virtual_keyboard_new); 361 wl_container_of(listener, input_manager, virtual_keyboard_new);
379 struct wlr_virtual_keyboard_v1 *keyboard = data; 362 struct wlr_virtual_keyboard_v1 *keyboard = data;
380 struct wlr_input_device *device = &keyboard->input_device; 363 struct wlr_input_device *device = &keyboard->keyboard.base;
381 364
382 // TODO: Amend protocol to allow NULL seat 365 // TODO: Amend protocol to allow NULL seat
383 struct sway_seat *seat = keyboard->seat ? 366 struct sway_seat *seat = keyboard->seat ?
@@ -410,7 +393,7 @@ void handle_virtual_pointer(struct wl_listener *listener, void *data) {
410 wl_container_of(listener, input_manager, virtual_pointer_new); 393 wl_container_of(listener, input_manager, virtual_pointer_new);
411 struct wlr_virtual_pointer_v1_new_pointer_event *event = data; 394 struct wlr_virtual_pointer_v1_new_pointer_event *event = data;
412 struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; 395 struct wlr_virtual_pointer_v1 *pointer = event->new_pointer;
413 struct wlr_input_device *device = &pointer->input_device; 396 struct wlr_input_device *device = &pointer->pointer.base;
414 397
415 struct sway_seat *seat = event->suggested_seat ? 398 struct sway_seat *seat = event->suggested_seat ?
416 input_manager_sway_seat_from_wlr_seat(event->suggested_seat) : 399 input_manager_sway_seat_from_wlr_seat(event->suggested_seat) :
@@ -468,14 +451,6 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) {
468 &input->virtual_pointer_new); 451 &input->virtual_pointer_new);
469 input->virtual_pointer_new.notify = handle_virtual_pointer; 452 input->virtual_pointer_new.notify = handle_virtual_pointer;
470 453
471 input->inhibit = wlr_input_inhibit_manager_create(server->wl_display);
472 input->inhibit_activate.notify = handle_inhibit_activate;
473 wl_signal_add(&input->inhibit->events.activate,
474 &input->inhibit_activate);
475 input->inhibit_deactivate.notify = handle_inhibit_deactivate;
476 wl_signal_add(&input->inhibit->events.deactivate,
477 &input->inhibit_deactivate);
478
479 input->keyboard_shortcuts_inhibit = 454 input->keyboard_shortcuts_inhibit =
480 wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display); 455 wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display);
481 input->keyboard_shortcuts_inhibit_new_inhibitor.notify = 456 input->keyboard_shortcuts_inhibit_new_inhibitor.notify =
@@ -483,6 +458,8 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) {
483 wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor, 458 wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor,
484 &input->keyboard_shortcuts_inhibit_new_inhibitor); 459 &input->keyboard_shortcuts_inhibit_new_inhibitor);
485 460
461 input->pointer_gestures = wlr_pointer_gestures_v1_create(server->wl_display);
462
486 return input; 463 return input;
487} 464}
488 465
@@ -520,21 +497,50 @@ static void retranslate_keysyms(struct input_config *input_config) {
520 return; 497 return;
521 } 498 }
522 } 499 }
500
501 for (int i = 0; i < config->input_type_configs->length; ++i) {
502 struct input_config *ic = config->input_type_configs->items[i];
503 if (ic->xkb_layout || ic->xkb_file) {
504 // this is the first config with xkb_layout or xkb_file
505 if (ic->identifier == input_config->identifier) {
506 translate_keysyms(ic);
507 }
508
509 return;
510 }
511 }
523} 512}
524 513
525static void input_manager_configure_input( 514static void input_manager_configure_input(
526 struct sway_input_device *input_device) { 515 struct sway_input_device *input_device) {
527 sway_input_configure_libinput_device(input_device); 516#if WLR_HAS_LIBINPUT_BACKEND
517 bool config_changed = sway_input_configure_libinput_device(input_device);
518#else
519 bool config_changed = false;
520#endif
528 struct sway_seat *seat = NULL; 521 struct sway_seat *seat = NULL;
529 wl_list_for_each(seat, &server.input->seats, link) { 522 wl_list_for_each(seat, &server.input->seats, link) {
530 seat_configure_device(seat, input_device); 523 seat_configure_device(seat, input_device);
531 } 524 }
525 if (config_changed) {
526 ipc_event_input("libinput_config", input_device);
527 }
532} 528}
533 529
534void input_manager_configure_all_inputs(void) { 530void input_manager_configure_all_input_mappings(void) {
535 struct sway_input_device *input_device = NULL; 531 struct sway_input_device *input_device;
536 wl_list_for_each(input_device, &server.input->devices, link) { 532 wl_list_for_each(input_device, &server.input->devices, link) {
537 input_manager_configure_input(input_device); 533 struct sway_seat *seat;
534 wl_list_for_each(seat, &server.input->seats, link) {
535 seat_configure_device_mapping(seat, input_device);
536 }
537
538#if WLR_HAS_LIBINPUT_BACKEND
539 // Input devices mapped to unavailable outputs get their libinput
540 // send_events setting switched to false. We need to re-enable this
541 // when the output appears.
542 sway_input_configure_libinput_device_send_events(input_device);
543#endif
538 } 544 }
539} 545}
540 546
@@ -556,7 +562,9 @@ void input_manager_apply_input_config(struct input_config *input_config) {
556} 562}
557 563
558void input_manager_reset_input(struct sway_input_device *input_device) { 564void input_manager_reset_input(struct sway_input_device *input_device) {
565#if WLR_HAS_LIBINPUT_BACKEND
559 sway_input_reset_libinput_device(input_device); 566 sway_input_reset_libinput_device(input_device);
567#endif
560 struct sway_seat *seat = NULL; 568 struct sway_seat *seat = NULL;
561 wl_list_for_each(seat, &server.input->seats, link) { 569 wl_list_for_each(seat, &server.input->seats, link) {
562 seat_reset_device(seat, input_device); 570 seat_reset_device(seat, input_device);
@@ -564,6 +572,13 @@ void input_manager_reset_input(struct sway_input_device *input_device) {
564} 572}
565 573
566void input_manager_reset_all_inputs(void) { 574void input_manager_reset_all_inputs(void) {
575 // Set the active keyboard to NULL to avoid spamming configuration updates
576 // for all keyboard devices.
577 struct sway_seat *seat;
578 wl_list_for_each(seat, &server.input->seats, link) {
579 wlr_seat_set_keyboard(seat->wlr_seat, NULL);
580 }
581
567 struct sway_input_device *input_device = NULL; 582 struct sway_input_device *input_device = NULL;
568 wl_list_for_each(input_device, &server.input->devices, link) { 583 wl_list_for_each(input_device, &server.input->devices, link) {
569 input_manager_reset_input(input_device); 584 input_manager_reset_input(input_device);
@@ -572,7 +587,6 @@ void input_manager_reset_all_inputs(void) {
572 // If there is at least one keyboard using the default keymap, repeat delay, 587 // If there is at least one keyboard using the default keymap, repeat delay,
573 // and repeat rate, then it is possible that there is a keyboard group that 588 // and repeat rate, then it is possible that there is a keyboard group that
574 // need their keyboard disarmed. 589 // need their keyboard disarmed.
575 struct sway_seat *seat;
576 wl_list_for_each(seat, &server.input->seats, link) { 590 wl_list_for_each(seat, &server.input->seats, link) {
577 struct sway_keyboard_group *group; 591 struct sway_keyboard_group *group;
578 wl_list_for_each(group, &seat->keyboard_groups, link) { 592 wl_list_for_each(group, &seat->keyboard_groups, link) {
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index ce259eb2..b97f0152 100644
--- a/sway/input/keyboard.c
+++ b/sway/input/keyboard.c
@@ -1,15 +1,13 @@
1#include <assert.h> 1#include <assert.h>
2#include <limits.h> 2#include <limits.h>
3#include <strings.h> 3#include <strings.h>
4#include <wlr/config.h>
4#include <wlr/backend/multi.h> 5#include <wlr/backend/multi.h>
5#include <wlr/backend/session.h>
6#include <wlr/interfaces/wlr_keyboard.h> 6#include <wlr/interfaces/wlr_keyboard.h>
7#include <wlr/types/wlr_idle.h>
8#include <wlr/types/wlr_keyboard.h> 7#include <wlr/types/wlr_keyboard.h>
9#include <wlr/types/wlr_keyboard_group.h> 8#include <wlr/types/wlr_keyboard_group.h>
10#include <xkbcommon/xkbcommon-names.h> 9#include <xkbcommon/xkbcommon-names.h>
11#include "sway/commands.h" 10#include "sway/commands.h"
12#include "sway/desktop/transaction.h"
13#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
14#include "sway/input/keyboard.h" 12#include "sway/input/keyboard.h"
15#include "sway/input/seat.h" 13#include "sway/input/seat.h"
@@ -17,6 +15,10 @@
17#include "sway/ipc-server.h" 15#include "sway/ipc-server.h"
18#include "log.h" 16#include "log.h"
19 17
18#if WLR_HAS_SESSION
19#include <wlr/backend/session.h>
20#endif
21
20static struct modifier_key { 22static struct modifier_key {
21 char *name; 23 char *name;
22 uint32_t mod; 24 uint32_t mod;
@@ -265,14 +267,12 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
265 xkb_keysym_t keysym = pressed_keysyms[i]; 267 xkb_keysym_t keysym = pressed_keysyms[i];
266 if (keysym >= XKB_KEY_XF86Switch_VT_1 && 268 if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
267 keysym <= XKB_KEY_XF86Switch_VT_12) { 269 keysym <= XKB_KEY_XF86Switch_VT_12) {
268 if (wlr_backend_is_multi(server.backend)) { 270#if WLR_HAS_SESSION
269 struct wlr_session *session = 271 if (server.session) {
270 wlr_backend_get_session(server.backend); 272 unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
271 if (session) { 273 wlr_session_change_vt(server.session, vt);
272 unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
273 wlr_session_change_vt(session, vt);
274 }
275 } 274 }
275#endif
276 return true; 276 return true;
277 } 277 }
278 } 278 }
@@ -292,14 +292,12 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
292static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard, 292static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard,
293 xkb_keycode_t keycode, const xkb_keysym_t **keysyms, 293 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
294 uint32_t *modifiers) { 294 uint32_t *modifiers) {
295 struct wlr_input_device *device = 295 *modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
296 keyboard->seat_device->input_device->wlr_device;
297 *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
298 xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( 296 xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
299 device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); 297 keyboard->wlr->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
300 *modifiers = *modifiers & ~consumed; 298 *modifiers = *modifiers & ~consumed;
301 299
302 return xkb_state_key_get_syms(device->keyboard->xkb_state, 300 return xkb_state_key_get_syms(keyboard->wlr->xkb_state,
303 keycode, keysyms); 301 keycode, keysyms);
304} 302}
305 303
@@ -315,13 +313,11 @@ static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard,
315static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard, 313static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard,
316 xkb_keycode_t keycode, const xkb_keysym_t **keysyms, 314 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
317 uint32_t *modifiers) { 315 uint32_t *modifiers) {
318 struct wlr_input_device *device = 316 *modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
319 keyboard->seat_device->input_device->wlr_device;
320 *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
321 317
322 xkb_layout_index_t layout_index = xkb_state_key_get_layout( 318 xkb_layout_index_t layout_index = xkb_state_key_get_layout(
323 device->keyboard->xkb_state, keycode); 319 keyboard->wlr->xkb_state, keycode);
324 return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap, 320 return xkb_keymap_key_get_syms_by_level(keyboard->wlr->keymap,
325 keycode, layout_index, 0, keysyms); 321 keycode, layout_index, 0, keysyms);
326} 322}
327 323
@@ -361,8 +357,7 @@ static void update_keyboard_state(struct sway_keyboard *keyboard,
361 keyinfo->keycode, &keyinfo->translated_keysyms, 357 keyinfo->keycode, &keyinfo->translated_keysyms,
362 &keyinfo->translated_modifiers); 358 &keyinfo->translated_modifiers);
363 359
364 keyinfo->code_modifiers = wlr_keyboard_get_modifiers( 360 keyinfo->code_modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
365 keyboard->seat_device->input_device->wlr_device->keyboard);
366 361
367 // Update shortcut model keyinfo 362 // Update shortcut model keyinfo
368 update_shortcut_state(&keyboard->state_keycodes, raw_keycode, keystate, 363 update_shortcut_state(&keyboard->state_keycodes, raw_keycode, keystate,
@@ -379,16 +374,38 @@ static void update_keyboard_state(struct sway_keyboard *keyboard,
379 } 374 }
380} 375}
381 376
377/**
378 * Get keyboard grab of the seat from sway_keyboard if we should forward events
379 * to it.
380 *
381 * Returns NULL if the keyboard is not grabbed by an input method,
382 * or if event is from virtual keyboard of the same client as grab.
383 * TODO: see swaywm/wlroots#2322
384 */
385static struct wlr_input_method_keyboard_grab_v2 *keyboard_get_im_grab(
386 struct sway_keyboard *keyboard) {
387 struct wlr_input_method_v2 *input_method = keyboard->seat_device->
388 sway_seat->im_relay.input_method;
389 struct wlr_virtual_keyboard_v1 *virtual_keyboard =
390 wlr_input_device_get_virtual_keyboard(keyboard->seat_device->input_device->wlr_device);
391 if (!input_method || !input_method->keyboard_grab || (virtual_keyboard &&
392 wl_resource_get_client(virtual_keyboard->resource) ==
393 wl_resource_get_client(input_method->keyboard_grab->resource))) {
394 return NULL;
395 }
396 return input_method->keyboard_grab;
397}
398
382static void handle_key_event(struct sway_keyboard *keyboard, 399static void handle_key_event(struct sway_keyboard *keyboard,
383 struct wlr_event_keyboard_key *event) { 400 struct wlr_keyboard_key_event *event) {
384 struct sway_seat *seat = keyboard->seat_device->sway_seat; 401 struct sway_seat *seat = keyboard->seat_device->sway_seat;
385 struct wlr_seat *wlr_seat = seat->wlr_seat; 402 struct wlr_seat *wlr_seat = seat->wlr_seat;
386 struct wlr_input_device *wlr_device = 403 struct wlr_input_device *wlr_device =
387 keyboard->seat_device->input_device->wlr_device; 404 keyboard->seat_device->input_device->wlr_device;
388 char *device_identifier = input_device_get_identifier(wlr_device); 405 char *device_identifier = input_device_get_identifier(wlr_device);
389 bool exact_identifier = wlr_device->keyboard->group != NULL; 406 bool exact_identifier = keyboard->wlr->group != NULL;
390 seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); 407 seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD);
391 bool input_inhibited = seat->exclusive_client != NULL; 408 bool locked = server.session_lock.lock;
392 struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = 409 struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor =
393 keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); 410 keyboard_shortcuts_inhibitor_get_for_focused_surface(seat);
394 bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active; 411 bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active;
@@ -406,17 +423,17 @@ static void handle_key_event(struct sway_keyboard *keyboard,
406 struct sway_binding *binding_released = NULL; 423 struct sway_binding *binding_released = NULL;
407 get_active_binding(&keyboard->state_keycodes, 424 get_active_binding(&keyboard->state_keycodes,
408 config->current_mode->keycode_bindings, &binding_released, 425 config->current_mode->keycode_bindings, &binding_released,
409 keyinfo.code_modifiers, true, input_inhibited, 426 keyinfo.code_modifiers, true, locked,
410 shortcuts_inhibited, device_identifier, 427 shortcuts_inhibited, device_identifier,
411 exact_identifier, keyboard->effective_layout); 428 exact_identifier, keyboard->effective_layout);
412 get_active_binding(&keyboard->state_keysyms_raw, 429 get_active_binding(&keyboard->state_keysyms_raw,
413 config->current_mode->keysym_bindings, &binding_released, 430 config->current_mode->keysym_bindings, &binding_released,
414 keyinfo.raw_modifiers, true, input_inhibited, 431 keyinfo.raw_modifiers, true, locked,
415 shortcuts_inhibited, device_identifier, 432 shortcuts_inhibited, device_identifier,
416 exact_identifier, keyboard->effective_layout); 433 exact_identifier, keyboard->effective_layout);
417 get_active_binding(&keyboard->state_keysyms_translated, 434 get_active_binding(&keyboard->state_keysyms_translated,
418 config->current_mode->keysym_bindings, &binding_released, 435 config->current_mode->keysym_bindings, &binding_released,
419 keyinfo.translated_modifiers, true, input_inhibited, 436 keyinfo.translated_modifiers, true, locked,
420 shortcuts_inhibited, device_identifier, 437 shortcuts_inhibited, device_identifier,
421 exact_identifier, keyboard->effective_layout); 438 exact_identifier, keyboard->effective_layout);
422 439
@@ -438,17 +455,17 @@ static void handle_key_event(struct sway_keyboard *keyboard,
438 if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { 455 if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
439 get_active_binding(&keyboard->state_keycodes, 456 get_active_binding(&keyboard->state_keycodes,
440 config->current_mode->keycode_bindings, &binding, 457 config->current_mode->keycode_bindings, &binding,
441 keyinfo.code_modifiers, false, input_inhibited, 458 keyinfo.code_modifiers, false, locked,
442 shortcuts_inhibited, device_identifier, 459 shortcuts_inhibited, device_identifier,
443 exact_identifier, keyboard->effective_layout); 460 exact_identifier, keyboard->effective_layout);
444 get_active_binding(&keyboard->state_keysyms_raw, 461 get_active_binding(&keyboard->state_keysyms_raw,
445 config->current_mode->keysym_bindings, &binding, 462 config->current_mode->keysym_bindings, &binding,
446 keyinfo.raw_modifiers, false, input_inhibited, 463 keyinfo.raw_modifiers, false, locked,
447 shortcuts_inhibited, device_identifier, 464 shortcuts_inhibited, device_identifier,
448 exact_identifier, keyboard->effective_layout); 465 exact_identifier, keyboard->effective_layout);
449 get_active_binding(&keyboard->state_keysyms_translated, 466 get_active_binding(&keyboard->state_keysyms_translated,
450 config->current_mode->keysym_bindings, &binding, 467 config->current_mode->keysym_bindings, &binding,
451 keyinfo.translated_modifiers, false, input_inhibited, 468 keyinfo.translated_modifiers, false, locked,
452 shortcuts_inhibited, device_identifier, 469 shortcuts_inhibited, device_identifier,
453 exact_identifier, keyboard->effective_layout); 470 exact_identifier, keyboard->effective_layout);
454 } 471 }
@@ -456,10 +473,10 @@ static void handle_key_event(struct sway_keyboard *keyboard,
456 // Set up (or clear) keyboard repeat for a pressed binding. Since the 473 // Set up (or clear) keyboard repeat for a pressed binding. Since the
457 // binding may remove the keyboard, the timer needs to be updated first 474 // binding may remove the keyboard, the timer needs to be updated first
458 if (binding && !(binding->flags & BINDING_NOREPEAT) && 475 if (binding && !(binding->flags & BINDING_NOREPEAT) &&
459 wlr_device->keyboard->repeat_info.delay > 0) { 476 keyboard->wlr->repeat_info.delay > 0) {
460 keyboard->repeat_binding = binding; 477 keyboard->repeat_binding = binding;
461 if (wl_event_source_timer_update(keyboard->key_repeat_source, 478 if (wl_event_source_timer_update(keyboard->key_repeat_source,
462 wlr_device->keyboard->repeat_info.delay) < 0) { 479 keyboard->wlr->repeat_info.delay) < 0) {
463 sway_log(SWAY_DEBUG, "failed to set key repeat timer"); 480 sway_log(SWAY_DEBUG, "failed to set key repeat timer");
464 } 481 }
465 } else if (keyboard->repeat_binding) { 482 } else if (keyboard->repeat_binding) {
@@ -471,7 +488,7 @@ static void handle_key_event(struct sway_keyboard *keyboard,
471 handled = true; 488 handled = true;
472 } 489 }
473 490
474 if (!handled && wlr_device->keyboard->group) { 491 if (!handled && keyboard->wlr->group) {
475 // Only handle device specific bindings for keyboards in a group 492 // Only handle device specific bindings for keyboards in a group
476 free(device_identifier); 493 free(device_identifier);
477 return; 494 return;
@@ -489,18 +506,41 @@ static void handle_key_event(struct sway_keyboard *keyboard,
489 keyinfo.raw_keysyms_len); 506 keyinfo.raw_keysyms_len);
490 } 507 }
491 508
492 if (!handled || event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { 509 if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) {
510 // If the pressed event was sent to a client, also send the released
511 // event. In particular, don't send the released event to the IM grab.
493 bool pressed_sent = update_shortcut_state( 512 bool pressed_sent = update_shortcut_state(
494 &keyboard->state_pressed_sent, event->keycode, event->state, 513 &keyboard->state_pressed_sent, event->keycode,
495 keyinfo.keycode, 0); 514 event->state, keyinfo.keycode, 0);
496 if (pressed_sent || event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { 515 if (pressed_sent) {
497 wlr_seat_set_keyboard(wlr_seat, wlr_device); 516 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
498 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, 517 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
499 event->keycode, event->state); 518 event->keycode, event->state);
519 handled = true;
520 }
521 }
522
523 if (!handled) {
524 struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(keyboard);
525
526 if (kb_grab) {
527 wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr);
528 wlr_input_method_keyboard_grab_v2_send_key(kb_grab,
529 event->time_msec, event->keycode, event->state);
530 handled = true;
500 } 531 }
501 } 532 }
502 533
503 transaction_commit_dirty(); 534 if (!handled && event->state != WL_KEYBOARD_KEY_STATE_RELEASED) {
535 // If a released event failed pressed sent test, and not in sent to
536 // keyboard grab, it is still not handled. Don't handle released here.
537 update_shortcut_state(
538 &keyboard->state_pressed_sent, event->keycode, event->state,
539 keyinfo.keycode, 0);
540 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
541 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
542 event->keycode, event->state);
543 }
504 544
505 free(device_identifier); 545 free(device_identifier);
506} 546}
@@ -573,21 +613,18 @@ static void handle_keyboard_group_leave(struct wl_listener *listener,
573} 613}
574 614
575static int handle_keyboard_repeat(void *data) { 615static int handle_keyboard_repeat(void *data) {
576 struct sway_keyboard *keyboard = (struct sway_keyboard *)data; 616 struct sway_keyboard *keyboard = data;
577 struct wlr_keyboard *wlr_device =
578 keyboard->seat_device->input_device->wlr_device->keyboard;
579 if (keyboard->repeat_binding) { 617 if (keyboard->repeat_binding) {
580 if (wlr_device->repeat_info.rate > 0) { 618 if (keyboard->wlr->repeat_info.rate > 0) {
581 // We queue the next event first, as the command might cancel it 619 // We queue the next event first, as the command might cancel it
582 if (wl_event_source_timer_update(keyboard->key_repeat_source, 620 if (wl_event_source_timer_update(keyboard->key_repeat_source,
583 1000 / wlr_device->repeat_info.rate) < 0) { 621 1000 / keyboard->wlr->repeat_info.rate) < 0) {
584 sway_log(SWAY_DEBUG, "failed to update key repeat timer"); 622 sway_log(SWAY_DEBUG, "failed to update key repeat timer");
585 } 623 }
586 } 624 }
587 625
588 seat_execute_command(keyboard->seat_device->sway_seat, 626 seat_execute_command(keyboard->seat_device->sway_seat,
589 keyboard->repeat_binding); 627 keyboard->repeat_binding);
590 transaction_commit_dirty();
591 } 628 }
592 return 0; 629 return 0;
593} 630}
@@ -614,22 +651,28 @@ static void determine_bar_visibility(uint32_t modifiers) {
614} 651}
615 652
616static void handle_modifier_event(struct sway_keyboard *keyboard) { 653static void handle_modifier_event(struct sway_keyboard *keyboard) {
617 struct wlr_input_device *wlr_device = 654 if (!keyboard->wlr->group) {
618 keyboard->seat_device->input_device->wlr_device; 655 struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(keyboard);
619 if (!wlr_device->keyboard->group) {
620 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
621 wlr_seat_set_keyboard(wlr_seat, wlr_device);
622 wlr_seat_keyboard_notify_modifiers(wlr_seat,
623 &wlr_device->keyboard->modifiers);
624 656
625 uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard); 657 if (kb_grab) {
658 wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr);
659 wlr_input_method_keyboard_grab_v2_send_modifiers(kb_grab,
660 &keyboard->wlr->modifiers);
661 } else {
662 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
663 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
664 wlr_seat_keyboard_notify_modifiers(wlr_seat,
665 &keyboard->wlr->modifiers);
666 }
667
668 uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
626 determine_bar_visibility(modifiers); 669 determine_bar_visibility(modifiers);
627 } 670 }
628 671
629 if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout) { 672 if (keyboard->wlr->modifiers.group != keyboard->effective_layout) {
630 keyboard->effective_layout = wlr_device->keyboard->modifiers.group; 673 keyboard->effective_layout = keyboard->wlr->modifiers.group;
631 674
632 if (!wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard)) { 675 if (!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr)) {
633 ipc_event_input("xkb_layout", keyboard->seat_device->input_device); 676 ipc_event_input("xkb_layout", keyboard->seat_device->input_device);
634 } 677 }
635 } 678 }
@@ -658,6 +701,7 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
658 } 701 }
659 702
660 keyboard->seat_device = device; 703 keyboard->seat_device = device;
704 keyboard->wlr = wlr_keyboard_from_input_device(device->input_device->wlr_device);
661 device->keyboard = keyboard; 705 device->keyboard = keyboard;
662 706
663 wl_list_init(&keyboard->keyboard_key.link); 707 wl_list_init(&keyboard->keyboard_key.link);
@@ -671,23 +715,11 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
671 715
672static void handle_xkb_context_log(struct xkb_context *context, 716static void handle_xkb_context_log(struct xkb_context *context,
673 enum xkb_log_level level, const char *format, va_list args) { 717 enum xkb_log_level level, const char *format, va_list args) {
674 va_list args_copy; 718 char *error = vformat_str(format, args);
675 va_copy(args_copy, args);
676 size_t length = vsnprintf(NULL, 0, format, args_copy) + 1;
677 va_end(args_copy);
678
679 char *error = malloc(length);
680 if (!error) {
681 sway_log(SWAY_ERROR, "Failed to allocate libxkbcommon log message");
682 return;
683 }
684
685 va_copy(args_copy, args);
686 vsnprintf(error, length, format, args_copy);
687 va_end(args_copy);
688 719
689 if (error[length - 2] == '\n') { 720 size_t len = strlen(error);
690 error[length - 2] = '\0'; 721 if (error[len - 1] == '\n') {
722 error[len - 1] = '\0';
691 } 723 }
692 724
693 sway_log_importance_t importance = SWAY_DEBUG; 725 sway_log_importance_t importance = SWAY_DEBUG;
@@ -708,7 +740,7 @@ static void handle_xkb_context_log(struct xkb_context *context,
708 740
709struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, 741struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic,
710 char **error) { 742 char **error) {
711 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 743 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_SECURE_GETENV);
712 if (!sway_assert(context, "cannot create XKB context")) { 744 if (!sway_assert(context, "cannot create XKB context")) {
713 return NULL; 745 return NULL;
714 } 746 }
@@ -722,13 +754,8 @@ struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic,
722 if (!keymap_file) { 754 if (!keymap_file) {
723 sway_log_errno(SWAY_ERROR, "cannot read xkb file %s", ic->xkb_file); 755 sway_log_errno(SWAY_ERROR, "cannot read xkb file %s", ic->xkb_file);
724 if (error) { 756 if (error) {
725 size_t len = snprintf(NULL, 0, "cannot read xkb file %s: %s", 757 *error = format_str("cannot read xkb file %s: %s",
726 ic->xkb_file, strerror(errno)) + 1; 758 ic->xkb_file, strerror(errno));
727 *error = malloc(len);
728 if (*error) {
729 snprintf(*error, len, "cannot read xkb_file %s: %s",
730 ic->xkb_file, strerror(errno));
731 }
732 } 759 }
733 goto cleanup; 760 goto cleanup;
734 } 761 }
@@ -766,13 +793,12 @@ static void destroy_empty_wlr_keyboard_group(void *data) {
766 793
767static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) { 794static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) {
768 struct sway_input_device *device = keyboard->seat_device->input_device; 795 struct sway_input_device *device = keyboard->seat_device->input_device;
769 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard; 796 struct wlr_keyboard_group *wlr_group = keyboard->wlr->group;
770 struct wlr_keyboard_group *wlr_group = wlr_keyboard->group;
771 797
772 sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p", 798 sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p",
773 device->identifier, wlr_group); 799 device->identifier, wlr_group);
774 800
775 wlr_keyboard_group_remove_keyboard(wlr_keyboard->group, wlr_keyboard); 801 wlr_keyboard_group_remove_keyboard(keyboard->wlr->group, keyboard->wlr);
776 802
777 if (wl_list_empty(&wlr_group->devices)) { 803 if (wl_list_empty(&wlr_group->devices)) {
778 sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p", 804 sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p",
@@ -797,9 +823,7 @@ static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) {
797} 823}
798 824
799static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) { 825static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
800 struct sway_input_device *device = keyboard->seat_device->input_device; 826 if (!keyboard->wlr->group) {
801 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
802 if (!wlr_keyboard->group) {
803 return; 827 return;
804 } 828 }
805 829
@@ -815,7 +839,7 @@ static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
815 break; 839 break;
816 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ 840 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */
817 case KEYBOARD_GROUP_SMART:; 841 case KEYBOARD_GROUP_SMART:;
818 struct wlr_keyboard_group *group = wlr_keyboard->group; 842 struct wlr_keyboard_group *group = keyboard->wlr->group;
819 if (!wlr_keyboard_keymaps_match(keyboard->keymap, group->keyboard.keymap) || 843 if (!wlr_keyboard_keymaps_match(keyboard->keymap, group->keyboard.keymap) ||
820 !repeat_info_match(keyboard, &group->keyboard)) { 844 !repeat_info_match(keyboard, &group->keyboard)) {
821 sway_keyboard_group_remove(keyboard); 845 sway_keyboard_group_remove(keyboard);
@@ -826,7 +850,6 @@ static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
826 850
827static void sway_keyboard_group_add(struct sway_keyboard *keyboard) { 851static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
828 struct sway_input_device *device = keyboard->seat_device->input_device; 852 struct sway_input_device *device = keyboard->seat_device->input_device;
829 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
830 struct sway_seat *seat = keyboard->seat_device->sway_seat; 853 struct sway_seat *seat = keyboard->seat_device->sway_seat;
831 struct seat_config *sc = seat_get_config(seat); 854 struct seat_config *sc = seat_get_config(seat);
832 855
@@ -858,7 +881,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
858 repeat_info_match(keyboard, &wlr_group->keyboard)) { 881 repeat_info_match(keyboard, &wlr_group->keyboard)) {
859 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", 882 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
860 device->identifier, wlr_group); 883 device->identifier, wlr_group);
861 wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard); 884 wlr_keyboard_group_add_keyboard(wlr_group, keyboard->wlr);
862 return; 885 return;
863 } 886 }
864 break; 887 break;
@@ -897,7 +920,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
897 goto cleanup; 920 goto cleanup;
898 } 921 }
899 sway_group->seat_device->input_device->wlr_device = 922 sway_group->seat_device->input_device->wlr_device =
900 sway_group->wlr_group->input_device; 923 &sway_group->wlr_group->keyboard.base;
901 924
902 if (!sway_keyboard_create(seat, sway_group->seat_device)) { 925 if (!sway_keyboard_create(seat, sway_group->seat_device)) {
903 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group"); 926 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group");
@@ -906,7 +929,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
906 929
907 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", 930 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
908 device->identifier, sway_group->wlr_group); 931 device->identifier, sway_group->wlr_group);
909 wlr_keyboard_group_add_keyboard(sway_group->wlr_group, wlr_keyboard); 932 wlr_keyboard_group_add_keyboard(sway_group->wlr_group, keyboard->wlr);
910 933
911 wl_list_insert(&seat->keyboard_groups, &sway_group->link); 934 wl_list_insert(&seat->keyboard_groups, &sway_group->link);
912 935
@@ -938,10 +961,8 @@ cleanup:
938void sway_keyboard_configure(struct sway_keyboard *keyboard) { 961void sway_keyboard_configure(struct sway_keyboard *keyboard) {
939 struct input_config *input_config = 962 struct input_config *input_config =
940 input_device_get_config(keyboard->seat_device->input_device); 963 input_device_get_config(keyboard->seat_device->input_device);
941 struct wlr_input_device *wlr_device =
942 keyboard->seat_device->input_device->wlr_device;
943 964
944 if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard), 965 if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr),
945 "sway_keyboard_configure should not be called with a " 966 "sway_keyboard_configure should not be called with a "
946 "keyboard group's keyboard")) { 967 "keyboard group's keyboard")) {
947 return; 968 return;
@@ -983,11 +1004,11 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
983 1004
984 sway_keyboard_group_remove_invalid(keyboard); 1005 sway_keyboard_group_remove_invalid(keyboard);
985 1006
986 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); 1007 wlr_keyboard_set_keymap(keyboard->wlr, keyboard->keymap);
987 wlr_keyboard_set_repeat_info(wlr_device->keyboard, 1008 wlr_keyboard_set_repeat_info(keyboard->wlr,
988 keyboard->repeat_rate, keyboard->repeat_delay); 1009 keyboard->repeat_rate, keyboard->repeat_delay);
989 1010
990 if (!wlr_device->keyboard->group) { 1011 if (!keyboard->wlr->group) {
991 sway_keyboard_group_add(keyboard); 1012 sway_keyboard_group_add(keyboard);
992 } 1013 }
993 1014
@@ -1007,40 +1028,42 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
1007 } 1028 }
1008 } 1029 }
1009 if (locked_mods) { 1030 if (locked_mods) {
1010 wlr_keyboard_notify_modifiers(wlr_device->keyboard, 0, 0, 1031 wlr_keyboard_notify_modifiers(keyboard->wlr, 0, 0,
1011 locked_mods, 0); 1032 locked_mods, 0);
1012 uint32_t leds = 0; 1033 uint32_t leds = 0;
1013 for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { 1034 for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) {
1014 if (xkb_state_led_index_is_active( 1035 if (xkb_state_led_index_is_active(keyboard->wlr->xkb_state,
1015 wlr_device->keyboard->xkb_state, 1036 keyboard->wlr->led_indexes[i])) {
1016 wlr_device->keyboard->led_indexes[i])) {
1017 leds |= (1 << i); 1037 leds |= (1 << i);
1018 } 1038 }
1019 } 1039 }
1020 if (wlr_device->keyboard->group) { 1040 if (keyboard->wlr->group) {
1021 wlr_keyboard_led_update( 1041 wlr_keyboard_led_update(&keyboard->wlr->group->keyboard, leds);
1022 &wlr_device->keyboard->group->keyboard, leds);
1023 } else { 1042 } else {
1024 wlr_keyboard_led_update(wlr_device->keyboard, leds); 1043 wlr_keyboard_led_update(keyboard->wlr, leds);
1025 } 1044 }
1026 } 1045 }
1027 } else { 1046 } else {
1028 xkb_keymap_unref(keymap); 1047 xkb_keymap_unref(keymap);
1029 sway_keyboard_group_remove_invalid(keyboard); 1048 sway_keyboard_group_remove_invalid(keyboard);
1030 if (!wlr_device->keyboard->group) { 1049 if (!keyboard->wlr->group) {
1031 sway_keyboard_group_add(keyboard); 1050 sway_keyboard_group_add(keyboard);
1032 } 1051 }
1033 } 1052 }
1034 1053
1054 // If the seat has no active keyboard, set this one
1035 struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat; 1055 struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat;
1036 wlr_seat_set_keyboard(seat, wlr_device); 1056 struct wlr_keyboard *current_keyboard = seat->keyboard_state.keyboard;
1057 if (current_keyboard == NULL) {
1058 wlr_seat_set_keyboard(seat, keyboard->wlr);
1059 }
1037 1060
1038 wl_list_remove(&keyboard->keyboard_key.link); 1061 wl_list_remove(&keyboard->keyboard_key.link);
1039 wl_signal_add(&wlr_device->keyboard->events.key, &keyboard->keyboard_key); 1062 wl_signal_add(&keyboard->wlr->events.key, &keyboard->keyboard_key);
1040 keyboard->keyboard_key.notify = handle_keyboard_key; 1063 keyboard->keyboard_key.notify = handle_keyboard_key;
1041 1064
1042 wl_list_remove(&keyboard->keyboard_modifiers.link); 1065 wl_list_remove(&keyboard->keyboard_modifiers.link);
1043 wl_signal_add(&wlr_device->keyboard->events.modifiers, 1066 wl_signal_add(&keyboard->wlr->events.modifiers,
1044 &keyboard->keyboard_modifiers); 1067 &keyboard->keyboard_modifiers);
1045 keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; 1068 keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
1046 1069
@@ -1057,12 +1080,11 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
1057 if (!keyboard) { 1080 if (!keyboard) {
1058 return; 1081 return;
1059 } 1082 }
1060 if (keyboard->seat_device->input_device->wlr_device->keyboard->group) { 1083 if (keyboard->wlr->group) {
1061 sway_keyboard_group_remove(keyboard); 1084 sway_keyboard_group_remove(keyboard);
1062 } 1085 }
1063 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; 1086 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
1064 struct sway_input_device *device = keyboard->seat_device->input_device; 1087 if (wlr_seat_get_keyboard(wlr_seat) == keyboard->wlr) {
1065 if (wlr_seat_get_keyboard(wlr_seat) == device->wlr_device->keyboard) {
1066 wlr_seat_set_keyboard(wlr_seat, NULL); 1088 wlr_seat_set_keyboard(wlr_seat, NULL);
1067 } 1089 }
1068 if (keyboard->keymap) { 1090 if (keyboard->keymap) {
diff --git a/sway/input/libinput.c b/sway/input/libinput.c
index 54520f9e..0266c7a9 100644
--- a/sway/input/libinput.c
+++ b/sway/input/libinput.c
@@ -1,5 +1,6 @@
1#include <float.h> 1#include <float.h>
2#include <libinput.h> 2#include <libinput.h>
3#include <libudev.h>
3#include <limits.h> 4#include <limits.h>
4#include <wlr/backend/libinput.h> 5#include <wlr/backend/libinput.h>
5#include "log.h" 6#include "log.h"
@@ -78,6 +79,16 @@ static bool set_accel_speed(struct libinput_device *device, double speed) {
78 return true; 79 return true;
79} 80}
80 81
82static bool set_rotation_angle(struct libinput_device *device, double angle) {
83 if (!libinput_device_config_rotation_is_available(device) ||
84 libinput_device_config_rotation_get_angle(device) == angle) {
85 return false;
86 }
87 sway_log(SWAY_DEBUG, "rotation_set_angle(%f)", angle);
88 log_status(libinput_device_config_rotation_set_angle(device, angle));
89 return true;
90}
91
81static bool set_accel_profile(struct libinput_device *device, 92static bool set_accel_profile(struct libinput_device *device,
82 enum libinput_config_accel_profile profile) { 93 enum libinput_config_accel_profile profile) {
83 if (!libinput_device_config_accel_is_available(device) || 94 if (!libinput_device_config_accel_is_available(device) ||
@@ -155,6 +166,18 @@ static bool set_scroll_button(struct libinput_device *dev, uint32_t button) {
155 return true; 166 return true;
156} 167}
157 168
169static bool set_scroll_button_lock(struct libinput_device *dev,
170 enum libinput_config_scroll_button_lock_state lock) {
171 uint32_t scroll = libinput_device_config_scroll_get_methods(dev);
172 if ((scroll & ~LIBINPUT_CONFIG_SCROLL_NO_SCROLL) == 0 ||
173 libinput_device_config_scroll_get_button_lock(dev) == lock) {
174 return false;
175 }
176 sway_log(SWAY_DEBUG, "scroll_set_button_lock(%" PRIu32 ")", lock);
177 log_status(libinput_device_config_scroll_set_button_lock(dev, lock));
178 return true;
179}
180
158static bool set_dwt(struct libinput_device *device, bool dwt) { 181static bool set_dwt(struct libinput_device *device, bool dwt) {
159 if (!libinput_device_config_dwt_is_available(device) || 182 if (!libinput_device_config_dwt_is_available(device) ||
160 libinput_device_config_dwt_get_enabled(device) == dwt) { 183 libinput_device_config_dwt_get_enabled(device) == dwt) {
@@ -165,6 +188,16 @@ static bool set_dwt(struct libinput_device *device, bool dwt) {
165 return true; 188 return true;
166} 189}
167 190
191static bool set_dwtp(struct libinput_device *device, bool dwtp) {
192 if (!libinput_device_config_dwtp_is_available(device) ||
193 libinput_device_config_dwtp_get_enabled(device) == dwtp) {
194 return false;
195 }
196 sway_log(SWAY_DEBUG, "dwtp_set_enabled(%d)", dwtp);
197 log_status(libinput_device_config_dwtp_set_enabled(device, dwtp));
198 return true;
199}
200
168static bool set_calibration_matrix(struct libinput_device *dev, float mat[6]) { 201static bool set_calibration_matrix(struct libinput_device *dev, float mat[6]) {
169 if (!libinput_device_config_calibration_has_matrix(dev)) { 202 if (!libinput_device_config_calibration_has_matrix(dev)) {
170 return false; 203 return false;
@@ -186,35 +219,38 @@ static bool set_calibration_matrix(struct libinput_device *dev, float mat[6]) {
186 return changed; 219 return changed;
187} 220}
188 221
189void sway_input_configure_libinput_device(struct sway_input_device *input_device) { 222static bool configure_send_events(struct libinput_device *device,
190 struct input_config *ic = input_device_get_config(input_device); 223 struct input_config *ic) {
191 if (!ic || !wlr_input_device_is_libinput(input_device->wlr_device)) {
192 return;
193 }
194
195 struct libinput_device *device =
196 wlr_libinput_get_device_handle(input_device->wlr_device);
197 sway_log(SWAY_DEBUG, "sway_input_configure_libinput_device('%s' on '%s')",
198 ic->identifier, input_device->identifier);
199
200 bool changed = false;
201 if (ic->mapped_to_output && 224 if (ic->mapped_to_output &&
225 strcmp("*", ic->mapped_to_output) != 0 &&
202 !output_by_name_or_id(ic->mapped_to_output)) { 226 !output_by_name_or_id(ic->mapped_to_output)) {
203 sway_log(SWAY_DEBUG, 227 sway_log(SWAY_DEBUG,
204 "%s '%s' is mapped to offline output '%s'; disabling input", 228 "%s '%s' is mapped to offline output '%s'; disabling input",
205 ic->input_type, ic->identifier, ic->mapped_to_output); 229 ic->input_type, ic->identifier, ic->mapped_to_output);
206 changed |= set_send_events(device, 230 return set_send_events(device, LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
207 LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
208 } else if (ic->send_events != INT_MIN) { 231 } else if (ic->send_events != INT_MIN) {
209 changed |= set_send_events(device, ic->send_events); 232 return set_send_events(device, ic->send_events);
210 } else { 233 } else {
211 // Have to reset to the default mode here, otherwise if ic->send_events 234 // Have to reset to the default mode here, otherwise if ic->send_events
212 // is unset and a mapped output just came online after being disabled, 235 // is unset and a mapped output just came online after being disabled,
213 // we'd remain stuck sending no events. 236 // we'd remain stuck sending no events.
214 changed |= set_send_events(device, 237 return set_send_events(device,
215 libinput_device_config_send_events_get_default_mode(device)); 238 libinput_device_config_send_events_get_default_mode(device));
216 } 239 }
240}
241
242bool sway_input_configure_libinput_device(struct sway_input_device *input_device) {
243 struct input_config *ic = input_device_get_config(input_device);
244 if (!ic || !wlr_input_device_is_libinput(input_device->wlr_device)) {
245 return false;
246 }
217 247
248 struct libinput_device *device =
249 wlr_libinput_get_device_handle(input_device->wlr_device);
250 sway_log(SWAY_DEBUG, "sway_input_configure_libinput_device('%s' on '%s')",
251 ic->identifier, input_device->identifier);
252
253 bool changed = configure_send_events(device, ic);
218 if (ic->tap != INT_MIN) { 254 if (ic->tap != INT_MIN) {
219 changed |= set_tap(device, ic->tap); 255 changed |= set_tap(device, ic->tap);
220 } 256 }
@@ -230,6 +266,9 @@ void sway_input_configure_libinput_device(struct sway_input_device *input_device
230 if (ic->pointer_accel != FLT_MIN) { 266 if (ic->pointer_accel != FLT_MIN) {
231 changed |= set_accel_speed(device, ic->pointer_accel); 267 changed |= set_accel_speed(device, ic->pointer_accel);
232 } 268 }
269 if (ic->rotation_angle != FLT_MIN) {
270 changed |= set_rotation_angle(device, ic->rotation_angle);
271 }
233 if (ic->accel_profile != INT_MIN) { 272 if (ic->accel_profile != INT_MIN) {
234 changed |= set_accel_profile(device, ic->accel_profile); 273 changed |= set_accel_profile(device, ic->accel_profile);
235 } 274 }
@@ -251,13 +290,33 @@ void sway_input_configure_libinput_device(struct sway_input_device *input_device
251 if (ic->scroll_button != INT_MIN) { 290 if (ic->scroll_button != INT_MIN) {
252 changed |= set_scroll_button(device, ic->scroll_button); 291 changed |= set_scroll_button(device, ic->scroll_button);
253 } 292 }
293 if (ic->scroll_button_lock != INT_MIN) {
294 changed |= set_scroll_button_lock(device, ic->scroll_button_lock);
295 }
254 if (ic->dwt != INT_MIN) { 296 if (ic->dwt != INT_MIN) {
255 changed |= set_dwt(device, ic->dwt); 297 changed |= set_dwt(device, ic->dwt);
256 } 298 }
299 if (ic->dwtp != INT_MIN) {
300 changed |= set_dwtp(device, ic->dwtp);
301 }
257 if (ic->calibration_matrix.configured) { 302 if (ic->calibration_matrix.configured) {
258 changed |= set_calibration_matrix(device, ic->calibration_matrix.matrix); 303 changed |= set_calibration_matrix(device, ic->calibration_matrix.matrix);
259 } 304 }
260 305
306 return changed;
307}
308
309void sway_input_configure_libinput_device_send_events(
310 struct sway_input_device *input_device) {
311 struct input_config *ic = input_device_get_config(input_device);
312 if (!ic || !wlr_input_device_is_libinput(input_device->wlr_device)) {
313 return;
314 }
315
316 struct libinput_device *device =
317 wlr_libinput_get_device_handle(input_device->wlr_device);
318 bool changed = configure_send_events(device, ic);
319
261 if (changed) { 320 if (changed) {
262 ipc_event_input("libinput_config", input_device); 321 ipc_event_input("libinput_config", input_device);
263 } 322 }
@@ -286,6 +345,8 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) {
286 libinput_device_config_tap_get_default_drag_lock_enabled(device)); 345 libinput_device_config_tap_get_default_drag_lock_enabled(device));
287 changed |= set_accel_speed(device, 346 changed |= set_accel_speed(device,
288 libinput_device_config_accel_get_default_speed(device)); 347 libinput_device_config_accel_get_default_speed(device));
348 changed |= set_rotation_angle(device,
349 libinput_device_config_rotation_get_default_angle(device));
289 changed |= set_accel_profile(device, 350 changed |= set_accel_profile(device,
290 libinput_device_config_accel_get_default_profile(device)); 351 libinput_device_config_accel_get_default_profile(device));
291 changed |= set_natural_scroll(device, 352 changed |= set_natural_scroll(device,
@@ -303,6 +364,8 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) {
303 libinput_device_config_scroll_get_default_button(device)); 364 libinput_device_config_scroll_get_default_button(device));
304 changed |= set_dwt(device, 365 changed |= set_dwt(device,
305 libinput_device_config_dwt_get_default_enabled(device)); 366 libinput_device_config_dwt_get_default_enabled(device));
367 changed |= set_dwtp(device,
368 libinput_device_config_dwtp_get_default_enabled(device));
306 369
307 float matrix[6]; 370 float matrix[6];
308 libinput_device_config_calibration_get_default_matrix(device, matrix); 371 libinput_device_config_calibration_get_default_matrix(device, matrix);
@@ -312,3 +375,32 @@ void sway_input_reset_libinput_device(struct sway_input_device *input_device) {
312 ipc_event_input("libinput_config", input_device); 375 ipc_event_input("libinput_config", input_device);
313 } 376 }
314} 377}
378
379bool sway_libinput_device_is_builtin(struct sway_input_device *sway_device) {
380 if (!wlr_input_device_is_libinput(sway_device->wlr_device)) {
381 return false;
382 }
383
384 struct libinput_device *device =
385 wlr_libinput_get_device_handle(sway_device->wlr_device);
386 struct udev_device *udev_device =
387 libinput_device_get_udev_device(device);
388 if (!udev_device) {
389 return false;
390 }
391
392 const char *id_path = udev_device_get_property_value(udev_device, "ID_PATH");
393 if (!id_path) {
394 return false;
395 }
396
397 const char prefix_platform[] = "platform-";
398 if (strncmp(id_path, prefix_platform, strlen(prefix_platform)) != 0) {
399 return false;
400 }
401
402 const char prefix_pci[] = "pci-";
403 const char infix_platform[] = "-platform-";
404 return (strncmp(id_path, prefix_pci, strlen(prefix_pci)) == 0) &&
405 strstr(id_path, infix_platform);
406}
diff --git a/sway/input/seat.c b/sway/input/seat.c
index 1f5865ee..75fea484 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -4,22 +4,25 @@
4#include <string.h> 4#include <string.h>
5#include <strings.h> 5#include <strings.h>
6#include <time.h> 6#include <time.h>
7#include <wlr/config.h>
7#include <wlr/types/wlr_cursor.h> 8#include <wlr/types/wlr_cursor.h>
8#include <wlr/types/wlr_data_device.h> 9#include <wlr/types/wlr_data_device.h>
9#include <wlr/types/wlr_idle.h> 10#include <wlr/types/wlr_idle_notify_v1.h>
10#include <wlr/types/wlr_keyboard_group.h> 11#include <wlr/types/wlr_keyboard_group.h>
11#include <wlr/types/wlr_output_layout.h> 12#include <wlr/types/wlr_output_layout.h>
12#include <wlr/types/wlr_primary_selection.h> 13#include <wlr/types/wlr_primary_selection.h>
13#include <wlr/types/wlr_tablet_v2.h> 14#include <wlr/types/wlr_tablet_v2.h>
15#include <wlr/types/wlr_touch.h>
14#include <wlr/types/wlr_xcursor_manager.h> 16#include <wlr/types/wlr_xcursor_manager.h>
15#include "config.h" 17#include "config.h"
16#include "list.h" 18#include "list.h"
17#include "log.h" 19#include "log.h"
18#include "sway/config.h" 20#include "sway/config.h"
19#include "sway/desktop.h" 21#include "sway/scene_descriptor.h"
20#include "sway/input/cursor.h" 22#include "sway/input/cursor.h"
21#include "sway/input/input-manager.h" 23#include "sway/input/input-manager.h"
22#include "sway/input/keyboard.h" 24#include "sway/input/keyboard.h"
25#include "sway/input/libinput.h"
23#include "sway/input/seat.h" 26#include "sway/input/seat.h"
24#include "sway/input/switch.h" 27#include "sway/input/switch.h"
25#include "sway/input/tablet.h" 28#include "sway/input/tablet.h"
@@ -41,6 +44,7 @@ static void seat_device_destroy(struct sway_seat_device *seat_device) {
41 sway_keyboard_destroy(seat_device->keyboard); 44 sway_keyboard_destroy(seat_device->keyboard);
42 sway_tablet_destroy(seat_device->tablet); 45 sway_tablet_destroy(seat_device->tablet);
43 sway_tablet_pad_destroy(seat_device->tablet_pad); 46 sway_tablet_pad_destroy(seat_device->tablet_pad);
47 sway_switch_destroy(seat_device->switch_device);
44 wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor, 48 wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor,
45 seat_device->input_device->wlr_device); 49 seat_device->input_device->wlr_device);
46 wl_list_remove(&seat_device->link); 50 wl_list_remove(&seat_device->link);
@@ -50,6 +54,16 @@ static void seat_device_destroy(struct sway_seat_device *seat_device) {
50static void seat_node_destroy(struct sway_seat_node *seat_node) { 54static void seat_node_destroy(struct sway_seat_node *seat_node) {
51 wl_list_remove(&seat_node->destroy.link); 55 wl_list_remove(&seat_node->destroy.link);
52 wl_list_remove(&seat_node->link); 56 wl_list_remove(&seat_node->link);
57
58 /*
59 * This is the only time we remove items from the focus stack without
60 * immediately re-adding them. If we just removed the last thing,
61 * mark that nothing has focus anymore.
62 */
63 if (wl_list_empty(&seat_node->seat->focus_stack)) {
64 seat_node->seat->has_focus = false;
65 }
66
53 free(seat_node); 67 free(seat_node);
54} 68}
55 69
@@ -78,6 +92,7 @@ void seat_destroy(struct sway_seat *seat) {
78 for (int i = 0; i < seat->deferred_bindings->length; i++) { 92 for (int i = 0; i < seat->deferred_bindings->length; i++) {
79 free_sway_binding(seat->deferred_bindings->items[i]); 93 free_sway_binding(seat->deferred_bindings->items[i]);
80 } 94 }
95 wlr_scene_node_destroy(&seat->scene_tree->node);
81 list_free(seat->deferred_bindings); 96 list_free(seat->deferred_bindings);
82 free(seat->prev_workspace_name); 97 free(seat->prev_workspace_name);
83 free(seat); 98 free(seat);
@@ -85,21 +100,10 @@ void seat_destroy(struct sway_seat *seat) {
85 100
86void seat_idle_notify_activity(struct sway_seat *seat, 101void seat_idle_notify_activity(struct sway_seat *seat,
87 enum sway_input_idle_source source) { 102 enum sway_input_idle_source source) {
88 uint32_t mask = seat->idle_inhibit_sources; 103 if ((source & seat->idle_inhibit_sources) == 0) {
89 struct wlr_idle_timeout *timeout; 104 return;
90 int ntimers = 0, nidle = 0;
91 wl_list_for_each(timeout, &server.idle->idle_timers, link) {
92 ++ntimers;
93 if (timeout->idle_state) {
94 ++nidle;
95 }
96 }
97 if (nidle == ntimers) {
98 mask = seat->idle_wake_sources;
99 }
100 if ((source & mask) > 0) {
101 wlr_idle_notify_activity(server.idle, seat->wlr_seat);
102 } 105 }
106 wlr_idle_notifier_v1_notify_activity(server.idle_notifier_v1, seat->wlr_seat);
103} 107}
104 108
105/** 109/**
@@ -129,7 +133,7 @@ static struct sway_keyboard *sway_keyboard_for_wlr_keyboard(
129 if (input_device->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { 133 if (input_device->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) {
130 continue; 134 continue;
131 } 135 }
132 if (input_device->wlr_device->keyboard == wlr_keyboard) { 136 if (input_device->wlr_device == &wlr_keyboard->base) {
133 return seat_device->keyboard; 137 return seat_device->keyboard;
134 } 138 }
135 } 139 }
@@ -137,7 +141,7 @@ static struct sway_keyboard *sway_keyboard_for_wlr_keyboard(
137 wl_list_for_each(group, &seat->keyboard_groups, link) { 141 wl_list_for_each(group, &seat->keyboard_groups, link) {
138 struct sway_input_device *input_device = 142 struct sway_input_device *input_device =
139 group->seat_device->input_device; 143 group->seat_device->input_device;
140 if (input_device->wlr_device->keyboard == wlr_keyboard) { 144 if (input_device->wlr_device == &wlr_keyboard->base) {
141 return group->seat_device->keyboard; 145 return group->seat_device->keyboard;
142 } 146 }
143 } 147 }
@@ -161,11 +165,11 @@ static void seat_keyboard_notify_enter(struct sway_seat *seat,
161 state->pressed_keycodes, state->npressed, &keyboard->modifiers); 165 state->pressed_keycodes, state->npressed, &keyboard->modifiers);
162} 166}
163 167
164static void seat_tablet_pads_notify_enter(struct sway_seat *seat, 168static void seat_tablet_pads_set_focus(struct sway_seat *seat,
165 struct wlr_surface *surface) { 169 struct wlr_surface *surface) {
166 struct sway_seat_device *seat_device; 170 struct sway_seat_device *seat_device;
167 wl_list_for_each(seat_device, &seat->devices, link) { 171 wl_list_for_each(seat_device, &seat->devices, link) {
168 sway_tablet_pad_notify_enter(seat_device->tablet_pad, surface); 172 sway_tablet_pad_set_focus(seat_device->tablet_pad, surface);
169 } 173 }
170} 174}
171 175
@@ -189,7 +193,7 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) {
189#endif 193#endif
190 194
191 seat_keyboard_notify_enter(seat, view->surface); 195 seat_keyboard_notify_enter(seat, view->surface);
192 seat_tablet_pads_notify_enter(seat, view->surface); 196 seat_tablet_pads_set_focus(seat, view->surface);
193 sway_input_method_relay_set_focus(&seat->im_relay, view->surface); 197 sway_input_method_relay_set_focus(&seat->im_relay, view->surface);
194 198
195 struct wlr_pointer_constraint_v1 *constraint = 199 struct wlr_pointer_constraint_v1 *constraint =
@@ -209,14 +213,13 @@ void seat_for_each_node(struct sway_seat *seat,
209 213
210struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, 214struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
211 struct sway_node *ancestor) { 215 struct sway_node *ancestor) {
212 if (ancestor->type == N_CONTAINER && ancestor->sway_container->view) { 216 if (node_is_view(ancestor)) {
213 return ancestor->sway_container; 217 return ancestor->sway_container;
214 } 218 }
215 struct sway_seat_node *current; 219 struct sway_seat_node *current;
216 wl_list_for_each(current, &seat->focus_stack, link) { 220 wl_list_for_each(current, &seat->focus_stack, link) {
217 struct sway_node *node = current->node; 221 struct sway_node *node = current->node;
218 if (node->type == N_CONTAINER && node->sway_container->view && 222 if (node_is_view(node) && node_has_ancestor(node, ancestor)) {
219 node_has_ancestor(node, ancestor)) {
220 return node->sway_container; 223 return node->sway_container;
221 } 224 }
222 } 225 }
@@ -235,7 +238,7 @@ static void handle_seat_node_destroy(struct wl_listener *listener, void *data) {
235 seat_node_destroy(seat_node); 238 seat_node_destroy(seat_node);
236 // If an unmanaged or layer surface is focused when an output gets 239 // If an unmanaged or layer surface is focused when an output gets
237 // disabled and an empty workspace on the output was focused by the 240 // disabled and an empty workspace on the output was focused by the
238 // seat, the seat needs to refocus it's focus inactive to update the 241 // seat, the seat needs to refocus its focus inactive to update the
239 // value of seat->workspace. 242 // value of seat->workspace.
240 if (seat->workspace == node->sway_workspace) { 243 if (seat->workspace == node->sway_workspace) {
241 struct sway_node *node = seat_get_focus_inactive(seat, &root->node); 244 struct sway_node *node = seat_get_focus_inactive(seat, &root->node);
@@ -309,8 +312,8 @@ static void handle_seat_node_destroy(struct wl_listener *listener, void *data) {
309 // Setting focus_inactive 312 // Setting focus_inactive
310 focus = seat_get_focus_inactive(seat, &root->node); 313 focus = seat_get_focus_inactive(seat, &root->node);
311 seat_set_raw_focus(seat, next_focus); 314 seat_set_raw_focus(seat, next_focus);
312 if (focus->type == N_CONTAINER && focus->sway_container->workspace) { 315 if (focus->type == N_CONTAINER && focus->sway_container->pending.workspace) {
313 seat_set_raw_focus(seat, &focus->sway_container->workspace->node); 316 seat_set_raw_focus(seat, &focus->sway_container->pending.workspace->node);
314 } 317 }
315 seat_set_raw_focus(seat, focus); 318 seat_set_raw_focus(seat, focus);
316 } 319 }
@@ -351,25 +354,15 @@ static void handle_new_node(struct wl_listener *listener, void *data) {
351 seat_node_from_node(seat, node); 354 seat_node_from_node(seat, node);
352} 355}
353 356
354static void drag_icon_damage_whole(struct sway_drag_icon *icon) { 357static void drag_icon_update_position(struct sway_seat *seat, struct wlr_scene_node *node) {
355 if (!icon->wlr_drag_icon->mapped) { 358 struct wlr_drag_icon *wlr_icon = scene_descriptor_try_get(node, SWAY_SCENE_DESC_DRAG_ICON);
356 return;
357 }
358 desktop_damage_surface(icon->wlr_drag_icon->surface, icon->x, icon->y, true);
359}
360
361void drag_icon_update_position(struct sway_drag_icon *icon) {
362 drag_icon_damage_whole(icon);
363
364 struct wlr_drag_icon *wlr_icon = icon->wlr_drag_icon;
365 struct sway_seat *seat = icon->seat;
366 struct wlr_cursor *cursor = seat->cursor->cursor; 359 struct wlr_cursor *cursor = seat->cursor->cursor;
360
367 switch (wlr_icon->drag->grab_type) { 361 switch (wlr_icon->drag->grab_type) {
368 case WLR_DRAG_GRAB_KEYBOARD: 362 case WLR_DRAG_GRAB_KEYBOARD:
369 return; 363 return;
370 case WLR_DRAG_GRAB_KEYBOARD_POINTER: 364 case WLR_DRAG_GRAB_KEYBOARD_POINTER:
371 icon->x = cursor->x; 365 wlr_scene_node_set_position(node, cursor->x, cursor->y);
372 icon->y = cursor->y;
373 break; 366 break;
374 case WLR_DRAG_GRAB_KEYBOARD_TOUCH:; 367 case WLR_DRAG_GRAB_KEYBOARD_TOUCH:;
375 struct wlr_touch_point *point = 368 struct wlr_touch_point *point =
@@ -377,39 +370,15 @@ void drag_icon_update_position(struct sway_drag_icon *icon) {
377 if (point == NULL) { 370 if (point == NULL) {
378 return; 371 return;
379 } 372 }
380 icon->x = seat->touch_x; 373 wlr_scene_node_set_position(node, seat->touch_x, seat->touch_y);
381 icon->y = seat->touch_y;
382 } 374 }
383
384 drag_icon_damage_whole(icon);
385}
386
387static void drag_icon_handle_surface_commit(struct wl_listener *listener,
388 void *data) {
389 struct sway_drag_icon *icon =
390 wl_container_of(listener, icon, surface_commit);
391 drag_icon_update_position(icon);
392}
393
394static void drag_icon_handle_map(struct wl_listener *listener, void *data) {
395 struct sway_drag_icon *icon = wl_container_of(listener, icon, map);
396 drag_icon_damage_whole(icon);
397}
398
399static void drag_icon_handle_unmap(struct wl_listener *listener, void *data) {
400 struct sway_drag_icon *icon = wl_container_of(listener, icon, unmap);
401 drag_icon_damage_whole(icon);
402} 375}
403 376
404static void drag_icon_handle_destroy(struct wl_listener *listener, void *data) { 377void drag_icons_update_position(struct sway_seat *seat) {
405 struct sway_drag_icon *icon = wl_container_of(listener, icon, destroy); 378 struct wlr_scene_node *node;
406 icon->wlr_drag_icon->data = NULL; 379 wl_list_for_each(node, &seat->drag_icons->children, link) {
407 wl_list_remove(&icon->link); 380 drag_icon_update_position(seat, node);
408 wl_list_remove(&icon->surface_commit.link); 381 }
409 wl_list_remove(&icon->unmap.link);
410 wl_list_remove(&icon->map.link);
411 wl_list_remove(&icon->destroy.link);
412 free(icon);
413} 382}
414 383
415static void drag_handle_destroy(struct wl_listener *listener, void *data) { 384static void drag_handle_destroy(struct wl_listener *listener, void *data) {
@@ -481,27 +450,20 @@ static void handle_start_drag(struct wl_listener *listener, void *data) {
481 450
482 struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; 451 struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon;
483 if (wlr_drag_icon != NULL) { 452 if (wlr_drag_icon != NULL) {
484 struct sway_drag_icon *icon = calloc(1, sizeof(struct sway_drag_icon)); 453 struct wlr_scene_tree *tree = wlr_scene_drag_icon_create(seat->drag_icons, wlr_drag_icon);
485 if (icon == NULL) { 454 if (!tree) {
486 sway_log(SWAY_ERROR, "Allocation failed"); 455 sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene tree");
487 return; 456 return;
488 } 457 }
489 icon->seat = seat;
490 icon->wlr_drag_icon = wlr_drag_icon;
491 wlr_drag_icon->data = icon;
492
493 icon->surface_commit.notify = drag_icon_handle_surface_commit;
494 wl_signal_add(&wlr_drag_icon->surface->events.commit, &icon->surface_commit);
495 icon->unmap.notify = drag_icon_handle_unmap;
496 wl_signal_add(&wlr_drag_icon->events.unmap, &icon->unmap);
497 icon->map.notify = drag_icon_handle_map;
498 wl_signal_add(&wlr_drag_icon->events.map, &icon->map);
499 icon->destroy.notify = drag_icon_handle_destroy;
500 wl_signal_add(&wlr_drag_icon->events.destroy, &icon->destroy);
501 458
502 wl_list_insert(&root->drag_icons, &icon->link); 459 if (!scene_descriptor_assign(&tree->node, SWAY_SCENE_DESC_DRAG_ICON,
460 wlr_drag_icon)) {
461 sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene descriptor");
462 wlr_scene_node_destroy(&tree->node);
463 return;
464 }
503 465
504 drag_icon_update_position(icon); 466 drag_icon_update_position(seat, &tree->node);
505 } 467 }
506 seatop_begin_default(seat); 468 seatop_begin_default(seat);
507} 469}
@@ -548,8 +510,18 @@ struct sway_seat *seat_create(const char *seat_name) {
548 return NULL; 510 return NULL;
549 } 511 }
550 512
513 bool failed = false;
514 seat->scene_tree = alloc_scene_tree(root->layers.seat, &failed);
515 seat->drag_icons = alloc_scene_tree(seat->scene_tree, &failed);
516 if (failed) {
517 wlr_scene_node_destroy(&seat->scene_tree->node);
518 free(seat);
519 return NULL;
520 }
521
551 seat->wlr_seat = wlr_seat_create(server.wl_display, seat_name); 522 seat->wlr_seat = wlr_seat_create(server.wl_display, seat_name);
552 if (!sway_assert(seat->wlr_seat, "could not allocate seat")) { 523 if (!sway_assert(seat->wlr_seat, "could not allocate seat")) {
524 wlr_scene_node_destroy(&seat->scene_tree->node);
553 free(seat); 525 free(seat);
554 return NULL; 526 return NULL;
555 } 527 }
@@ -557,6 +529,7 @@ struct sway_seat *seat_create(const char *seat_name) {
557 529
558 seat->cursor = sway_cursor_create(seat); 530 seat->cursor = sway_cursor_create(seat);
559 if (!seat->cursor) { 531 if (!seat->cursor) {
532 wlr_scene_node_destroy(&seat->scene_tree->node);
560 wlr_seat_destroy(seat->wlr_seat); 533 wlr_seat_destroy(seat->wlr_seat);
561 free(seat); 534 free(seat);
562 return NULL; 535 return NULL;
@@ -653,7 +626,7 @@ static void seat_update_capabilities(struct sway_seat *seat) {
653 } else { 626 } else {
654 wlr_seat_set_capabilities(seat->wlr_seat, caps); 627 wlr_seat_set_capabilities(seat->wlr_seat, caps);
655 if ((previous_caps & WL_SEAT_CAPABILITY_POINTER) == 0) { 628 if ((previous_caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
656 cursor_set_image(seat->cursor, "left_ptr", NULL); 629 cursor_set_image(seat->cursor, "default", NULL);
657 } 630 }
658 } 631 }
659} 632}
@@ -666,12 +639,55 @@ static void seat_reset_input_config(struct sway_seat *seat,
666 sway_device->input_device->wlr_device, NULL); 639 sway_device->input_device->wlr_device, NULL);
667} 640}
668 641
669static void seat_apply_input_config(struct sway_seat *seat, 642static bool has_prefix(const char *str, const char *prefix) {
643 return strncmp(str, prefix, strlen(prefix)) == 0;
644}
645
646/**
647 * Get the name of the built-in output, if any. Returns NULL if there isn't
648 * exactly one built-in output.
649 */
650static const char *get_builtin_output_name(void) {
651 const char *match = NULL;
652 for (int i = 0; i < root->outputs->length; ++i) {
653 struct sway_output *output = root->outputs->items[i];
654 const char *name = output->wlr_output->name;
655 if (has_prefix(name, "eDP-") || has_prefix(name, "LVDS-") ||
656 has_prefix(name, "DSI-")) {
657 if (match != NULL) {
658 return NULL;
659 }
660 match = name;
661 }
662 }
663 return match;
664}
665
666static bool is_touch_or_tablet_tool(struct sway_seat_device *seat_device) {
667 switch (seat_device->input_device->wlr_device->type) {
668 case WLR_INPUT_DEVICE_TOUCH:
669 case WLR_INPUT_DEVICE_TABLET_TOOL:
670 return true;
671 default:
672 return false;
673 }
674}
675
676static void seat_apply_input_mapping(struct sway_seat *seat,
670 struct sway_seat_device *sway_device) { 677 struct sway_seat_device *sway_device) {
671 struct input_config *ic = 678 struct input_config *ic =
672 input_device_get_config(sway_device->input_device); 679 input_device_get_config(sway_device->input_device);
673 680
674 sway_log(SWAY_DEBUG, "Applying input config to %s", 681 switch (sway_device->input_device->wlr_device->type) {
682 case WLR_INPUT_DEVICE_POINTER:
683 case WLR_INPUT_DEVICE_TOUCH:
684 case WLR_INPUT_DEVICE_TABLET_TOOL:
685 break;
686 default:
687 return; // these devices don't support mappings
688 }
689
690 sway_log(SWAY_DEBUG, "Applying input mapping to %s",
675 sway_device->input_device->identifier); 691 sway_device->input_device->identifier);
676 692
677 const char *mapped_to_output = ic == NULL ? NULL : ic->mapped_to_output; 693 const char *mapped_to_output = ic == NULL ? NULL : ic->mapped_to_output;
@@ -680,8 +696,38 @@ static void seat_apply_input_config(struct sway_seat *seat,
680 ic == NULL ? MAPPED_TO_DEFAULT : ic->mapped_to; 696 ic == NULL ? MAPPED_TO_DEFAULT : ic->mapped_to;
681 697
682 switch (mapped_to) { 698 switch (mapped_to) {
683 case MAPPED_TO_DEFAULT: 699 case MAPPED_TO_DEFAULT:;
684 mapped_to_output = sway_device->input_device->wlr_device->output_name; 700 /*
701 * If the wlroots backend provides an output name, use that.
702 *
703 * Otherwise, try to map built-in touch and pointer devices to the
704 * built-in output.
705 */
706 struct wlr_input_device *dev = sway_device->input_device->wlr_device;
707 switch (dev->type) {
708 case WLR_INPUT_DEVICE_POINTER:
709 mapped_to_output = wlr_pointer_from_input_device(dev)->output_name;
710 break;
711 case WLR_INPUT_DEVICE_TOUCH:
712 mapped_to_output = wlr_touch_from_input_device(dev)->output_name;
713 break;
714 default:
715 mapped_to_output = NULL;
716 break;
717 }
718#if WLR_HAS_LIBINPUT_BACKEND
719 if (mapped_to_output == NULL && is_touch_or_tablet_tool(sway_device) &&
720 sway_libinput_device_is_builtin(sway_device->input_device)) {
721 mapped_to_output = get_builtin_output_name();
722 if (mapped_to_output) {
723 sway_log(SWAY_DEBUG, "Auto-detected output '%s' for device '%s'",
724 mapped_to_output, sway_device->input_device->identifier);
725 }
726 }
727#else
728 (void)is_touch_or_tablet_tool;
729 (void)get_builtin_output_name;
730#endif
685 if (mapped_to_output == NULL) { 731 if (mapped_to_output == NULL) {
686 return; 732 return;
687 } 733 }
@@ -725,12 +771,9 @@ static void seat_apply_input_config(struct sway_seat *seat,
725 771
726static void seat_configure_pointer(struct sway_seat *seat, 772static void seat_configure_pointer(struct sway_seat *seat,
727 struct sway_seat_device *sway_device) { 773 struct sway_seat_device *sway_device) {
728 if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { 774 seat_configure_xcursor(seat);
729 seat_configure_xcursor(seat);
730 }
731 wlr_cursor_attach_input_device(seat->cursor->cursor, 775 wlr_cursor_attach_input_device(seat->cursor->cursor,
732 sway_device->input_device->wlr_device); 776 sway_device->input_device->wlr_device);
733 seat_apply_input_config(seat, sway_device);
734 wl_event_source_timer_update( 777 wl_event_source_timer_update(
735 seat->cursor->hide_source, cursor_get_timeout(seat->cursor)); 778 seat->cursor->hide_source, cursor_get_timeout(seat->cursor));
736} 779}
@@ -741,13 +784,22 @@ static void seat_configure_keyboard(struct sway_seat *seat,
741 sway_keyboard_create(seat, seat_device); 784 sway_keyboard_create(seat, seat_device);
742 } 785 }
743 sway_keyboard_configure(seat_device->keyboard); 786 sway_keyboard_configure(seat_device->keyboard);
744 wlr_seat_set_keyboard(seat->wlr_seat, 787
745 seat_device->input_device->wlr_device); 788 // We only need to update the current keyboard, as the rest will be updated
746 struct sway_node *focus = seat_get_focus(seat); 789 // as they are activated.
747 if (focus && node_is_view(focus)) { 790 struct wlr_keyboard *wlr_keyboard =
748 // force notify reenter to pick up the new configuration 791 wlr_keyboard_from_input_device(seat_device->input_device->wlr_device);
792 struct wlr_keyboard *current_keyboard = seat->wlr_seat->keyboard_state.keyboard;
793 if (wlr_keyboard != current_keyboard) {
794 return;
795 }
796
797 // force notify reenter to pick up the new configuration. This reuses
798 // the current focused surface to avoid breaking input grabs.
799 struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface;
800 if (surface) {
749 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); 801 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat);
750 seat_keyboard_notify_enter(seat, focus->sway_container->view->surface); 802 seat_keyboard_notify_enter(seat, surface);
751 } 803 }
752} 804}
753 805
@@ -756,7 +808,6 @@ static void seat_configure_switch(struct sway_seat *seat,
756 if (!seat_device->switch_device) { 808 if (!seat_device->switch_device) {
757 sway_switch_create(seat, seat_device); 809 sway_switch_create(seat, seat_device);
758 } 810 }
759 seat_apply_input_config(seat, seat_device);
760 sway_switch_configure(seat_device->switch_device); 811 sway_switch_configure(seat_device->switch_device);
761} 812}
762 813
@@ -764,7 +815,6 @@ static void seat_configure_touch(struct sway_seat *seat,
764 struct sway_seat_device *sway_device) { 815 struct sway_seat_device *sway_device) {
765 wlr_cursor_attach_input_device(seat->cursor->cursor, 816 wlr_cursor_attach_input_device(seat->cursor->cursor,
766 sway_device->input_device->wlr_device); 817 sway_device->input_device->wlr_device);
767 seat_apply_input_config(seat, sway_device);
768} 818}
769 819
770static void seat_configure_tablet_tool(struct sway_seat *seat, 820static void seat_configure_tablet_tool(struct sway_seat *seat,
@@ -775,7 +825,6 @@ static void seat_configure_tablet_tool(struct sway_seat *seat,
775 sway_configure_tablet(sway_device->tablet); 825 sway_configure_tablet(sway_device->tablet);
776 wlr_cursor_attach_input_device(seat->cursor->cursor, 826 wlr_cursor_attach_input_device(seat->cursor->cursor,
777 sway_device->input_device->wlr_device); 827 sway_device->input_device->wlr_device);
778 seat_apply_input_config(seat, sway_device);
779} 828}
780 829
781static void seat_configure_tablet_pad(struct sway_seat *seat, 830static void seat_configure_tablet_pad(struct sway_seat *seat,
@@ -832,6 +881,18 @@ void seat_configure_device(struct sway_seat *seat,
832 seat_configure_tablet_pad(seat, seat_device); 881 seat_configure_tablet_pad(seat, seat_device);
833 break; 882 break;
834 } 883 }
884
885 seat_apply_input_mapping(seat, seat_device);
886}
887
888void seat_configure_device_mapping(struct sway_seat *seat,
889 struct sway_input_device *input_device) {
890 struct sway_seat_device *seat_device = seat_get_device(seat, input_device);
891 if (!seat_device) {
892 return;
893 }
894
895 seat_apply_input_mapping(seat, seat_device);
835} 896}
836 897
837void seat_reset_device(struct sway_seat *seat, 898void seat_reset_device(struct sway_seat *seat,
@@ -948,7 +1009,7 @@ void seat_configure_xcursor(struct sway_seat *seat) {
948 1009
949 wlr_xcursor_manager_load(server.xwayland.xcursor_manager, 1); 1010 wlr_xcursor_manager_load(server.xwayland.xcursor_manager, 1);
950 struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( 1011 struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
951 server.xwayland.xcursor_manager, "left_ptr", 1); 1012 server.xwayland.xcursor_manager, "default", 1);
952 if (xcursor != NULL) { 1013 if (xcursor != NULL) {
953 struct wlr_xcursor_image *image = xcursor->images[0]; 1014 struct wlr_xcursor_image *image = xcursor->images[0];
954 wlr_xwayland_set_cursor( 1015 wlr_xwayland_set_cursor(
@@ -974,32 +1035,35 @@ void seat_configure_xcursor(struct sway_seat *seat) {
974 sway_log(SWAY_ERROR, 1035 sway_log(SWAY_ERROR,
975 "Cannot create XCursor manager for theme '%s'", cursor_theme); 1036 "Cannot create XCursor manager for theme '%s'", cursor_theme);
976 } 1037 }
977 }
978 1038
979 for (int i = 0; i < root->outputs->length; ++i) { 1039
980 struct sway_output *sway_output = root->outputs->items[i]; 1040 for (int i = 0; i < root->outputs->length; ++i) {
981 struct wlr_output *output = sway_output->wlr_output; 1041 struct sway_output *sway_output = root->outputs->items[i];
982 bool result = 1042 struct wlr_output *output = sway_output->wlr_output;
983 wlr_xcursor_manager_load(seat->cursor->xcursor_manager, 1043 bool result =
984 output->scale); 1044 wlr_xcursor_manager_load(seat->cursor->xcursor_manager,
985 if (!result) { 1045 output->scale);
986 sway_log(SWAY_ERROR, 1046 if (!result) {
987 "Cannot load xcursor theme for output '%s' with scale %f", 1047 sway_log(SWAY_ERROR,
988 output->name, output->scale); 1048 "Cannot load xcursor theme for output '%s' with scale %f",
1049 output->name, output->scale);
1050 }
989 } 1051 }
990 }
991 1052
992 // Reset the cursor so that we apply it to outputs that just appeared 1053 // Reset the cursor so that we apply it to outputs that just appeared
993 cursor_set_image(seat->cursor, NULL, NULL); 1054 cursor_set_image(seat->cursor, NULL, NULL);
994 cursor_set_image(seat->cursor, "left_ptr", NULL); 1055 cursor_set_image(seat->cursor, "default", NULL);
995 wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x, 1056 wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
996 seat->cursor->cursor->y); 1057 seat->cursor->cursor->y);
1058 }
997} 1059}
998 1060
999bool seat_is_input_allowed(struct sway_seat *seat, 1061bool seat_is_input_allowed(struct sway_seat *seat,
1000 struct wlr_surface *surface) { 1062 struct wlr_surface *surface) {
1001 struct wl_client *client = wl_resource_get_client(surface->resource); 1063 if (server.session_lock.lock) {
1002 return !seat->exclusive_client || seat->exclusive_client == client; 1064 return sway_session_lock_has_surface(server.session_lock.lock, surface);
1065 }
1066 return true;
1003} 1067}
1004 1068
1005static void send_unfocus(struct sway_container *con, void *data) { 1069static void send_unfocus(struct sway_container *con, void *data) {
@@ -1058,15 +1122,7 @@ void seat_set_raw_focus(struct sway_seat *seat, struct sway_node *node) {
1058 } 1122 }
1059} 1123}
1060 1124
1061void seat_set_focus(struct sway_seat *seat, struct sway_node *node) { 1125static void seat_set_workspace_focus(struct sway_seat *seat, struct sway_node *node) {
1062 if (seat->focused_layer) {
1063 struct wlr_layer_surface_v1 *layer = seat->focused_layer;
1064 seat_set_focus_layer(seat, NULL);
1065 seat_set_focus(seat, node);
1066 seat_set_focus_layer(seat, layer);
1067 return;
1068 }
1069
1070 struct sway_node *last_focus = seat_get_focus(seat); 1126 struct sway_node *last_focus = seat_get_focus(seat);
1071 if (last_focus == node) { 1127 if (last_focus == node) {
1072 return; 1128 return;
@@ -1086,30 +1142,19 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1086 } 1142 }
1087 1143
1088 struct sway_workspace *new_workspace = node->type == N_WORKSPACE ? 1144 struct sway_workspace *new_workspace = node->type == N_WORKSPACE ?
1089 node->sway_workspace : node->sway_container->workspace; 1145 node->sway_workspace : node->sway_container->pending.workspace;
1090 struct sway_container *container = node->type == N_CONTAINER ? 1146 struct sway_container *container = node->type == N_CONTAINER ?
1091 node->sway_container : NULL; 1147 node->sway_container : NULL;
1092 1148
1093 // Deny setting focus to a view which is hidden by a fullscreen container 1149 // Deny setting focus to a view which is hidden by a fullscreen container or global
1094 if (new_workspace && new_workspace->fullscreen && container && 1150 if (container && container_obstructing_fullscreen_container(container)) {
1095 !container_is_fullscreen_or_child(container)) { 1151 return;
1096 // Unless it's a transient container
1097 if (!container_is_transient_for(container, new_workspace->fullscreen)) {
1098 return;
1099 }
1100 } 1152 }
1153
1101 // Deny setting focus to a workspace node when using fullscreen global 1154 // Deny setting focus to a workspace node when using fullscreen global
1102 if (root->fullscreen_global && !container && new_workspace) { 1155 if (root->fullscreen_global && !container && new_workspace) {
1103 return; 1156 return;
1104 } 1157 }
1105 // Deny setting focus to a view which is hidden by a fullscreen global
1106 if (root->fullscreen_global && container != root->fullscreen_global &&
1107 !container_has_ancestor(container, root->fullscreen_global)) {
1108 // Unless it's a transient container
1109 if (!container_is_transient_for(container, root->fullscreen_global)) {
1110 return;
1111 }
1112 }
1113 1158
1114 struct sway_output *new_output = 1159 struct sway_output *new_output =
1115 new_workspace ? new_workspace->output : NULL; 1160 new_workspace ? new_workspace->output : NULL;
@@ -1135,10 +1180,10 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1135 // Put the container parents on the focus stack, then the workspace, then 1180 // Put the container parents on the focus stack, then the workspace, then
1136 // the focused container. 1181 // the focused container.
1137 if (container) { 1182 if (container) {
1138 struct sway_container *parent = container->parent; 1183 struct sway_container *parent = container->pending.parent;
1139 while (parent) { 1184 while (parent) {
1140 seat_set_raw_focus(seat, &parent->node); 1185 seat_set_raw_focus(seat, &parent->node);
1141 parent = parent->parent; 1186 parent = parent->pending.parent;
1142 } 1187 }
1143 } 1188 }
1144 if (new_workspace) { 1189 if (new_workspace) {
@@ -1210,6 +1255,24 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1210 } 1255 }
1211} 1256}
1212 1257
1258void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1259 // Prevents the layer from losing focus if it has keyboard exclusivity
1260 if (seat->has_exclusive_layer) {
1261 struct wlr_layer_surface_v1 *layer = seat->focused_layer;
1262 seat_set_focus_layer(seat, NULL);
1263 seat_set_workspace_focus(seat, node);
1264 seat_set_focus_layer(seat, layer);
1265 } else if (seat->focused_layer) {
1266 seat_set_focus_layer(seat, NULL);
1267 seat_set_workspace_focus(seat, node);
1268 } else {
1269 seat_set_workspace_focus(seat, node);
1270 }
1271 if (server.session_lock.lock) {
1272 seat_set_focus_surface(seat, server.session_lock.lock->focused, false);
1273 }
1274}
1275
1213void seat_set_focus_container(struct sway_seat *seat, 1276void seat_set_focus_container(struct sway_seat *seat,
1214 struct sway_container *con) { 1277 struct sway_container *con) {
1215 seat_set_focus(seat, con ? &con->node : NULL); 1278 seat_set_focus(seat, con ? &con->node : NULL);
@@ -1234,7 +1297,8 @@ void seat_set_focus_surface(struct sway_seat *seat,
1234 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); 1297 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat);
1235 } 1298 }
1236 1299
1237 seat_tablet_pads_notify_enter(seat, surface); 1300 sway_input_method_relay_set_focus(&seat->im_relay, surface);
1301 seat_tablet_pads_set_focus(seat, surface);
1238} 1302}
1239 1303
1240void seat_set_focus_layer(struct sway_seat *seat, 1304void seat_set_focus_layer(struct sway_seat *seat,
@@ -1248,28 +1312,23 @@ void seat_set_focus_layer(struct sway_seat *seat,
1248 seat_set_focus(seat, previous); 1312 seat_set_focus(seat, previous);
1249 } 1313 }
1250 return; 1314 return;
1251 } else if (!layer || seat->focused_layer == layer) { 1315 } else if (!layer) {
1252 return; 1316 return;
1253 } 1317 }
1254 assert(layer->mapped); 1318 assert(layer->surface->mapped);
1255 seat_set_focus_surface(seat, layer->surface, true); 1319 if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP &&
1256 if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { 1320 layer->current.keyboard_interactive
1257 seat->focused_layer = layer; 1321 == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) {
1322 seat->has_exclusive_layer = true;
1258 } 1323 }
1259} 1324 if (seat->focused_layer == layer) {
1260
1261void seat_set_exclusive_client(struct sway_seat *seat,
1262 struct wl_client *client) {
1263 if (!client) {
1264 seat->exclusive_client = client;
1265 // Triggers a refocus of the topmost surface layer if necessary
1266 // TODO: Make layer surface focus per-output based on cursor position
1267 for (int i = 0; i < root->outputs->length; ++i) {
1268 struct sway_output *output = root->outputs->items[i];
1269 arrange_layers(output);
1270 }
1271 return; 1325 return;
1272 } 1326 }
1327 seat_set_focus_surface(seat, layer->surface, true);
1328 seat->focused_layer = layer;
1329}
1330
1331void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client) {
1273 if (seat->focused_layer) { 1332 if (seat->focused_layer) {
1274 if (wl_resource_get_client(seat->focused_layer->resource) != client) { 1333 if (wl_resource_get_client(seat->focused_layer->resource) != client) {
1275 seat_set_focus_layer(seat, NULL); 1334 seat_set_focus_layer(seat, NULL);
@@ -1296,7 +1355,6 @@ void seat_set_exclusive_client(struct sway_seat *seat,
1296 now.tv_nsec / 1000, point->touch_id); 1355 now.tv_nsec / 1000, point->touch_id);
1297 } 1356 }
1298 } 1357 }
1299 seat->exclusive_client = client;
1300} 1358}
1301 1359
1302struct sway_node *seat_get_focus_inactive(struct sway_seat *seat, 1360struct sway_node *seat_get_focus_inactive(struct sway_seat *seat,
@@ -1326,7 +1384,7 @@ struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat,
1326 struct sway_node *node = current->node; 1384 struct sway_node *node = current->node;
1327 if (node->type == N_CONTAINER && 1385 if (node->type == N_CONTAINER &&
1328 !container_is_floating_or_child(node->sway_container) && 1386 !container_is_floating_or_child(node->sway_container) &&
1329 node->sway_container->workspace == workspace) { 1387 node->sway_container->pending.workspace == workspace) {
1330 return node->sway_container; 1388 return node->sway_container;
1331 } 1389 }
1332 } 1390 }
@@ -1343,7 +1401,7 @@ struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat,
1343 struct sway_node *node = current->node; 1401 struct sway_node *node = current->node;
1344 if (node->type == N_CONTAINER && 1402 if (node->type == N_CONTAINER &&
1345 container_is_floating_or_child(node->sway_container) && 1403 container_is_floating_or_child(node->sway_container) &&
1346 node->sway_container->workspace == workspace) { 1404 node->sway_container->pending.workspace == workspace) {
1347 return node->sway_container; 1405 return node->sway_container;
1348 } 1406 }
1349 } 1407 }
@@ -1377,9 +1435,8 @@ struct sway_node *seat_get_focus(struct sway_seat *seat) {
1377 if (!seat->has_focus) { 1435 if (!seat->has_focus) {
1378 return NULL; 1436 return NULL;
1379 } 1437 }
1380 if (wl_list_empty(&seat->focus_stack)) { 1438 sway_assert(!wl_list_empty(&seat->focus_stack),
1381 return NULL; 1439 "focus_stack is empty, but has_focus is true");
1382 }
1383 struct sway_seat_node *current = 1440 struct sway_seat_node *current =
1384 wl_container_of(seat->focus_stack.next, current, link); 1441 wl_container_of(seat->focus_stack.next, current, link);
1385 return current->node; 1442 return current->node;
@@ -1391,7 +1448,7 @@ struct sway_workspace *seat_get_focused_workspace(struct sway_seat *seat) {
1391 return NULL; 1448 return NULL;
1392 } 1449 }
1393 if (focus->type == N_CONTAINER) { 1450 if (focus->type == N_CONTAINER) {
1394 return focus->sway_container->workspace; 1451 return focus->sway_container->pending.workspace;
1395 } 1452 }
1396 if (focus->type == N_WORKSPACE) { 1453 if (focus->type == N_WORKSPACE) {
1397 return focus->sway_workspace; 1454 return focus->sway_workspace;
@@ -1404,8 +1461,8 @@ struct sway_workspace *seat_get_last_known_workspace(struct sway_seat *seat) {
1404 wl_list_for_each(current, &seat->focus_stack, link) { 1461 wl_list_for_each(current, &seat->focus_stack, link) {
1405 struct sway_node *node = current->node; 1462 struct sway_node *node = current->node;
1406 if (node->type == N_CONTAINER && 1463 if (node->type == N_CONTAINER &&
1407 node->sway_container->workspace) { 1464 node->sway_container->pending.workspace) {
1408 return node->sway_container->workspace; 1465 return node->sway_container->pending.workspace;
1409 } else if (node->type == N_WORKSPACE) { 1466 } else if (node->type == N_WORKSPACE) {
1410 return node->sway_workspace; 1467 return node->sway_workspace;
1411 } 1468 }
@@ -1514,12 +1571,38 @@ void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
1514} 1571}
1515 1572
1516void seatop_pointer_axis(struct sway_seat *seat, 1573void seatop_pointer_axis(struct sway_seat *seat,
1517 struct wlr_event_pointer_axis *event) { 1574 struct wlr_pointer_axis_event *event) {
1518 if (seat->seatop_impl->pointer_axis) { 1575 if (seat->seatop_impl->pointer_axis) {
1519 seat->seatop_impl->pointer_axis(seat, event); 1576 seat->seatop_impl->pointer_axis(seat, event);
1520 } 1577 }
1521} 1578}
1522 1579
1580void seatop_touch_motion(struct sway_seat *seat, struct wlr_touch_motion_event *event,
1581 double lx, double ly) {
1582 if (seat->seatop_impl->touch_motion) {
1583 seat->seatop_impl->touch_motion(seat, event, lx, ly);
1584 }
1585}
1586
1587void seatop_touch_up(struct sway_seat *seat, struct wlr_touch_up_event *event) {
1588 if (seat->seatop_impl->touch_up) {
1589 seat->seatop_impl->touch_up(seat, event);
1590 }
1591}
1592
1593void seatop_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event,
1594 double lx, double ly) {
1595 if (seat->seatop_impl->touch_down) {
1596 seat->seatop_impl->touch_down(seat, event, lx, ly);
1597 }
1598}
1599
1600void seatop_touch_cancel(struct sway_seat *seat, struct wlr_touch_cancel_event *event) {
1601 if (seat->seatop_impl->touch_cancel) {
1602 seat->seatop_impl->touch_cancel(seat, event);
1603 }
1604}
1605
1523void seatop_tablet_tool_tip(struct sway_seat *seat, 1606void seatop_tablet_tool_tip(struct sway_seat *seat,
1524 struct sway_tablet_tool *tool, uint32_t time_msec, 1607 struct sway_tablet_tool *tool, uint32_t time_msec,
1525 enum wlr_tablet_tool_tip_state state) { 1608 enum wlr_tablet_tool_tip_state state) {
@@ -1537,6 +1620,62 @@ void seatop_tablet_tool_motion(struct sway_seat *seat,
1537 } 1620 }
1538} 1621}
1539 1622
1623void seatop_hold_begin(struct sway_seat *seat,
1624 struct wlr_pointer_hold_begin_event *event) {
1625 if (seat->seatop_impl->hold_begin) {
1626 seat->seatop_impl->hold_begin(seat, event);
1627 }
1628}
1629
1630void seatop_hold_end(struct sway_seat *seat,
1631 struct wlr_pointer_hold_end_event *event) {
1632 if (seat->seatop_impl->hold_end) {
1633 seat->seatop_impl->hold_end(seat, event);
1634 }
1635}
1636
1637void seatop_pinch_begin(struct sway_seat *seat,
1638 struct wlr_pointer_pinch_begin_event *event) {
1639 if (seat->seatop_impl->pinch_begin) {
1640 seat->seatop_impl->pinch_begin(seat, event);
1641 }
1642}
1643
1644void seatop_pinch_update(struct sway_seat *seat,
1645 struct wlr_pointer_pinch_update_event *event) {
1646 if (seat->seatop_impl->pinch_update) {
1647 seat->seatop_impl->pinch_update(seat, event);
1648 }
1649}
1650
1651void seatop_pinch_end(struct sway_seat *seat,
1652 struct wlr_pointer_pinch_end_event *event) {
1653 if (seat->seatop_impl->pinch_end) {
1654 seat->seatop_impl->pinch_end(seat, event);
1655 }
1656}
1657
1658void seatop_swipe_begin(struct sway_seat *seat,
1659 struct wlr_pointer_swipe_begin_event *event) {
1660 if (seat->seatop_impl->swipe_begin) {
1661 seat->seatop_impl->swipe_begin(seat, event);
1662 }
1663}
1664
1665void seatop_swipe_update(struct sway_seat *seat,
1666 struct wlr_pointer_swipe_update_event *event) {
1667 if (seat->seatop_impl->swipe_update) {
1668 seat->seatop_impl->swipe_update(seat, event);
1669 }
1670}
1671
1672void seatop_swipe_end(struct sway_seat *seat,
1673 struct wlr_pointer_swipe_end_event *event) {
1674 if (seat->seatop_impl->swipe_end) {
1675 seat->seatop_impl->swipe_end(seat, event);
1676 }
1677}
1678
1540void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) { 1679void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) {
1541 if (seat->seatop_impl->rebase) { 1680 if (seat->seatop_impl->rebase) {
1542 seat->seatop_impl->rebase(seat, time_msec); 1681 seat->seatop_impl->rebase(seat, time_msec);
@@ -1552,13 +1691,6 @@ void seatop_end(struct sway_seat *seat) {
1552 seat->seatop_impl = NULL; 1691 seat->seatop_impl = NULL;
1553} 1692}
1554 1693
1555void seatop_render(struct sway_seat *seat, struct sway_output *output,
1556 pixman_region32_t *damage) {
1557 if (seat->seatop_impl->render) {
1558 seat->seatop_impl->render(seat, output, damage);
1559 }
1560}
1561
1562bool seatop_allows_set_cursor(struct sway_seat *seat) { 1694bool seatop_allows_set_cursor(struct sway_seat *seat) {
1563 return seat->seatop_impl->allow_set_cursor; 1695 return seat->seatop_impl->allow_set_cursor;
1564} 1696}
diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c
index a583ed62..c56330fd 100644
--- a/sway/input/seatop_default.c
+++ b/sway/input/seatop_default.c
@@ -2,12 +2,17 @@
2#include <float.h> 2#include <float.h>
3#include <libevdev/libevdev.h> 3#include <libevdev/libevdev.h>
4#include <wlr/types/wlr_cursor.h> 4#include <wlr/types/wlr_cursor.h>
5#include <wlr/types/wlr_subcompositor.h>
5#include <wlr/types/wlr_tablet_v2.h> 6#include <wlr/types/wlr_tablet_v2.h>
6#include <wlr/types/wlr_xcursor_manager.h> 7#include <wlr/types/wlr_xcursor_manager.h>
8#include "gesture.h"
9#include "sway/desktop/transaction.h"
7#include "sway/input/cursor.h" 10#include "sway/input/cursor.h"
8#include "sway/input/seat.h" 11#include "sway/input/seat.h"
9#include "sway/input/tablet.h" 12#include "sway/input/tablet.h"
13#include "sway/layers.h"
10#include "sway/output.h" 14#include "sway/output.h"
15#include "sway/scene_descriptor.h"
11#include "sway/tree/view.h" 16#include "sway/tree/view.h"
12#include "sway/tree/workspace.h" 17#include "sway/tree/workspace.h"
13#include "log.h" 18#include "log.h"
@@ -19,6 +24,7 @@ struct seatop_default_event {
19 struct sway_node *previous_node; 24 struct sway_node *previous_node;
20 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; 25 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
21 size_t pressed_button_count; 26 size_t pressed_button_count;
27 struct gesture_tracker gestures;
22}; 28};
23 29
24/*-----------------------------------------\ 30/*-----------------------------------------\
@@ -50,6 +56,9 @@ static bool edge_is_external(struct sway_container *cont, enum wlr_edges edge) {
50 while (cont) { 56 while (cont) {
51 if (container_parent_layout(cont) == layout) { 57 if (container_parent_layout(cont) == layout) {
52 list_t *siblings = container_get_siblings(cont); 58 list_t *siblings = container_get_siblings(cont);
59 if (!siblings) {
60 return false;
61 }
53 int index = list_find(siblings, cont); 62 int index = list_find(siblings, cont);
54 if (index > 0 && (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_TOP)) { 63 if (index > 0 && (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_TOP)) {
55 return false; 64 return false;
@@ -59,7 +68,7 @@ static bool edge_is_external(struct sway_container *cont, enum wlr_edges edge) {
59 return false; 68 return false;
60 } 69 }
61 } 70 }
62 cont = cont->parent; 71 cont = cont->pending.parent;
63 } 72 }
64 return true; 73 return true;
65} 74}
@@ -69,25 +78,25 @@ static enum wlr_edges find_edge(struct sway_container *cont,
69 if (!cont->view || (surface && cont->view->surface != surface)) { 78 if (!cont->view || (surface && cont->view->surface != surface)) {
70 return WLR_EDGE_NONE; 79 return WLR_EDGE_NONE;
71 } 80 }
72 if (cont->border == B_NONE || !cont->border_thickness || 81 if (cont->pending.border == B_NONE || !cont->pending.border_thickness ||
73 cont->border == B_CSD) { 82 cont->pending.border == B_CSD) {
74 return WLR_EDGE_NONE; 83 return WLR_EDGE_NONE;
75 } 84 }
76 if (cont->fullscreen_mode) { 85 if (cont->pending.fullscreen_mode) {
77 return WLR_EDGE_NONE; 86 return WLR_EDGE_NONE;
78 } 87 }
79 88
80 enum wlr_edges edge = 0; 89 enum wlr_edges edge = 0;
81 if (cursor->cursor->x < cont->x + cont->border_thickness) { 90 if (cursor->cursor->x < cont->pending.x + cont->pending.border_thickness) {
82 edge |= WLR_EDGE_LEFT; 91 edge |= WLR_EDGE_LEFT;
83 } 92 }
84 if (cursor->cursor->y < cont->y + cont->border_thickness) { 93 if (cursor->cursor->y < cont->pending.y + cont->pending.border_thickness) {
85 edge |= WLR_EDGE_TOP; 94 edge |= WLR_EDGE_TOP;
86 } 95 }
87 if (cursor->cursor->x >= cont->x + cont->width - cont->border_thickness) { 96 if (cursor->cursor->x >= cont->pending.x + cont->pending.width - cont->pending.border_thickness) {
88 edge |= WLR_EDGE_RIGHT; 97 edge |= WLR_EDGE_RIGHT;
89 } 98 }
90 if (cursor->cursor->y >= cont->y + cont->height - cont->border_thickness) { 99 if (cursor->cursor->y >= cont->pending.y + cont->pending.height - cont->pending.border_thickness) {
91 edge |= WLR_EDGE_BOTTOM; 100 edge |= WLR_EDGE_BOTTOM;
92 } 101 }
93 102
@@ -225,13 +234,15 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
225 struct sway_container *cont = node && node->type == N_CONTAINER ? 234 struct sway_container *cont = node && node->type == N_CONTAINER ?
226 node->sway_container : NULL; 235 node->sway_container : NULL;
227 236
228 if (wlr_surface_is_layer_surface(surface)) { 237 struct wlr_layer_surface_v1 *layer;
238#if HAVE_XWAYLAND
239 struct wlr_xwayland_surface *xsurface;
240#endif
241 if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface)) &&
242 layer->current.keyboard_interactive) {
229 // Handle tapping a layer surface 243 // Handle tapping a layer surface
230 struct wlr_layer_surface_v1 *layer = 244 seat_set_focus_layer(seat, layer);
231 wlr_layer_surface_v1_from_wlr_surface(surface); 245 transaction_commit_dirty();
232 if (layer->current.keyboard_interactive) {
233 seat_set_focus_layer(seat, layer);
234 }
235 } else if (cont) { 246 } else if (cont) {
236 bool is_floating_or_child = container_is_floating_or_child(cont); 247 bool is_floating_or_child = container_is_floating_or_child(cont);
237 bool is_fullscreen_or_child = container_is_fullscreen_or_child(cont); 248 bool is_fullscreen_or_child = container_is_fullscreen_or_child(cont);
@@ -249,26 +260,24 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
249 260
250 // Handle moving a tiling container 261 // Handle moving a tiling container
251 if (config->tiling_drag && mod_pressed && !is_floating_or_child && 262 if (config->tiling_drag && mod_pressed && !is_floating_or_child &&
252 cont->fullscreen_mode == FULLSCREEN_NONE) { 263 cont->pending.fullscreen_mode == FULLSCREEN_NONE) {
253 seatop_begin_move_tiling(seat, cont); 264 seatop_begin_move_tiling(seat, cont);
254 return; 265 return;
255 } 266 }
256 267
257 // Handle tapping on a container surface 268 // Handle tapping on a container surface
258 seat_set_focus_container(seat, cont); 269 seat_set_focus_container(seat, cont);
259 seatop_begin_down(seat, node->sway_container, time_msec, sx, sy); 270 seatop_begin_down(seat, node->sway_container, sx, sy);
260 } 271 }
261#if HAVE_XWAYLAND 272#if HAVE_XWAYLAND
262 // Handle tapping on an xwayland unmanaged view 273 // Handle tapping on an xwayland unmanaged view
263 else if (wlr_surface_is_xwayland_surface(surface)) { 274 else if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) &&
264 struct wlr_xwayland_surface *xsurface = 275 xsurface->override_redirect &&
265 wlr_xwayland_surface_from_wlr_surface(surface); 276 wlr_xwayland_or_surface_wants_focus(xsurface)) {
266 if (xsurface->override_redirect && 277 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
267 wlr_xwayland_or_surface_wants_focus(xsurface)) { 278 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
268 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; 279 seat_set_focus_surface(seat, xsurface->surface, false);
269 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 280 transaction_commit_dirty();
270 seat_set_focus_surface(seat, xsurface->surface, false);
271 }
272 } 281 }
273#endif 282#endif
274 283
@@ -356,17 +365,21 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
356 if (node && node->type == N_WORKSPACE) { 365 if (node && node->type == N_WORKSPACE) {
357 if (state == WLR_BUTTON_PRESSED) { 366 if (state == WLR_BUTTON_PRESSED) {
358 seat_set_focus(seat, node); 367 seat_set_focus(seat, node);
368 transaction_commit_dirty();
359 } 369 }
360 seat_pointer_notify_button(seat, time_msec, button, state); 370 seat_pointer_notify_button(seat, time_msec, button, state);
361 return; 371 return;
362 } 372 }
363 373
364 // Handle clicking a layer surface 374 // Handle clicking a layer surface and its popups/subsurfaces
365 if (surface && wlr_surface_is_layer_surface(surface)) { 375 struct wlr_layer_surface_v1 *layer = NULL;
366 struct wlr_layer_surface_v1 *layer = 376 if ((layer = toplevel_layer_surface_from_surface(surface))) {
367 wlr_layer_surface_v1_from_wlr_surface(surface);
368 if (layer->current.keyboard_interactive) { 377 if (layer->current.keyboard_interactive) {
369 seat_set_focus_layer(seat, layer); 378 seat_set_focus_layer(seat, layer);
379 transaction_commit_dirty();
380 }
381 if (state == WLR_BUTTON_PRESSED) {
382 seatop_begin_down_on_surface(seat, surface, sx, sy);
370 } 383 }
371 seat_pointer_notify_button(seat, time_msec, button, state); 384 seat_pointer_notify_button(seat, time_msec, button, state);
372 return; 385 return;
@@ -381,7 +394,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
381 struct sway_container *cont_to_focus = cont; 394 struct sway_container *cont_to_focus = cont;
382 enum sway_container_layout layout = container_parent_layout(cont); 395 enum sway_container_layout layout = container_parent_layout(cont);
383 if (layout == L_TABBED || layout == L_STACKED) { 396 if (layout == L_TABBED || layout == L_STACKED) {
384 cont_to_focus = seat_get_focus_inactive_view(seat, &cont->parent->node); 397 cont_to_focus = seat_get_focus_inactive_view(seat, &cont->pending.parent->node);
385 } 398 }
386 399
387 seat_set_focus_container(seat, cont_to_focus); 400 seat_set_focus_container(seat, cont_to_focus);
@@ -397,9 +410,9 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
397 BTN_LEFT : BTN_RIGHT; 410 BTN_LEFT : BTN_RIGHT;
398 if (button == btn_resize) { 411 if (button == btn_resize) {
399 edge = 0; 412 edge = 0;
400 edge |= cursor->cursor->x > cont->x + cont->width / 2 ? 413 edge |= cursor->cursor->x > cont->pending.x + cont->pending.width / 2 ?
401 WLR_EDGE_RIGHT : WLR_EDGE_LEFT; 414 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
402 edge |= cursor->cursor->y > cont->y + cont->height / 2 ? 415 edge |= cursor->cursor->y > cont->pending.y + cont->pending.height / 2 ?
403 WLR_EDGE_BOTTOM : WLR_EDGE_TOP; 416 WLR_EDGE_BOTTOM : WLR_EDGE_TOP;
404 417
405 const char *image = NULL; 418 const char *image = NULL;
@@ -419,13 +432,31 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
419 } 432 }
420 } 433 }
421 434
435 // Handle changing focus when clicking on a container
436 if (cont && state == WLR_BUTTON_PRESSED) {
437 // Default case: focus the container that was just clicked.
438 node = &cont->node;
439
440 // If the container is a tab/stacked container and the click happened
441 // on a tab, switch to the tab. If the tab contents were already
442 // focused, focus the tab container itself. If the tab container was
443 // already focused, cycle back to focusing the tab contents.
444 if (on_titlebar) {
445 struct sway_container *focus = seat_get_focused_container(seat);
446 if (focus == cont || !container_has_ancestor(focus, cont)) {
447 node = seat_get_focus_inactive(seat, &cont->node);
448 }
449 }
450
451 seat_set_focus(seat, node);
452 transaction_commit_dirty();
453 }
454
422 // Handle beginning floating move 455 // Handle beginning floating move
423 if (cont && is_floating_or_child && !is_fullscreen_or_child && 456 if (cont && is_floating_or_child && !is_fullscreen_or_child &&
424 state == WLR_BUTTON_PRESSED) { 457 state == WLR_BUTTON_PRESSED) {
425 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; 458 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT;
426 if (button == btn_move && (mod_pressed || on_titlebar)) { 459 if (button == btn_move && (mod_pressed || on_titlebar)) {
427 seat_set_focus_container(seat,
428 seat_get_focus_inactive_view(seat, &cont->node));
429 seatop_begin_move_floating(seat, container_toplevel_ancestor(cont)); 460 seatop_begin_move_floating(seat, container_toplevel_ancestor(cont));
430 return; 461 return;
431 } 462 }
@@ -436,6 +467,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
436 state == WLR_BUTTON_PRESSED) { 467 state == WLR_BUTTON_PRESSED) {
437 // Via border 468 // Via border
438 if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) { 469 if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) {
470 seat_set_focus_container(seat, cont);
439 seatop_begin_resize_floating(seat, cont, resize_edge); 471 seatop_begin_resize_floating(seat, cont, resize_edge);
440 return; 472 return;
441 } 473 }
@@ -446,10 +478,11 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
446 if (mod_pressed && button == btn_resize) { 478 if (mod_pressed && button == btn_resize) {
447 struct sway_container *floater = container_toplevel_ancestor(cont); 479 struct sway_container *floater = container_toplevel_ancestor(cont);
448 edge = 0; 480 edge = 0;
449 edge |= cursor->cursor->x > floater->x + floater->width / 2 ? 481 edge |= cursor->cursor->x > floater->pending.x + floater->pending.width / 2 ?
450 WLR_EDGE_RIGHT : WLR_EDGE_LEFT; 482 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
451 edge |= cursor->cursor->y > floater->y + floater->height / 2 ? 483 edge |= cursor->cursor->y > floater->pending.y + floater->pending.height / 2 ?
452 WLR_EDGE_BOTTOM : WLR_EDGE_TOP; 484 WLR_EDGE_BOTTOM : WLR_EDGE_TOP;
485 seat_set_focus_container(seat, floater);
453 seatop_begin_resize_floating(seat, floater, edge); 486 seatop_begin_resize_floating(seat, floater, edge);
454 return; 487 return;
455 } 488 }
@@ -458,52 +491,42 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
458 // Handle moving a tiling container 491 // Handle moving a tiling container
459 if (config->tiling_drag && (mod_pressed || on_titlebar) && 492 if (config->tiling_drag && (mod_pressed || on_titlebar) &&
460 state == WLR_BUTTON_PRESSED && !is_floating_or_child && 493 state == WLR_BUTTON_PRESSED && !is_floating_or_child &&
461 cont && cont->fullscreen_mode == FULLSCREEN_NONE) { 494 cont && cont->pending.fullscreen_mode == FULLSCREEN_NONE) {
462 struct sway_container *focus = seat_get_focused_container(seat); 495 // If moving a container by its title bar, use a threshold for the drag
463 bool focused = focus == cont || container_has_ancestor(focus, cont);
464 if (on_titlebar && !focused) {
465 node = seat_get_focus_inactive(seat, &cont->node);
466 seat_set_focus(seat, node);
467 }
468
469 // If moving a container by it's title bar, use a threshold for the drag
470 if (!mod_pressed && config->tiling_drag_threshold > 0) { 496 if (!mod_pressed && config->tiling_drag_threshold > 0) {
471 seatop_begin_move_tiling_threshold(seat, cont); 497 seatop_begin_move_tiling_threshold(seat, cont);
472 } else { 498 } else {
473 seatop_begin_move_tiling(seat, cont); 499 seatop_begin_move_tiling(seat, cont);
474 } 500 }
501
475 return; 502 return;
476 } 503 }
477 504
478 // Handle mousedown on a container surface 505 // Handle mousedown on a container surface
479 if (surface && cont && state == WLR_BUTTON_PRESSED) { 506 if (surface && cont && state == WLR_BUTTON_PRESSED) {
480 seat_set_focus_container(seat, cont); 507 seatop_begin_down(seat, cont, sx, sy);
481 seatop_begin_down(seat, cont, time_msec, sx, sy);
482 seat_pointer_notify_button(seat, time_msec, button, WLR_BUTTON_PRESSED); 508 seat_pointer_notify_button(seat, time_msec, button, WLR_BUTTON_PRESSED);
483 return; 509 return;
484 } 510 }
485 511
486 // Handle clicking a container surface or decorations 512 // Handle clicking a container surface or decorations
487 if (cont && state == WLR_BUTTON_PRESSED) { 513 if (cont && state == WLR_BUTTON_PRESSED) {
488 node = seat_get_focus_inactive(seat, &cont->node);
489 seat_set_focus(seat, node);
490 seat_pointer_notify_button(seat, time_msec, button, state); 514 seat_pointer_notify_button(seat, time_msec, button, state);
491 return; 515 return;
492 } 516 }
493 517
494#if HAVE_XWAYLAND 518#if HAVE_XWAYLAND
495 // Handle clicking on xwayland unmanaged view 519 // Handle clicking on xwayland unmanaged view
496 if (surface && wlr_surface_is_xwayland_surface(surface)) { 520 struct wlr_xwayland_surface *xsurface;
497 struct wlr_xwayland_surface *xsurface = 521 if (surface &&
498 wlr_xwayland_surface_from_wlr_surface(surface); 522 (xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) &&
499 if (xsurface->override_redirect && 523 xsurface->override_redirect &&
500 wlr_xwayland_or_surface_wants_focus(xsurface)) { 524 wlr_xwayland_or_surface_wants_focus(xsurface)) {
501 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; 525 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
502 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 526 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
503 seat_set_focus_surface(seat, xsurface->surface, false); 527 seat_set_focus_surface(seat, xsurface->surface, false);
504 seat_pointer_notify_button(seat, time_msec, button, state); 528 transaction_commit_dirty();
505 return; 529 seat_pointer_notify_button(seat, time_msec, button, state);
506 }
507 } 530 }
508#endif 531#endif
509 532
@@ -526,10 +549,26 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
526 if (wlr_output == NULL) { 549 if (wlr_output == NULL) {
527 return; 550 return;
528 } 551 }
552
553 struct wlr_surface *surface = NULL;
554 double sx, sy;
555 node_at_coords(seat, seat->cursor->cursor->x, seat->cursor->cursor->y,
556 &surface, &sx, &sy);
557
558 // Focus topmost layer surface
559 struct wlr_layer_surface_v1 *layer = NULL;
560 if ((layer = toplevel_layer_surface_from_surface(surface)) &&
561 layer->current.keyboard_interactive) {
562 seat_set_focus_layer(seat, layer);
563 transaction_commit_dirty();
564 return;
565 }
566
529 struct sway_output *hovered_output = wlr_output->data; 567 struct sway_output *hovered_output = wlr_output->data;
530 if (focus && hovered_output != node_get_output(focus)) { 568 if (focus && hovered_output != node_get_output(focus)) {
531 struct sway_workspace *ws = output_get_active_workspace(hovered_output); 569 struct sway_workspace *ws = output_get_active_workspace(hovered_output);
532 seat_set_focus(seat, &ws->node); 570 seat_set_focus(seat, &ws->node);
571 transaction_commit_dirty();
533 } 572 }
534 return; 573 return;
535 } 574 }
@@ -541,6 +580,7 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
541 struct sway_output *hovered_output = node_get_output(hovered_node); 580 struct sway_output *hovered_output = node_get_output(hovered_node);
542 if (hovered_output != focused_output) { 581 if (hovered_output != focused_output) {
543 seat_set_focus(seat, seat_get_focus_inactive(seat, hovered_node)); 582 seat_set_focus(seat, seat_get_focus_inactive(seat, hovered_node));
583 transaction_commit_dirty();
544 } 584 }
545 return; 585 return;
546 } 586 }
@@ -556,6 +596,7 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
556 if (hovered_node != e->previous_node || 596 if (hovered_node != e->previous_node ||
557 config->focus_follows_mouse == FOLLOWS_ALWAYS) { 597 config->focus_follows_mouse == FOLLOWS_ALWAYS) {
558 seat_set_focus(seat, hovered_node); 598 seat_set_focus(seat, hovered_node);
599 transaction_commit_dirty();
559 } 600 }
560 } 601 }
561} 602}
@@ -583,12 +624,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
583 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 624 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
584 } 625 }
585 626
586 struct sway_drag_icon *drag_icon; 627 drag_icons_update_position(seat);
587 wl_list_for_each(drag_icon, &root->drag_icons, link) {
588 if (drag_icon->seat == seat) {
589 drag_icon_update_position(drag_icon);
590 }
591 }
592 628
593 e->previous_node = node; 629 e->previous_node = node;
594} 630}
@@ -618,21 +654,46 @@ static void handle_tablet_tool_motion(struct sway_seat *seat,
618 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); 654 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool);
619 } 655 }
620 656
621 struct sway_drag_icon *drag_icon; 657 drag_icons_update_position(seat);
622 wl_list_for_each(drag_icon, &root->drag_icons, link) {
623 if (drag_icon->seat == seat) {
624 drag_icon_update_position(drag_icon);
625 }
626 }
627 658
628 e->previous_node = node; 659 e->previous_node = node;
629} 660}
630 661
662static void handle_touch_down(struct sway_seat *seat,
663 struct wlr_touch_down_event *event, double lx, double ly) {
664 struct wlr_surface *surface = NULL;
665 struct wlr_seat *wlr_seat = seat->wlr_seat;
666 struct sway_cursor *cursor = seat->cursor;
667 double sx, sy;
668 node_at_coords(seat, seat->touch_x, seat->touch_y, &surface, &sx, &sy);
669
670 if (surface && wlr_surface_accepts_touch(wlr_seat, surface)) {
671 if (seat_is_input_allowed(seat, surface)) {
672 cursor->simulating_pointer_from_touch = false;
673 seatop_begin_touch_down(seat, surface, event, sx, sy, lx, ly);
674 }
675 } else if (!cursor->simulating_pointer_from_touch &&
676 (!surface || seat_is_input_allowed(seat, surface))) {
677 // Fallback to cursor simulation.
678 // The pointer_touch_id state is needed, so drags are not aborted when over
679 // a surface supporting touch and multi touch events don't interfere.
680 cursor->simulating_pointer_from_touch = true;
681 cursor->pointer_touch_id = seat->touch_id;
682 double dx, dy;
683 dx = seat->touch_x - cursor->cursor->x;
684 dy = seat->touch_y - cursor->cursor->y;
685 pointer_motion(cursor, event->time_msec, &event->touch->base, dx, dy,
686 dx, dy);
687 dispatch_cursor_button(cursor, &event->touch->base, event->time_msec,
688 BTN_LEFT, WLR_BUTTON_PRESSED);
689 }
690}
691
631/*----------------------------------------\ 692/*----------------------------------------\
632 * Functions used by handle_pointer_axis / 693 * Functions used by handle_pointer_axis /
633 *--------------------------------------*/ 694 *--------------------------------------*/
634 695
635static uint32_t wl_axis_to_button(struct wlr_event_pointer_axis *event) { 696static uint32_t wl_axis_to_button(struct wlr_pointer_axis_event *event) {
636 switch (event->orientation) { 697 switch (event->orientation) {
637 case WLR_AXIS_ORIENTATION_VERTICAL: 698 case WLR_AXIS_ORIENTATION_VERTICAL:
638 return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; 699 return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN;
@@ -645,9 +706,9 @@ static uint32_t wl_axis_to_button(struct wlr_event_pointer_axis *event) {
645} 706}
646 707
647static void handle_pointer_axis(struct sway_seat *seat, 708static void handle_pointer_axis(struct sway_seat *seat,
648 struct wlr_event_pointer_axis *event) { 709 struct wlr_pointer_axis_event *event) {
649 struct sway_input_device *input_device = 710 struct sway_input_device *input_device =
650 event->device ? event->device->data : NULL; 711 event->pointer ? event->pointer->base.data : NULL;
651 struct input_config *ic = 712 struct input_config *ic =
652 input_device ? input_device_get_config(input_device) : NULL; 713 input_device ? input_device_get_config(input_device) : NULL;
653 struct sway_cursor *cursor = seat->cursor; 714 struct sway_cursor *cursor = seat->cursor;
@@ -664,7 +725,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
664 bool on_border = edge != WLR_EDGE_NONE; 725 bool on_border = edge != WLR_EDGE_NONE;
665 bool on_titlebar = cont && !on_border && !surface; 726 bool on_titlebar = cont && !on_border && !surface;
666 bool on_titlebar_border = cont && on_border && 727 bool on_titlebar_border = cont && on_border &&
667 cursor->cursor->y < cont->content_y; 728 cursor->cursor->y < cont->pending.content_y;
668 bool on_contents = cont && !on_border && surface; 729 bool on_contents = cont && !on_border && surface;
669 bool on_workspace = node && node->type == N_WORKSPACE; 730 bool on_workspace = node && node->type == N_WORKSPACE;
670 float scroll_factor = 731 float scroll_factor =
@@ -693,6 +754,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
693 754
694 // Scrolling on a tabbed or stacked title bar (handled as press event) 755 // Scrolling on a tabbed or stacked title bar (handled as press event)
695 if (!handled && (on_titlebar || on_titlebar_border)) { 756 if (!handled && (on_titlebar || on_titlebar_border)) {
757 struct sway_node *new_focus;
696 enum sway_container_layout layout = container_parent_layout(cont); 758 enum sway_container_layout layout = container_parent_layout(cont);
697 if (layout == L_TABBED || layout == L_STACKED) { 759 if (layout == L_TABBED || layout == L_STACKED) {
698 struct sway_node *tabcontainer = node_get_parent(node); 760 struct sway_node *tabcontainer = node_get_parent(node);
@@ -700,7 +762,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
700 seat_get_active_tiling_child(seat, tabcontainer); 762 seat_get_active_tiling_child(seat, tabcontainer);
701 list_t *siblings = container_get_siblings(cont); 763 list_t *siblings = container_get_siblings(cont);
702 int desired = list_find(siblings, active->sway_container) + 764 int desired = list_find(siblings, active->sway_container) +
703 round(scroll_factor * event->delta_discrete); 765 roundf(scroll_factor * event->delta_discrete / WLR_POINTER_AXIS_DISCRETE_STEP);
704 if (desired < 0) { 766 if (desired < 0) {
705 desired = 0; 767 desired = 0;
706 } else if (desired >= siblings->length) { 768 } else if (desired >= siblings->length) {
@@ -709,13 +771,16 @@ static void handle_pointer_axis(struct sway_seat *seat,
709 771
710 struct sway_container *new_sibling_con = siblings->items[desired]; 772 struct sway_container *new_sibling_con = siblings->items[desired];
711 struct sway_node *new_sibling = &new_sibling_con->node; 773 struct sway_node *new_sibling = &new_sibling_con->node;
712 struct sway_node *new_focus =
713 seat_get_focus_inactive(seat, new_sibling);
714 // Use the focused child of the tabbed/stacked container, not the 774 // Use the focused child of the tabbed/stacked container, not the
715 // container the user scrolled on. 775 // container the user scrolled on.
716 seat_set_focus(seat, new_focus); 776 new_focus = seat_get_focus_inactive(seat, new_sibling);
717 handled = true; 777 } else {
778 new_focus = seat_get_focus_inactive(seat, &cont->node);
718 } 779 }
780
781 seat_set_focus(seat, new_focus);
782 transaction_commit_dirty();
783 handled = true;
719 } 784 }
720 785
721 // Handle mouse bindings - x11 mouse buttons 4-7 - release event 786 // Handle mouse bindings - x11 mouse buttons 4-7 - release event
@@ -731,8 +796,307 @@ static void handle_pointer_axis(struct sway_seat *seat,
731 796
732 if (!handled) { 797 if (!handled) {
733 wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, 798 wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec,
734 event->orientation, scroll_factor * event->delta, 799 event->orientation, scroll_factor * event->delta,
735 round(scroll_factor * event->delta_discrete), event->source); 800 roundf(scroll_factor * event->delta_discrete), event->source,
801 event->relative_direction);
802 }
803}
804
805/*------------------------------------\
806 * Functions used by gesture support /
807 *----------------------------------*/
808
809/**
810 * Check gesture binding for a specific gesture type and finger count.
811 * Returns true if binding is present, false otherwise
812 */
813static bool gesture_binding_check(list_t *bindings, enum gesture_type type,
814 uint8_t fingers, struct sway_input_device *device) {
815 char *input =
816 device ? input_device_get_identifier(device->wlr_device) : strdup("*");
817
818 for (int i = 0; i < bindings->length; ++i) {
819 struct sway_gesture_binding *binding = bindings->items[i];
820
821 // Check type and finger count
822 if (!gesture_check(&binding->gesture, type, fingers)) {
823 continue;
824 }
825
826 // Check that input matches
827 if (strcmp(binding->input, "*") != 0 &&
828 strcmp(binding->input, input) != 0) {
829 continue;
830 }
831
832 free(input);
833
834 return true;
835 }
836
837 free(input);
838
839 return false;
840}
841
842/**
843 * Return the gesture binding which matches gesture type, finger count
844 * and direction, otherwise return null.
845 */
846static struct sway_gesture_binding* gesture_binding_match(
847 list_t *bindings, struct gesture *gesture, const char *input) {
848 struct sway_gesture_binding *current = NULL;
849
850 // Find best matching binding
851 for (int i = 0; i < bindings->length; ++i) {
852 struct sway_gesture_binding *binding = bindings->items[i];
853 bool exact = binding->flags & BINDING_EXACT;
854
855 // Check gesture matching
856 if (!gesture_match(&binding->gesture, gesture, exact)) {
857 continue;
858 }
859
860 // Check input matching
861 if (strcmp(binding->input, "*") != 0 &&
862 strcmp(binding->input, input) != 0) {
863 continue;
864 }
865
866 // If we already have a match ...
867 if (current) {
868 // ... check if input matching is equivalent
869 if (strcmp(current->input, binding->input) == 0) {
870
871 // ... - do not override an exact binding
872 if (!exact && current->flags & BINDING_EXACT) {
873 continue;
874 }
875
876 // ... - and ensure direction matching is better or equal
877 if (gesture_compare(&current->gesture, &binding->gesture) > 0) {
878 continue;
879 }
880 } else if (strcmp(binding->input, "*") == 0) {
881 // ... do not accept worse input match
882 continue;
883 }
884 }
885
886 // Accept newer or better match
887 current = binding;
888
889 // If exact binding and input is found, quit search
890 if (strcmp(current->input, input) == 0 &&
891 gesture_compare(&current->gesture, gesture) == 0) {
892 break;
893 }
894 } // for all gesture bindings
895
896 return current;
897}
898
899// Wrapper around gesture_tracker_end to use tracker with sway bindings
900static struct sway_gesture_binding* gesture_tracker_end_and_match(
901 struct gesture_tracker *tracker, struct sway_input_device* device) {
902 // Determine name of input that received gesture
903 char *input = device
904 ? input_device_get_identifier(device->wlr_device)
905 : strdup("*");
906
907 // Match tracking result to binding
908 struct gesture *gesture = gesture_tracker_end(tracker);
909 struct sway_gesture_binding *binding = gesture_binding_match(
910 config->current_mode->gesture_bindings, gesture, input);
911 free(gesture);
912 free(input);
913
914 return binding;
915}
916
917// Small wrapper around seat_execute_command to work on gesture bindings
918static void gesture_binding_execute(struct sway_seat *seat,
919 struct sway_gesture_binding *binding) {
920 struct sway_binding *dummy_binding =
921 calloc(1, sizeof(struct sway_binding));
922 dummy_binding->type = BINDING_GESTURE;
923 dummy_binding->command = binding->command;
924
925 char *description = gesture_to_string(&binding->gesture);
926 sway_log(SWAY_DEBUG, "executing gesture binding: %s", description);
927 free(description);
928
929 seat_execute_command(seat, dummy_binding);
930
931 free(dummy_binding);
932}
933
934static void handle_hold_begin(struct sway_seat *seat,
935 struct wlr_pointer_hold_begin_event *event) {
936 // Start tracking gesture if there is a matching binding ...
937 struct sway_input_device *device =
938 event->pointer ? event->pointer->base.data : NULL;
939 list_t *bindings = config->current_mode->gesture_bindings;
940 if (gesture_binding_check(bindings, GESTURE_TYPE_HOLD, event->fingers, device)) {
941 struct seatop_default_event *seatop = seat->seatop_data;
942 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_HOLD, event->fingers);
943 } else {
944 // ... otherwise forward to client
945 struct sway_cursor *cursor = seat->cursor;
946 wlr_pointer_gestures_v1_send_hold_begin(
947 server.input->pointer_gestures, cursor->seat->wlr_seat,
948 event->time_msec, event->fingers);
949 }
950}
951
952static void handle_hold_end(struct sway_seat *seat,
953 struct wlr_pointer_hold_end_event *event) {
954 // Ensure that gesture is being tracked and was not cancelled
955 struct seatop_default_event *seatop = seat->seatop_data;
956 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_HOLD)) {
957 struct sway_cursor *cursor = seat->cursor;
958 wlr_pointer_gestures_v1_send_hold_end(
959 server.input->pointer_gestures, cursor->seat->wlr_seat,
960 event->time_msec, event->cancelled);
961 return;
962 }
963 if (event->cancelled) {
964 gesture_tracker_cancel(&seatop->gestures);
965 return;
966 }
967
968 // End gesture tracking and execute matched binding
969 struct sway_input_device *device =
970 event->pointer ? event->pointer->base.data : NULL;
971 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
972 &seatop->gestures, device);
973
974 if (binding) {
975 gesture_binding_execute(seat, binding);
976 }
977}
978
979static void handle_pinch_begin(struct sway_seat *seat,
980 struct wlr_pointer_pinch_begin_event *event) {
981 // Start tracking gesture if there is a matching binding ...
982 struct sway_input_device *device =
983 event->pointer ? event->pointer->base.data : NULL;
984 list_t *bindings = config->current_mode->gesture_bindings;
985 if (gesture_binding_check(bindings, GESTURE_TYPE_PINCH, event->fingers, device)) {
986 struct seatop_default_event *seatop = seat->seatop_data;
987 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_PINCH, event->fingers);
988 } else {
989 // ... otherwise forward to client
990 struct sway_cursor *cursor = seat->cursor;
991 wlr_pointer_gestures_v1_send_pinch_begin(
992 server.input->pointer_gestures, cursor->seat->wlr_seat,
993 event->time_msec, event->fingers);
994 }
995}
996
997static void handle_pinch_update(struct sway_seat *seat,
998 struct wlr_pointer_pinch_update_event *event) {
999 // Update any ongoing tracking ...
1000 struct seatop_default_event *seatop = seat->seatop_data;
1001 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
1002 gesture_tracker_update(&seatop->gestures, event->dx, event->dy,
1003 event->scale, event->rotation);
1004 } else {
1005 // ... otherwise forward to client
1006 struct sway_cursor *cursor = seat->cursor;
1007 wlr_pointer_gestures_v1_send_pinch_update(
1008 server.input->pointer_gestures,
1009 cursor->seat->wlr_seat,
1010 event->time_msec, event->dx, event->dy,
1011 event->scale, event->rotation);
1012 }
1013}
1014
1015static void handle_pinch_end(struct sway_seat *seat,
1016 struct wlr_pointer_pinch_end_event *event) {
1017 // Ensure that gesture is being tracked and was not cancelled
1018 struct seatop_default_event *seatop = seat->seatop_data;
1019 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
1020 struct sway_cursor *cursor = seat->cursor;
1021 wlr_pointer_gestures_v1_send_pinch_end(
1022 server.input->pointer_gestures, cursor->seat->wlr_seat,
1023 event->time_msec, event->cancelled);
1024 return;
1025 }
1026 if (event->cancelled) {
1027 gesture_tracker_cancel(&seatop->gestures);
1028 return;
1029 }
1030
1031 // End gesture tracking and execute matched binding
1032 struct sway_input_device *device =
1033 event->pointer ? event->pointer->base.data : NULL;
1034 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
1035 &seatop->gestures, device);
1036
1037 if (binding) {
1038 gesture_binding_execute(seat, binding);
1039 }
1040}
1041
1042static void handle_swipe_begin(struct sway_seat *seat,
1043 struct wlr_pointer_swipe_begin_event *event) {
1044 // Start tracking gesture if there is a matching binding ...
1045 struct sway_input_device *device =
1046 event->pointer ? event->pointer->base.data : NULL;
1047 list_t *bindings = config->current_mode->gesture_bindings;
1048 if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) {
1049 struct seatop_default_event *seatop = seat->seatop_data;
1050 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers);
1051 } else {
1052 // ... otherwise forward to client
1053 struct sway_cursor *cursor = seat->cursor;
1054 wlr_pointer_gestures_v1_send_swipe_begin(
1055 server.input->pointer_gestures, cursor->seat->wlr_seat,
1056 event->time_msec, event->fingers);
1057 }
1058}
1059
1060static void handle_swipe_update(struct sway_seat *seat,
1061 struct wlr_pointer_swipe_update_event *event) {
1062
1063 // Update any ongoing tracking ...
1064 struct seatop_default_event *seatop = seat->seatop_data;
1065 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1066 gesture_tracker_update(&seatop->gestures,
1067 event->dx, event->dy, NAN, NAN);
1068 } else {
1069 // ... otherwise forward to client
1070 struct sway_cursor *cursor = seat->cursor;
1071 wlr_pointer_gestures_v1_send_swipe_update(
1072 server.input->pointer_gestures, cursor->seat->wlr_seat,
1073 event->time_msec, event->dx, event->dy);
1074 }
1075}
1076
1077static void handle_swipe_end(struct sway_seat *seat,
1078 struct wlr_pointer_swipe_end_event *event) {
1079 // Ensure gesture is being tracked and was not cancelled
1080 struct seatop_default_event *seatop = seat->seatop_data;
1081 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1082 struct sway_cursor *cursor = seat->cursor;
1083 wlr_pointer_gestures_v1_send_swipe_end(server.input->pointer_gestures,
1084 cursor->seat->wlr_seat, event->time_msec, event->cancelled);
1085 return;
1086 }
1087 if (event->cancelled) {
1088 gesture_tracker_cancel(&seatop->gestures);
1089 return;
1090 }
1091
1092 // End gesture tracking and execute matched binding
1093 struct sway_input_device *device =
1094 event->pointer ? event->pointer->base.data : NULL;
1095 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
1096 &seatop->gestures, device);
1097
1098 if (binding) {
1099 gesture_binding_execute(seat, binding);
736 } 1100 }
737} 1101}
738 1102
@@ -765,6 +1129,15 @@ static const struct sway_seatop_impl seatop_impl = {
765 .pointer_axis = handle_pointer_axis, 1129 .pointer_axis = handle_pointer_axis,
766 .tablet_tool_tip = handle_tablet_tool_tip, 1130 .tablet_tool_tip = handle_tablet_tool_tip,
767 .tablet_tool_motion = handle_tablet_tool_motion, 1131 .tablet_tool_motion = handle_tablet_tool_motion,
1132 .hold_begin = handle_hold_begin,
1133 .hold_end = handle_hold_end,
1134 .pinch_begin = handle_pinch_begin,
1135 .pinch_update = handle_pinch_update,
1136 .pinch_end = handle_pinch_end,
1137 .swipe_begin = handle_swipe_begin,
1138 .swipe_update = handle_swipe_update,
1139 .swipe_end = handle_swipe_end,
1140 .touch_down = handle_touch_down,
768 .rebase = handle_rebase, 1141 .rebase = handle_rebase,
769 .allow_set_cursor = true, 1142 .allow_set_cursor = true,
770}; 1143};
@@ -775,8 +1148,8 @@ void seatop_begin_default(struct sway_seat *seat) {
775 struct seatop_default_event *e = 1148 struct seatop_default_event *e =
776 calloc(1, sizeof(struct seatop_default_event)); 1149 calloc(1, sizeof(struct seatop_default_event));
777 sway_assert(e, "Unable to allocate seatop_default_event"); 1150 sway_assert(e, "Unable to allocate seatop_default_event");
1151
778 seat->seatop_impl = &seatop_impl; 1152 seat->seatop_impl = &seatop_impl;
779 seat->seatop_data = e; 1153 seat->seatop_data = e;
780
781 seatop_rebase(seat, 0); 1154 seatop_rebase(seat, 0);
782} 1155}
diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c
index 17f619e3..b4421fe6 100644
--- a/sway/input/seatop_down.c
+++ b/sway/input/seatop_down.c
@@ -2,21 +2,134 @@
2#include <float.h> 2#include <float.h>
3#include <wlr/types/wlr_cursor.h> 3#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_tablet_v2.h> 4#include <wlr/types/wlr_tablet_v2.h>
5#include <wlr/types/wlr_touch.h>
5#include "sway/input/cursor.h" 6#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 7#include "sway/input/seat.h"
7#include "sway/tree/view.h" 8#include "sway/tree/view.h"
9#include "sway/desktop/transaction.h"
8#include "log.h" 10#include "log.h"
9 11
12struct seatop_touch_point_event {
13 double ref_lx, ref_ly; // touch's x/y at start of op
14 double ref_con_lx, ref_con_ly; // container's x/y at start of op
15 int32_t touch_id;
16 struct wl_list link;
17};
18
10struct seatop_down_event { 19struct seatop_down_event {
11 struct sway_container *con; 20 struct sway_container *con;
21 struct sway_seat *seat;
22 struct wl_listener surface_destroy;
23 struct wlr_surface *surface;
12 double ref_lx, ref_ly; // cursor's x/y at start of op 24 double ref_lx, ref_ly; // cursor's x/y at start of op
13 double ref_con_lx, ref_con_ly; // container's x/y at start of op 25 double ref_con_lx, ref_con_ly; // container's x/y at start of op
26 struct wl_list point_events; // seatop_touch_point_event::link
14}; 27};
15 28
29static void handle_touch_motion(struct sway_seat *seat,
30 struct wlr_touch_motion_event *event, double lx, double ly) {
31 struct seatop_down_event *e = seat->seatop_data;
32
33 struct seatop_touch_point_event *point_event;
34 bool found = false;
35 wl_list_for_each(point_event, &e->point_events, link) {
36 if (point_event->touch_id == event->touch_id) {
37 found = true;
38 break;
39 }
40 }
41 if (!found) {
42 return; // Probably not a point_event from this seatop_down
43 }
44
45 double moved_x = lx - point_event->ref_lx;
46 double moved_y = ly - point_event->ref_ly;
47 double sx = point_event->ref_con_lx + moved_x;
48 double sy = point_event->ref_con_ly + moved_y;
49
50 wlr_seat_touch_notify_motion(seat->wlr_seat, event->time_msec,
51 event->touch_id, sx, sy);
52}
53
54static void handle_touch_up(struct sway_seat *seat,
55 struct wlr_touch_up_event *event) {
56 struct seatop_down_event *e = seat->seatop_data;
57 struct seatop_touch_point_event *point_event, *tmp;
58
59 wl_list_for_each_safe(point_event, tmp, &e->point_events, link) {
60 if (point_event->touch_id == event->touch_id) {
61 wl_list_remove(&point_event->link);
62 free(point_event);
63 break;
64 }
65 }
66
67 wlr_seat_touch_notify_up(seat->wlr_seat, event->time_msec, event->touch_id);
68
69 if (wl_list_empty(&e->point_events)) {
70 seatop_begin_default(seat);
71 }
72}
73
74static void handle_touch_down(struct sway_seat *seat,
75 struct wlr_touch_down_event *event, double lx, double ly) {
76 struct seatop_down_event *e = seat->seatop_data;
77 double sx, sy;
78 struct wlr_surface *surface = NULL;
79 struct sway_node *focused_node = node_at_coords(seat, seat->touch_x,
80 seat->touch_y, &surface, &sx, &sy);
81
82 if (!surface || surface != e->surface) { // Must start from the initial surface
83 return;
84 }
85
86 struct seatop_touch_point_event *point_event =
87 calloc(1, sizeof(struct seatop_touch_point_event));
88 if (!sway_assert(point_event, "Unable to allocate point_event")) {
89 return;
90 }
91 point_event->touch_id = event->touch_id;
92 point_event->ref_lx = lx;
93 point_event->ref_ly = ly;
94 point_event->ref_con_lx = sx;
95 point_event->ref_con_ly = sy;
96
97 wl_list_insert(&e->point_events, &point_event->link);
98
99 wlr_seat_touch_notify_down(seat->wlr_seat, surface, event->time_msec,
100 event->touch_id, sx, sy);
101
102 if (focused_node) {
103 seat_set_focus(seat, focused_node);
104 }
105}
106
107static void handle_touch_cancel(struct sway_seat *seat,
108 struct wlr_touch_cancel_event *event) {
109 struct seatop_down_event *e = seat->seatop_data;
110 struct seatop_touch_point_event *point_event, *tmp;
111
112 wl_list_for_each_safe(point_event, tmp, &e->point_events, link) {
113 if (point_event->touch_id == event->touch_id) {
114 wl_list_remove(&point_event->link);
115 free(point_event);
116 break;
117 }
118 }
119
120 if (e->surface) {
121 wlr_seat_touch_notify_cancel(seat->wlr_seat, e->surface);
122 }
123
124 if (wl_list_empty(&e->point_events)) {
125 seatop_begin_default(seat);
126 }
127}
128
16static void handle_pointer_axis(struct sway_seat *seat, 129static void handle_pointer_axis(struct sway_seat *seat,
17 struct wlr_event_pointer_axis *event) { 130 struct wlr_pointer_axis_event *event) {
18 struct sway_input_device *input_device = 131 struct sway_input_device *input_device =
19 event->device ? event->device->data : NULL; 132 event->pointer ? event->pointer->base.data : NULL;
20 struct input_config *ic = 133 struct input_config *ic =
21 input_device ? input_device_get_config(input_device) : NULL; 134 input_device ? input_device_get_config(input_device) : NULL;
22 float scroll_factor = 135 float scroll_factor =
@@ -24,7 +137,8 @@ static void handle_pointer_axis(struct sway_seat *seat,
24 137
25 wlr_seat_pointer_notify_axis(seat->wlr_seat, event->time_msec, 138 wlr_seat_pointer_notify_axis(seat->wlr_seat, event->time_msec,
26 event->orientation, scroll_factor * event->delta, 139 event->orientation, scroll_factor * event->delta,
27 round(scroll_factor * event->delta_discrete), event->source); 140 roundf(scroll_factor * event->delta_discrete), event->source,
141 event->relative_direction);
28} 142}
29 143
30static void handle_button(struct sway_seat *seat, uint32_t time_msec, 144static void handle_button(struct sway_seat *seat, uint32_t time_msec,
@@ -39,8 +153,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
39 153
40static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 154static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
41 struct seatop_down_event *e = seat->seatop_data; 155 struct seatop_down_event *e = seat->seatop_data;
42 struct sway_container *con = e->con; 156 if (seat_is_input_allowed(seat, e->surface)) {
43 if (seat_is_input_allowed(seat, con->view->surface)) {
44 double moved_x = seat->cursor->cursor->x - e->ref_lx; 157 double moved_x = seat->cursor->cursor->x - e->ref_lx;
45 double moved_y = seat->cursor->cursor->y - e->ref_ly; 158 double moved_y = seat->cursor->cursor->y - e->ref_ly;
46 double sx = e->ref_con_lx + moved_x; 159 double sx = e->ref_con_lx + moved_x;
@@ -61,8 +174,7 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
61static void handle_tablet_tool_motion(struct sway_seat *seat, 174static void handle_tablet_tool_motion(struct sway_seat *seat,
62 struct sway_tablet_tool *tool, uint32_t time_msec) { 175 struct sway_tablet_tool *tool, uint32_t time_msec) {
63 struct seatop_down_event *e = seat->seatop_data; 176 struct seatop_down_event *e = seat->seatop_data;
64 struct sway_container *con = e->con; 177 if (seat_is_input_allowed(seat, e->surface)) {
65 if (seat_is_input_allowed(seat, con->view->surface)) {
66 double moved_x = seat->cursor->cursor->x - e->ref_lx; 178 double moved_x = seat->cursor->cursor->x - e->ref_lx;
67 double moved_y = seat->cursor->cursor->y - e->ref_ly; 179 double moved_y = seat->cursor->cursor->y - e->ref_ly;
68 double sx = e->ref_con_lx + moved_x; 180 double sx = e->ref_con_lx + moved_x;
@@ -71,6 +183,14 @@ static void handle_tablet_tool_motion(struct sway_seat *seat,
71 } 183 }
72} 184}
73 185
186static void handle_destroy(struct wl_listener *listener, void *data) {
187 struct seatop_down_event *e =
188 wl_container_of(listener, e, surface_destroy);
189 if (e) {
190 seatop_begin_default(e->seat);
191 }
192}
193
74static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 194static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
75 struct seatop_down_event *e = seat->seatop_data; 195 struct seatop_down_event *e = seat->seatop_data;
76 if (e->con == con) { 196 if (e->con == con) {
@@ -78,33 +198,63 @@ static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
78 } 198 }
79} 199}
80 200
201static void handle_end(struct sway_seat *seat) {
202 struct seatop_down_event *e = seat->seatop_data;
203 wl_list_remove(&e->surface_destroy.link);
204}
205
81static const struct sway_seatop_impl seatop_impl = { 206static const struct sway_seatop_impl seatop_impl = {
82 .button = handle_button, 207 .button = handle_button,
83 .pointer_motion = handle_pointer_motion, 208 .pointer_motion = handle_pointer_motion,
84 .pointer_axis = handle_pointer_axis, 209 .pointer_axis = handle_pointer_axis,
85 .tablet_tool_tip = handle_tablet_tool_tip, 210 .tablet_tool_tip = handle_tablet_tool_tip,
86 .tablet_tool_motion = handle_tablet_tool_motion, 211 .tablet_tool_motion = handle_tablet_tool_motion,
212 .touch_motion = handle_touch_motion,
213 .touch_up = handle_touch_up,
214 .touch_down = handle_touch_down,
215 .touch_cancel = handle_touch_cancel,
87 .unref = handle_unref, 216 .unref = handle_unref,
217 .end = handle_end,
88 .allow_set_cursor = true, 218 .allow_set_cursor = true,
89}; 219};
90 220
91void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, 221void seatop_begin_down(struct sway_seat *seat, struct sway_container *con,
92 uint32_t time_msec, int sx, int sy) { 222 double sx, double sy) {
223 seatop_begin_down_on_surface(seat, con->view->surface, sx, sy);
224 struct seatop_down_event *e = seat->seatop_data;
225 e->con = con;
226
227 container_raise_floating(con);
228 transaction_commit_dirty();
229}
230
231void seatop_begin_touch_down(struct sway_seat *seat,
232 struct wlr_surface *surface, struct wlr_touch_down_event *event,
233 double sx, double sy, double lx, double ly) {
234 seatop_begin_down_on_surface(seat, surface, sx, sy);
235 handle_touch_down(seat, event, lx, ly);
236}
237
238void seatop_begin_down_on_surface(struct sway_seat *seat,
239 struct wlr_surface *surface, double sx, double sy) {
93 seatop_end(seat); 240 seatop_end(seat);
94 241
95 struct seatop_down_event *e = 242 struct seatop_down_event *e =
96 calloc(1, sizeof(struct seatop_down_event)); 243 calloc(1, sizeof(struct seatop_down_event));
97 if (!e) { 244 if (!sway_assert(e, "Unable to allocate e")) {
98 return; 245 return;
99 } 246 }
100 e->con = con; 247 e->con = NULL;
248 e->seat = seat;
249 e->surface = surface;
250 wl_signal_add(&e->surface->events.destroy, &e->surface_destroy);
251 e->surface_destroy.notify = handle_destroy;
101 e->ref_lx = seat->cursor->cursor->x; 252 e->ref_lx = seat->cursor->cursor->x;
102 e->ref_ly = seat->cursor->cursor->y; 253 e->ref_ly = seat->cursor->cursor->y;
103 e->ref_con_lx = sx; 254 e->ref_con_lx = sx;
104 e->ref_con_ly = sy; 255 e->ref_con_ly = sy;
256 wl_list_init(&e->point_events);
105 257
106 seat->seatop_impl = &seatop_impl; 258 seat->seatop_impl = &seatop_impl;
107 seat->seatop_data = e; 259 seat->seatop_data = e;
108
109 container_raise_floating(con);
110} 260}
diff --git a/sway/input/seatop_move_floating.c b/sway/input/seatop_move_floating.c
index 7f501fc9..21d048ce 100644
--- a/sway/input/seatop_move_floating.c
+++ b/sway/input/seatop_move_floating.c
@@ -1,6 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <wlr/types/wlr_cursor.h> 2#include <wlr/types/wlr_cursor.h>
3#include "sway/desktop.h" 3#include "sway/desktop/transaction.h"
4#include "sway/input/cursor.h" 4#include "sway/input/cursor.h"
5#include "sway/input/seat.h" 5#include "sway/input/seat.h"
6 6
@@ -14,7 +14,8 @@ static void finalize_move(struct sway_seat *seat) {
14 14
15 // We "move" the container to its own location 15 // We "move" the container to its own location
16 // so it discovers its output again. 16 // so it discovers its output again.
17 container_floating_move_to(e->con, e->con->x, e->con->y); 17 container_floating_move_to(e->con, e->con->pending.x, e->con->pending.y);
18 transaction_commit_dirty();
18 19
19 seatop_begin_default(seat); 20 seatop_begin_default(seat);
20} 21}
@@ -37,9 +38,8 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
37static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 38static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
38 struct seatop_move_floating_event *e = seat->seatop_data; 39 struct seatop_move_floating_event *e = seat->seatop_data;
39 struct wlr_cursor *cursor = seat->cursor->cursor; 40 struct wlr_cursor *cursor = seat->cursor->cursor;
40 desktop_damage_whole_container(e->con);
41 container_floating_move_to(e->con, cursor->x - e->dx, cursor->y - e->dy); 41 container_floating_move_to(e->con, cursor->x - e->dx, cursor->y - e->dy);
42 desktop_damage_whole_container(e->con); 42 transaction_commit_dirty();
43} 43}
44 44
45static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 45static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -67,13 +67,14 @@ void seatop_begin_move_floating(struct sway_seat *seat,
67 return; 67 return;
68 } 68 }
69 e->con = con; 69 e->con = con;
70 e->dx = cursor->cursor->x - con->x; 70 e->dx = cursor->cursor->x - con->pending.x;
71 e->dy = cursor->cursor->y - con->y; 71 e->dy = cursor->cursor->y - con->pending.y;
72 72
73 seat->seatop_impl = &seatop_impl; 73 seat->seatop_impl = &seatop_impl;
74 seat->seatop_data = e; 74 seat->seatop_data = e;
75 75
76 container_raise_floating(con); 76 container_raise_floating(con);
77 transaction_commit_dirty();
77 78
78 cursor_set_image(cursor, "grab", NULL); 79 cursor_set_image(cursor, "grab", NULL);
79 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 80 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c
index 704e7270..7de39ff6 100644
--- a/sway/input/seatop_move_tiling.c
+++ b/sway/input/seatop_move_tiling.c
@@ -2,7 +2,7 @@
2#include <limits.h> 2#include <limits.h>
3#include <wlr/types/wlr_cursor.h> 3#include <wlr/types/wlr_cursor.h>
4#include <wlr/util/edges.h> 4#include <wlr/util/edges.h>
5#include "sway/desktop.h" 5#include "sway/desktop/transaction.h"
6#include "sway/input/cursor.h" 6#include "sway/input/cursor.h"
7#include "sway/input/seat.h" 7#include "sway/input/seat.h"
8#include "sway/ipc-server.h" 8#include "sway/ipc-server.h"
@@ -15,31 +15,25 @@
15// Thickness of the dropzone when dragging to the edge of a layout container 15// Thickness of the dropzone when dragging to the edge of a layout container
16#define DROP_LAYOUT_BORDER 30 16#define DROP_LAYOUT_BORDER 30
17 17
18// Thickness of indicator when dropping onto a titlebar. This should be a
19// multiple of 2.
20#define DROP_SPLIT_INDICATOR 10
21
18struct seatop_move_tiling_event { 22struct seatop_move_tiling_event {
19 struct sway_container *con; 23 struct sway_container *con;
20 struct sway_node *target_node; 24 struct sway_node *target_node;
21 enum wlr_edges target_edge; 25 enum wlr_edges target_edge;
22 struct wlr_box drop_box;
23 double ref_lx, ref_ly; // cursor's x/y at start of op 26 double ref_lx, ref_ly; // cursor's x/y at start of op
24 bool threshold_reached; 27 bool threshold_reached;
28 bool split_target;
29 bool insert_after_target;
30 struct wlr_scene_rect *indicator_rect;
25}; 31};
26 32
27static void handle_render(struct sway_seat *seat, 33static void handle_end(struct sway_seat *seat) {
28 struct sway_output *output, pixman_region32_t *damage) {
29 struct seatop_move_tiling_event *e = seat->seatop_data; 34 struct seatop_move_tiling_event *e = seat->seatop_data;
30 if (!e->threshold_reached) { 35 wlr_scene_node_destroy(&e->indicator_rect->node);
31 return; 36 e->indicator_rect = NULL;
32 }
33 if (e->target_node && node_get_output(e->target_node) == output) {
34 float color[4];
35 memcpy(&color, config->border_colors.focused.indicator,
36 sizeof(float) * 4);
37 premultiply_alpha(color, 0.5);
38 struct wlr_box box;
39 memcpy(&box, &e->drop_box, sizeof(struct wlr_box));
40 scale_box(&box, output->wlr_output->scale);
41 render_rect(output, damage, &box, color);
42 }
43} 37}
44 38
45static void handle_motion_prethreshold(struct sway_seat *seat) { 39static void handle_motion_prethreshold(struct sway_seat *seat) {
@@ -60,6 +54,7 @@ static void handle_motion_prethreshold(struct sway_seat *seat) {
60 54
61 // If the threshold has been exceeded, start the actual drag 55 // If the threshold has been exceeded, start the actual drag
62 if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) { 56 if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) {
57 wlr_scene_node_set_enabled(&e->indicator_rect->node, true);
63 e->threshold_reached = true; 58 e->threshold_reached = true;
64 cursor_set_image(seat->cursor, "grab", NULL); 59 cursor_set_image(seat->cursor, "grab", NULL);
65 } 60 }
@@ -91,15 +86,86 @@ static void resize_box(struct wlr_box *box, enum wlr_edges edge,
91 } 86 }
92} 87}
93 88
89static void split_border(double pos, int offset, int len, int n_children,
90 int avoid, int *out_pos, bool *out_after) {
91 int region = 2 * n_children * (pos - offset) / len;
92 // If the cursor is over the right side of a left-adjacent titlebar, or the
93 // left side of a right-adjacent titlebar, it's position when dropped will
94 // be the same. To avoid this, shift the region for adjacent containers.
95 if (avoid >= 0) {
96 if (region == 2 * avoid - 1 || region == 2 * avoid) {
97 region--;
98 } else if (region == 2 * avoid + 1 || region == 2 * avoid + 2) {
99 region++;
100 }
101 }
102
103 int child_index = (region + 1) / 2;
104 *out_after = region % 2;
105 // When dropping at the beginning or end of a container, show the drop
106 // region within the container boundary, otherwise show it on top of the
107 // border between two titlebars.
108 if (child_index == 0) {
109 *out_pos = offset;
110 } else if (child_index == n_children) {
111 *out_pos = offset + len - DROP_SPLIT_INDICATOR;
112 } else {
113 *out_pos = offset + child_index * len / n_children -
114 DROP_SPLIT_INDICATOR / 2;
115 }
116}
117
118static bool split_titlebar(struct sway_node *node, struct sway_container *avoid,
119 struct wlr_cursor *cursor, struct wlr_box *title_box, bool *after) {
120 struct sway_container *con = node->sway_container;
121 struct sway_node *parent = &con->pending.parent->node;
122 int title_height = container_titlebar_height();
123 struct wlr_box box;
124 int n_children, avoid_index;
125 enum sway_container_layout layout =
126 parent ? node_get_layout(parent) : L_NONE;
127 if (layout == L_TABBED || layout == L_STACKED) {
128 node_get_box(parent, &box);
129 n_children = node_get_children(parent)->length;
130 avoid_index = list_find(node_get_children(parent), avoid);
131 } else {
132 node_get_box(node, &box);
133 n_children = 1;
134 avoid_index = -1;
135 }
136 if (layout == L_STACKED && cursor->y < box.y + title_height * n_children) {
137 // Drop into stacked titlebars.
138 title_box->width = box.width;
139 title_box->height = DROP_SPLIT_INDICATOR;
140 title_box->x = box.x;
141 split_border(cursor->y, box.y, title_height * n_children,
142 n_children, avoid_index, &title_box->y, after);
143 return true;
144 } else if (layout != L_STACKED && cursor->y < box.y + title_height) {
145 // Drop into side-by-side titlebars.
146 title_box->width = DROP_SPLIT_INDICATOR;
147 title_box->height = title_height;
148 title_box->y = box.y;
149 split_border(cursor->x, box.x, box.width, n_children,
150 avoid_index, &title_box->x, after);
151 return true;
152 }
153 return false;
154}
155
156static void update_indicator(struct seatop_move_tiling_event *e, struct wlr_box *box) {
157 wlr_scene_node_set_position(&e->indicator_rect->node, box->x, box->y);
158 wlr_scene_rect_set_size(e->indicator_rect, box->width, box->height);
159}
160
94static void handle_motion_postthreshold(struct sway_seat *seat) { 161static void handle_motion_postthreshold(struct sway_seat *seat) {
95 struct seatop_move_tiling_event *e = seat->seatop_data; 162 struct seatop_move_tiling_event *e = seat->seatop_data;
163 e->split_target = false;
96 struct wlr_surface *surface = NULL; 164 struct wlr_surface *surface = NULL;
97 double sx, sy; 165 double sx, sy;
98 struct sway_cursor *cursor = seat->cursor; 166 struct sway_cursor *cursor = seat->cursor;
99 struct sway_node *node = node_at_coords(seat, 167 struct sway_node *node = node_at_coords(seat,
100 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); 168 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
101 // Damage the old location
102 desktop_damage_box(&e->drop_box);
103 169
104 if (!node) { 170 if (!node) {
105 // Eg. hovered over a layer surface such as swaybar 171 // Eg. hovered over a layer surface such as swaybar
@@ -112,41 +178,77 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
112 // Empty workspace 178 // Empty workspace
113 e->target_node = node; 179 e->target_node = node;
114 e->target_edge = WLR_EDGE_NONE; 180 e->target_edge = WLR_EDGE_NONE;
115 workspace_get_box(node->sway_workspace, &e->drop_box); 181
116 desktop_damage_box(&e->drop_box); 182 struct wlr_box drop_box;
183 workspace_get_box(node->sway_workspace, &drop_box);
184 update_indicator(e, &drop_box);
117 return; 185 return;
118 } 186 }
119 187
120 // Deny moving within own workspace if this is the only child 188 // Deny moving within own workspace if this is the only child
121 struct sway_container *con = node->sway_container; 189 struct sway_container *con = node->sway_container;
122 if (workspace_num_tiling_views(e->con->workspace) == 1 && 190 if (workspace_num_tiling_views(e->con->pending.workspace) == 1 &&
123 con->workspace == e->con->workspace) { 191 con->pending.workspace == e->con->pending.workspace) {
124 e->target_node = NULL; 192 e->target_node = NULL;
125 e->target_edge = WLR_EDGE_NONE; 193 e->target_edge = WLR_EDGE_NONE;
126 return; 194 return;
127 } 195 }
128 196
197 struct wlr_box drop_box = {
198 .x = con->pending.content_x,
199 .y = con->pending.content_y,
200 .width = con->pending.content_width,
201 .height = con->pending.content_height,
202 };
203
204 // Check if the cursor is over a tilebar only if the destination
205 // container is not a descendant of the source container.
206 if (!surface && !container_has_ancestor(con, e->con) &&
207 split_titlebar(node, e->con, cursor->cursor,
208 &drop_box, &e->insert_after_target)) {
209 // Don't allow dropping over the source container's titlebar
210 // to give users a chance to cancel a drag operation.
211 if (con == e->con) {
212 e->target_node = NULL;
213 } else {
214 e->target_node = node;
215 e->split_target = true;
216 }
217 e->target_edge = WLR_EDGE_NONE;
218 update_indicator(e, &drop_box);
219 return;
220 }
221
129 // Traverse the ancestors, trying to find a layout container perpendicular 222 // Traverse the ancestors, trying to find a layout container perpendicular
130 // to the edge. Eg. close to the top or bottom of a horiz layout. 223 // to the edge. Eg. close to the top or bottom of a horiz layout.
224 int thresh_top = con->pending.content_y + DROP_LAYOUT_BORDER;
225 int thresh_bottom = con->pending.content_y +
226 con->pending.content_height - DROP_LAYOUT_BORDER;
227 int thresh_left = con->pending.content_x + DROP_LAYOUT_BORDER;
228 int thresh_right = con->pending.content_x +
229 con->pending.content_width - DROP_LAYOUT_BORDER;
131 while (con) { 230 while (con) {
132 enum wlr_edges edge = WLR_EDGE_NONE; 231 enum wlr_edges edge = WLR_EDGE_NONE;
133 enum sway_container_layout layout = container_parent_layout(con); 232 enum sway_container_layout layout = container_parent_layout(con);
134 struct wlr_box parent; 233 struct wlr_box box;
135 con->parent ? container_get_box(con->parent, &parent) : 234 node_get_box(node_get_parent(&con->node), &box);
136 workspace_get_box(con->workspace, &parent);
137 if (layout == L_HORIZ || layout == L_TABBED) { 235 if (layout == L_HORIZ || layout == L_TABBED) {
138 if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { 236 if (cursor->cursor->y < thresh_top) {
139 edge = WLR_EDGE_TOP; 237 edge = WLR_EDGE_TOP;
140 } else if (cursor->cursor->y > parent.y + parent.height 238 box.height = thresh_top - box.y;
141 - DROP_LAYOUT_BORDER) { 239 } else if (cursor->cursor->y > thresh_bottom) {
142 edge = WLR_EDGE_BOTTOM; 240 edge = WLR_EDGE_BOTTOM;
241 box.height = box.y + box.height - thresh_bottom;
242 box.y = thresh_bottom;
143 } 243 }
144 } else if (layout == L_VERT || layout == L_STACKED) { 244 } else if (layout == L_VERT || layout == L_STACKED) {
145 if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { 245 if (cursor->cursor->x < thresh_left) {
146 edge = WLR_EDGE_LEFT; 246 edge = WLR_EDGE_LEFT;
147 } else if (cursor->cursor->x > parent.x + parent.width 247 box.width = thresh_left - box.x;
148 - DROP_LAYOUT_BORDER) { 248 } else if (cursor->cursor->x > thresh_right) {
149 edge = WLR_EDGE_RIGHT; 249 edge = WLR_EDGE_RIGHT;
250 box.width = box.x + box.width - thresh_right;
251 box.x = thresh_right;
150 } 252 }
151 } 253 }
152 if (edge) { 254 if (edge) {
@@ -155,12 +257,10 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
155 e->target_node = node_get_parent(e->target_node); 257 e->target_node = node_get_parent(e->target_node);
156 } 258 }
157 e->target_edge = edge; 259 e->target_edge = edge;
158 node_get_box(e->target_node, &e->drop_box); 260 update_indicator(e, &box);
159 resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER);
160 desktop_damage_box(&e->drop_box);
161 return; 261 return;
162 } 262 }
163 con = con->parent; 263 con = con->pending.parent;
164 } 264 }
165 265
166 // Use the hovered view - but we must be over the actual surface 266 // Use the hovered view - but we must be over the actual surface
@@ -173,23 +273,23 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
173 } 273 }
174 274
175 // Find the closest edge 275 // Find the closest edge
176 size_t thickness = fmin(con->content_width, con->content_height) * 0.3; 276 size_t thickness = fmin(con->pending.content_width, con->pending.content_height) * 0.3;
177 size_t closest_dist = INT_MAX; 277 size_t closest_dist = INT_MAX;
178 size_t dist; 278 size_t dist;
179 e->target_edge = WLR_EDGE_NONE; 279 e->target_edge = WLR_EDGE_NONE;
180 if ((dist = cursor->cursor->y - con->y) < closest_dist) { 280 if ((dist = cursor->cursor->y - con->pending.y) < closest_dist) {
181 closest_dist = dist; 281 closest_dist = dist;
182 e->target_edge = WLR_EDGE_TOP; 282 e->target_edge = WLR_EDGE_TOP;
183 } 283 }
184 if ((dist = cursor->cursor->x - con->x) < closest_dist) { 284 if ((dist = cursor->cursor->x - con->pending.x) < closest_dist) {
185 closest_dist = dist; 285 closest_dist = dist;
186 e->target_edge = WLR_EDGE_LEFT; 286 e->target_edge = WLR_EDGE_LEFT;
187 } 287 }
188 if ((dist = con->x + con->width - cursor->cursor->x) < closest_dist) { 288 if ((dist = con->pending.x + con->pending.width - cursor->cursor->x) < closest_dist) {
189 closest_dist = dist; 289 closest_dist = dist;
190 e->target_edge = WLR_EDGE_RIGHT; 290 e->target_edge = WLR_EDGE_RIGHT;
191 } 291 }
192 if ((dist = con->y + con->height - cursor->cursor->y) < closest_dist) { 292 if ((dist = con->pending.y + con->pending.height - cursor->cursor->y) < closest_dist) {
193 closest_dist = dist; 293 closest_dist = dist;
194 e->target_edge = WLR_EDGE_BOTTOM; 294 e->target_edge = WLR_EDGE_BOTTOM;
195 } 295 }
@@ -199,12 +299,8 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
199 } 299 }
200 300
201 e->target_node = node; 301 e->target_node = node;
202 e->drop_box.x = con->content_x; 302 resize_box(&drop_box, e->target_edge, thickness);
203 e->drop_box.y = con->content_y; 303 update_indicator(e, &drop_box);
204 e->drop_box.width = con->content_width;
205 e->drop_box.height = con->content_height;
206 resize_box(&e->drop_box, e->target_edge, thickness);
207 desktop_damage_box(&e->drop_box);
208} 304}
209 305
210static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 306static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
@@ -214,6 +310,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
214 } else { 310 } else {
215 handle_motion_prethreshold(seat); 311 handle_motion_prethreshold(seat);
216 } 312 }
313 transaction_commit_dirty();
217} 314}
218 315
219static bool is_parallel(enum sway_container_layout layout, 316static bool is_parallel(enum sway_container_layout layout,
@@ -232,14 +329,15 @@ static void finalize_move(struct sway_seat *seat) {
232 } 329 }
233 330
234 struct sway_container *con = e->con; 331 struct sway_container *con = e->con;
235 struct sway_container *old_parent = con->parent; 332 struct sway_container *old_parent = con->pending.parent;
236 struct sway_workspace *old_ws = con->workspace; 333 struct sway_workspace *old_ws = con->pending.workspace;
237 struct sway_node *target_node = e->target_node; 334 struct sway_node *target_node = e->target_node;
238 struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? 335 struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ?
239 target_node->sway_workspace : target_node->sway_container->workspace; 336 target_node->sway_workspace : target_node->sway_container->pending.workspace;
240 enum wlr_edges edge = e->target_edge; 337 enum wlr_edges edge = e->target_edge;
241 int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; 338 int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT;
242 bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER; 339 bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER &&
340 !e->split_target;
243 341
244 if (!swap) { 342 if (!swap) {
245 container_detach(con); 343 container_detach(con);
@@ -248,6 +346,14 @@ static void finalize_move(struct sway_seat *seat) {
248 // Moving container into empty workspace 346 // Moving container into empty workspace
249 if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { 347 if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) {
250 con = workspace_add_tiling(new_ws, con); 348 con = workspace_add_tiling(new_ws, con);
349 } else if (e->split_target) {
350 struct sway_container *target = target_node->sway_container;
351 enum sway_container_layout layout = container_parent_layout(target);
352 if (layout != L_TABBED && layout != L_STACKED) {
353 container_split(target, L_TABBED);
354 }
355 container_add_sibling(target, con, e->insert_after_target);
356 ipc_event_window(con, "move");
251 } else if (target_node->type == N_CONTAINER) { 357 } else if (target_node->type == N_CONTAINER) {
252 // Moving container before/after another 358 // Moving container before/after another
253 struct sway_container *target = target_node->sway_container; 359 struct sway_container *target = target_node->sway_container;
@@ -283,8 +389,8 @@ static void finalize_move(struct sway_seat *seat) {
283 int index = list_find(siblings, con); 389 int index = list_find(siblings, con);
284 struct sway_container *sibling = index == 0 ? 390 struct sway_container *sibling = index == 0 ?
285 siblings->items[1] : siblings->items[index - 1]; 391 siblings->items[1] : siblings->items[index - 1];
286 con->width = sibling->width; 392 con->pending.width = sibling->pending.width;
287 con->height = sibling->height; 393 con->pending.height = sibling->pending.height;
288 con->width_fraction = sibling->width_fraction; 394 con->width_fraction = sibling->width_fraction;
289 con->height_fraction = sibling->height_fraction; 395 con->height_fraction = sibling->height_fraction;
290 } 396 }
@@ -294,6 +400,7 @@ static void finalize_move(struct sway_seat *seat) {
294 arrange_workspace(new_ws); 400 arrange_workspace(new_ws);
295 } 401 }
296 402
403 transaction_commit_dirty();
297 seatop_begin_default(seat); 404 seatop_begin_default(seat);
298} 405}
299 406
@@ -328,7 +435,7 @@ static const struct sway_seatop_impl seatop_impl = {
328 .pointer_motion = handle_pointer_motion, 435 .pointer_motion = handle_pointer_motion,
329 .tablet_tool_tip = handle_tablet_tool_tip, 436 .tablet_tool_tip = handle_tablet_tool_tip,
330 .unref = handle_unref, 437 .unref = handle_unref,
331 .render = handle_render, 438 .end = handle_end,
332}; 439};
333 440
334void seatop_begin_move_tiling_threshold(struct sway_seat *seat, 441void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
@@ -340,6 +447,20 @@ void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
340 if (!e) { 447 if (!e) {
341 return; 448 return;
342 } 449 }
450
451 const float *indicator = config->border_colors.focused.indicator;
452 float color[4] = {
453 indicator[0] * .5,
454 indicator[1] * .5,
455 indicator[2] * .5,
456 indicator[3] * .5,
457 };
458 e->indicator_rect = wlr_scene_rect_create(seat->scene_tree, 0, 0, color);
459 if (!e->indicator_rect) {
460 free(e);
461 return;
462 }
463
343 e->con = con; 464 e->con = con;
344 e->ref_lx = seat->cursor->cursor->x; 465 e->ref_lx = seat->cursor->cursor->x;
345 e->ref_ly = seat->cursor->cursor->y; 466 e->ref_ly = seat->cursor->cursor->y;
@@ -348,6 +469,7 @@ void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
348 seat->seatop_data = e; 469 seat->seatop_data = e;
349 470
350 container_raise_floating(con); 471 container_raise_floating(con);
472 transaction_commit_dirty();
351 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 473 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
352} 474}
353 475
diff --git a/sway/input/seatop_resize_floating.c b/sway/input/seatop_resize_floating.c
index 5da22e47..df683026 100644
--- a/sway/input/seatop_resize_floating.c
+++ b/sway/input/seatop_resize_floating.c
@@ -2,6 +2,7 @@
2#include <limits.h> 2#include <limits.h>
3#include <wlr/types/wlr_cursor.h> 3#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_xcursor_manager.h> 4#include <wlr/types/wlr_xcursor_manager.h>
5#include "sway/desktop/transaction.h"
5#include "sway/input/cursor.h" 6#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 7#include "sway/input/seat.h"
7#include "sway/tree/arrange.h" 8#include "sway/tree/arrange.h"
@@ -27,6 +28,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
27 if (seat->cursor->pressed_button_count == 0) { 28 if (seat->cursor->pressed_button_count == 0) {
28 container_set_resizing(con, false); 29 container_set_resizing(con, false);
29 arrange_container(con); // Send configure w/o resizing hint 30 arrange_container(con); // Send configure w/o resizing hint
31 transaction_commit_dirty();
30 seatop_begin_default(seat); 32 seatop_begin_default(seat);
31 } 33 }
32} 34}
@@ -78,17 +80,25 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
78 double height = e->ref_height + grow_height; 80 double height = e->ref_height + grow_height;
79 int min_width, max_width, min_height, max_height; 81 int min_width, max_width, min_height, max_height;
80 floating_calculate_constraints(&min_width, &max_width, 82 floating_calculate_constraints(&min_width, &max_width,
81 &min_height, &max_height); 83 &min_height, &max_height);
82 width = fmax(min_width + border_width, fmin(width, max_width)); 84 width = fmin(width, max_width - border_width);
83 height = fmax(min_height + border_height, fmin(height, max_height)); 85 width = fmax(width, min_width + border_width);
86 width = fmax(width, 1);
87 height = fmin(height, max_height - border_height);
88 height = fmax(height, min_height + border_height);
89 height = fmax(height, 1);
84 90
85 // Apply the view's min/max size 91 // Apply the view's min/max size
86 if (con->view) { 92 if (con->view) {
87 double view_min_width, view_max_width, view_min_height, view_max_height; 93 double view_min_width, view_max_width, view_min_height, view_max_height;
88 view_get_constraints(con->view, &view_min_width, &view_max_width, 94 view_get_constraints(con->view, &view_min_width, &view_max_width,
89 &view_min_height, &view_max_height); 95 &view_min_height, &view_max_height);
90 width = fmax(view_min_width + border_width, fmin(width, view_max_width)); 96 width = fmin(width, view_max_width - border_width);
91 height = fmax(view_min_height + border_height, fmin(height, view_max_height)); 97 width = fmax(width, view_min_width + border_width);
98 width = fmax(width, 1);
99 height = fmin(height, view_max_height - border_height);
100 height = fmax(height, view_min_height + border_height);
101 height = fmax(height, 1);
92 102
93 } 103 }
94 104
@@ -116,23 +126,24 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
116 126
117 // Determine the amounts we need to bump everything relative to the current 127 // Determine the amounts we need to bump everything relative to the current
118 // size. 128 // size.
119 int relative_grow_width = width - con->width; 129 int relative_grow_width = width - con->pending.width;
120 int relative_grow_height = height - con->height; 130 int relative_grow_height = height - con->pending.height;
121 int relative_grow_x = (e->ref_con_lx + grow_x) - con->x; 131 int relative_grow_x = (e->ref_con_lx + grow_x) - con->pending.x;
122 int relative_grow_y = (e->ref_con_ly + grow_y) - con->y; 132 int relative_grow_y = (e->ref_con_ly + grow_y) - con->pending.y;
123 133
124 // Actually resize stuff 134 // Actually resize stuff
125 con->x += relative_grow_x; 135 con->pending.x += relative_grow_x;
126 con->y += relative_grow_y; 136 con->pending.y += relative_grow_y;
127 con->width += relative_grow_width; 137 con->pending.width += relative_grow_width;
128 con->height += relative_grow_height; 138 con->pending.height += relative_grow_height;
129 139
130 con->content_x += relative_grow_x; 140 con->pending.content_x += relative_grow_x;
131 con->content_y += relative_grow_y; 141 con->pending.content_y += relative_grow_y;
132 con->content_width += relative_grow_width; 142 con->pending.content_width += relative_grow_width;
133 con->content_height += relative_grow_height; 143 con->pending.content_height += relative_grow_height;
134 144
135 arrange_container(con); 145 arrange_container(con);
146 transaction_commit_dirty();
136} 147}
137 148
138static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 149static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -166,16 +177,17 @@ void seatop_begin_resize_floating(struct sway_seat *seat,
166 e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge; 177 e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge;
167 e->ref_lx = seat->cursor->cursor->x; 178 e->ref_lx = seat->cursor->cursor->x;
168 e->ref_ly = seat->cursor->cursor->y; 179 e->ref_ly = seat->cursor->cursor->y;
169 e->ref_con_lx = con->x; 180 e->ref_con_lx = con->pending.x;
170 e->ref_con_ly = con->y; 181 e->ref_con_ly = con->pending.y;
171 e->ref_width = con->width; 182 e->ref_width = con->pending.width;
172 e->ref_height = con->height; 183 e->ref_height = con->pending.height;
173 184
174 seat->seatop_impl = &seatop_impl; 185 seat->seatop_impl = &seatop_impl;
175 seat->seatop_data = e; 186 seat->seatop_data = e;
176 187
177 container_set_resizing(con, true); 188 container_set_resizing(con, true);
178 container_raise_floating(con); 189 container_raise_floating(con);
190 transaction_commit_dirty();
179 191
180 const char *image = edge == WLR_EDGE_NONE ? 192 const char *image = edge == WLR_EDGE_NONE ?
181 "se-resize" : wlr_xcursor_get_resize_name(edge); 193 "se-resize" : wlr_xcursor_get_resize_name(edge);
diff --git a/sway/input/seatop_resize_tiling.c b/sway/input/seatop_resize_tiling.c
index 2cca805d..869d11b5 100644
--- a/sway/input/seatop_resize_tiling.c
+++ b/sway/input/seatop_resize_tiling.c
@@ -2,6 +2,7 @@
2#include <wlr/types/wlr_cursor.h> 2#include <wlr/types/wlr_cursor.h>
3#include <wlr/util/edges.h> 3#include <wlr/util/edges.h>
4#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/desktop/transaction.h"
5#include "sway/input/cursor.h" 6#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 7#include "sway/input/seat.h"
7#include "sway/tree/arrange.h" 8#include "sway/tree/arrange.h"
@@ -52,21 +53,22 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
52 if (e->h_con) { 53 if (e->h_con) {
53 container_set_resizing(e->h_con, false); 54 container_set_resizing(e->h_con, false);
54 container_set_resizing(e->h_sib, false); 55 container_set_resizing(e->h_sib, false);
55 if (e->h_con->parent) { 56 if (e->h_con->pending.parent) {
56 arrange_container(e->h_con->parent); 57 arrange_container(e->h_con->pending.parent);
57 } else { 58 } else {
58 arrange_workspace(e->h_con->workspace); 59 arrange_workspace(e->h_con->pending.workspace);
59 } 60 }
60 } 61 }
61 if (e->v_con) { 62 if (e->v_con) {
62 container_set_resizing(e->v_con, false); 63 container_set_resizing(e->v_con, false);
63 container_set_resizing(e->v_sib, false); 64 container_set_resizing(e->v_sib, false);
64 if (e->v_con->parent) { 65 if (e->v_con->pending.parent) {
65 arrange_container(e->v_con->parent); 66 arrange_container(e->v_con->pending.parent);
66 } else { 67 } else {
67 arrange_workspace(e->v_con->workspace); 68 arrange_workspace(e->v_con->pending.workspace);
68 } 69 }
69 } 70 }
71 transaction_commit_dirty();
70 seatop_begin_default(seat); 72 seatop_begin_default(seat);
71 } 73 }
72} 74}
@@ -80,16 +82,16 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
80 82
81 if (e->h_con) { 83 if (e->h_con) {
82 if (e->edge & WLR_EDGE_LEFT) { 84 if (e->edge & WLR_EDGE_LEFT) {
83 amount_x = (e->h_con_orig_width - moved_x) - e->h_con->width; 85 amount_x = (e->h_con_orig_width - moved_x) - e->h_con->pending.width;
84 } else if (e->edge & WLR_EDGE_RIGHT) { 86 } else if (e->edge & WLR_EDGE_RIGHT) {
85 amount_x = (e->h_con_orig_width + moved_x) - e->h_con->width; 87 amount_x = (e->h_con_orig_width + moved_x) - e->h_con->pending.width;
86 } 88 }
87 } 89 }
88 if (e->v_con) { 90 if (e->v_con) {
89 if (e->edge & WLR_EDGE_TOP) { 91 if (e->edge & WLR_EDGE_TOP) {
90 amount_y = (e->v_con_orig_height - moved_y) - e->v_con->height; 92 amount_y = (e->v_con_orig_height - moved_y) - e->v_con->pending.height;
91 } else if (e->edge & WLR_EDGE_BOTTOM) { 93 } else if (e->edge & WLR_EDGE_BOTTOM) {
92 amount_y = (e->v_con_orig_height + moved_y) - e->v_con->height; 94 amount_y = (e->v_con_orig_height + moved_y) - e->v_con->pending.height;
93 } 95 }
94 } 96 }
95 97
@@ -99,6 +101,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
99 if (amount_y != 0) { 101 if (amount_y != 0) {
100 container_resize_tiled(e->v_con, e->edge_y, amount_y); 102 container_resize_tiled(e->v_con, e->edge_y, amount_y);
101 } 103 }
104 transaction_commit_dirty();
102} 105}
103 106
104static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 107static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -140,7 +143,7 @@ void seatop_begin_resize_tiling(struct sway_seat *seat,
140 if (e->h_con) { 143 if (e->h_con) {
141 container_set_resizing(e->h_con, true); 144 container_set_resizing(e->h_con, true);
142 container_set_resizing(e->h_sib, true); 145 container_set_resizing(e->h_sib, true);
143 e->h_con_orig_width = e->h_con->width; 146 e->h_con_orig_width = e->h_con->pending.width;
144 } 147 }
145 } 148 }
146 if (edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { 149 if (edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) {
@@ -151,12 +154,13 @@ void seatop_begin_resize_tiling(struct sway_seat *seat,
151 if (e->v_con) { 154 if (e->v_con) {
152 container_set_resizing(e->v_con, true); 155 container_set_resizing(e->v_con, true);
153 container_set_resizing(e->v_sib, true); 156 container_set_resizing(e->v_sib, true);
154 e->v_con_orig_height = e->v_con->height; 157 e->v_con_orig_height = e->v_con->pending.height;
155 } 158 }
156 } 159 }
157 160
158 seat->seatop_impl = &seatop_impl; 161 seat->seatop_impl = &seatop_impl;
159 seat->seatop_data = e; 162 seat->seatop_data = e;
160 163
164 transaction_commit_dirty();
161 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 165 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
162} 166}
diff --git a/sway/input/switch.c b/sway/input/switch.c
index b7c28df1..831f4dbf 100644
--- a/sway/input/switch.c
+++ b/sway/input/switch.c
@@ -1,7 +1,5 @@
1#include "sway/config.h" 1#include "sway/config.h"
2#include "sway/desktop/transaction.h"
3#include "sway/input/switch.h" 2#include "sway/input/switch.h"
4#include <wlr/types/wlr_idle.h>
5#include "log.h" 3#include "log.h"
6 4
7struct sway_switch *sway_switch_create(struct sway_seat *seat, 5struct sway_switch *sway_switch_create(struct sway_seat *seat,
@@ -12,6 +10,7 @@ struct sway_switch *sway_switch_create(struct sway_seat *seat,
12 return NULL; 10 return NULL;
13 } 11 }
14 device->switch_device = switch_device; 12 device->switch_device = switch_device;
13 switch_device->wlr = wlr_switch_from_input_device(device->input_device->wlr_device);
15 switch_device->seat_device = device; 14 switch_device->seat_device = device;
16 switch_device->state = WLR_SWITCH_STATE_OFF; 15 switch_device->state = WLR_SWITCH_STATE_OFF;
17 wl_list_init(&switch_device->switch_toggle.link); 16 wl_list_init(&switch_device->switch_toggle.link);
@@ -20,9 +19,22 @@ struct sway_switch *sway_switch_create(struct sway_seat *seat,
20 return switch_device; 19 return switch_device;
21} 20}
22 21
22static bool sway_switch_trigger_test(enum sway_switch_trigger trigger,
23 enum wlr_switch_state state) {
24 switch (trigger) {
25 case SWAY_SWITCH_TRIGGER_ON:
26 return state == WLR_SWITCH_STATE_ON;
27 case SWAY_SWITCH_TRIGGER_OFF:
28 return state == WLR_SWITCH_STATE_OFF;
29 case SWAY_SWITCH_TRIGGER_TOGGLE:
30 return true;
31 }
32 abort(); // unreachable
33}
34
23static void execute_binding(struct sway_switch *sway_switch) { 35static void execute_binding(struct sway_switch *sway_switch) {
24 struct sway_seat* seat = sway_switch->seat_device->sway_seat; 36 struct sway_seat *seat = sway_switch->seat_device->sway_seat;
25 bool input_inhibited = seat->exclusive_client != NULL; 37 bool locked = server.session_lock.lock;
26 38
27 list_t *bindings = config->current_mode->switch_bindings; 39 list_t *bindings = config->current_mode->switch_bindings;
28 struct sway_switch_binding *matched_binding = NULL; 40 struct sway_switch_binding *matched_binding = NULL;
@@ -31,22 +43,21 @@ static void execute_binding(struct sway_switch *sway_switch) {
31 if (binding->type != sway_switch->type) { 43 if (binding->type != sway_switch->type) {
32 continue; 44 continue;
33 } 45 }
34 if (binding->state != WLR_SWITCH_STATE_TOGGLE && 46 if (!sway_switch_trigger_test(binding->trigger, sway_switch->state)) {
35 binding->state != sway_switch->state) {
36 continue; 47 continue;
37 } 48 }
38 if (config->reloading && (binding->state == WLR_SWITCH_STATE_TOGGLE 49 if (config->reloading && (binding->trigger == SWAY_SWITCH_TRIGGER_TOGGLE
39 || (binding->flags & BINDING_RELOAD) == 0)) { 50 || (binding->flags & BINDING_RELOAD) == 0)) {
40 continue; 51 continue;
41 } 52 }
42 bool binding_locked = binding->flags & BINDING_LOCKED; 53 bool binding_locked = binding->flags & BINDING_LOCKED;
43 if (!binding_locked && input_inhibited) { 54 if (!binding_locked && locked) {
44 continue; 55 continue;
45 } 56 }
46 57
47 matched_binding = binding; 58 matched_binding = binding;
48 59
49 if (binding_locked == input_inhibited) { 60 if (binding_locked == locked) {
50 break; 61 break;
51 } 62 }
52 } 63 }
@@ -61,15 +72,12 @@ static void execute_binding(struct sway_switch *sway_switch) {
61 seat_execute_command(seat, dummy_binding); 72 seat_execute_command(seat, dummy_binding);
62 free(dummy_binding); 73 free(dummy_binding);
63 } 74 }
64
65 transaction_commit_dirty();
66
67} 75}
68 76
69static void handle_switch_toggle(struct wl_listener *listener, void *data) { 77static void handle_switch_toggle(struct wl_listener *listener, void *data) {
70 struct sway_switch *sway_switch = 78 struct sway_switch *sway_switch =
71 wl_container_of(listener, sway_switch, switch_toggle); 79 wl_container_of(listener, sway_switch, switch_toggle);
72 struct wlr_event_switch_toggle *event = data; 80 struct wlr_switch_toggle_event *event = data;
73 struct sway_seat *seat = sway_switch->seat_device->sway_seat; 81 struct sway_seat *seat = sway_switch->seat_device->sway_seat;
74 seat_idle_notify_activity(seat, IDLE_SOURCE_SWITCH); 82 seat_idle_notify_activity(seat, IDLE_SOURCE_SWITCH);
75 83
@@ -86,10 +94,8 @@ static void handle_switch_toggle(struct wl_listener *listener, void *data) {
86} 94}
87 95
88void sway_switch_configure(struct sway_switch *sway_switch) { 96void sway_switch_configure(struct sway_switch *sway_switch) {
89 struct wlr_input_device *wlr_device =
90 sway_switch->seat_device->input_device->wlr_device;
91 wl_list_remove(&sway_switch->switch_toggle.link); 97 wl_list_remove(&sway_switch->switch_toggle.link);
92 wl_signal_add(&wlr_device->switch_device->events.toggle, 98 wl_signal_add(&sway_switch->wlr->events.toggle,
93 &sway_switch->switch_toggle); 99 &sway_switch->switch_toggle);
94 sway_switch->switch_toggle.notify = handle_switch_toggle; 100 sway_switch->switch_toggle.notify = handle_switch_toggle;
95 sway_log(SWAY_DEBUG, "Configured switch for device"); 101 sway_log(SWAY_DEBUG, "Configured switch for device");
diff --git a/sway/input/tablet.c b/sway/input/tablet.c
index 26e86e36..902cb7ed 100644
--- a/sway/input/tablet.c
+++ b/sway/input/tablet.c
@@ -1,6 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 2#include <stdlib.h>
3#include <wlr/backend/libinput.h> 3#include <wlr/config.h>
4#include <wlr/types/wlr_tablet_v2.h> 4#include <wlr/types/wlr_tablet_v2.h>
5#include <wlr/types/wlr_tablet_tool.h> 5#include <wlr/types/wlr_tablet_tool.h>
6#include <wlr/types/wlr_tablet_pad.h> 6#include <wlr/types/wlr_tablet_pad.h>
@@ -9,6 +9,10 @@
9#include "sway/input/seat.h" 9#include "sway/input/seat.h"
10#include "sway/input/tablet.h" 10#include "sway/input/tablet.h"
11 11
12#if WLR_HAS_LIBINPUT_BACKEND
13#include <wlr/backend/libinput.h>
14#endif
15
12static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) { 16static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) {
13 struct sway_tablet_pad *pad = 17 struct sway_tablet_pad *pad =
14 wl_container_of(listener, pad, tablet_destroy); 18 wl_container_of(listener, pad, tablet_destroy);
@@ -54,15 +58,14 @@ void sway_configure_tablet(struct sway_tablet *tablet) {
54 tablet->seat_device->input_device->wlr_device; 58 tablet->seat_device->input_device->wlr_device;
55 struct sway_seat *seat = tablet->seat_device->sway_seat; 59 struct sway_seat *seat = tablet->seat_device->sway_seat;
56 60
57 if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { 61 seat_configure_xcursor(seat);
58 seat_configure_xcursor(seat);
59 }
60 62
61 if (!tablet->tablet_v2) { 63 if (!tablet->tablet_v2) {
62 tablet->tablet_v2 = 64 tablet->tablet_v2 =
63 wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device); 65 wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device);
64 } 66 }
65 67
68#if WLR_HAS_LIBINPUT_BACKEND
66 /* Search for a sibling tablet pad */ 69 /* Search for a sibling tablet pad */
67 if (!wlr_input_device_is_libinput(device)) { 70 if (!wlr_input_device_is_libinput(device)) {
68 /* We can only do this on libinput devices */ 71 /* We can only do this on libinput devices */
@@ -87,6 +90,7 @@ void sway_configure_tablet(struct sway_tablet *tablet) {
87 break; 90 break;
88 } 91 }
89 } 92 }
93#endif
90} 94}
91 95
92void sway_tablet_destroy(struct sway_tablet *tablet) { 96void sway_tablet_destroy(struct sway_tablet *tablet) {
@@ -196,7 +200,7 @@ static void handle_tablet_pad_attach(struct wl_listener *listener,
196 200
197static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) { 201static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
198 struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring); 202 struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring);
199 struct wlr_event_tablet_pad_ring *event = data; 203 struct wlr_tablet_pad_ring_event *event = data;
200 204
201 if (!pad->current_surface) { 205 if (!pad->current_surface) {
202 return; 206 return;
@@ -210,7 +214,7 @@ static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
210 214
211static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) { 215static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
212 struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip); 216 struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip);
213 struct wlr_event_tablet_pad_strip *event = data; 217 struct wlr_tablet_pad_strip_event *event = data;
214 218
215 if (!pad->current_surface) { 219 if (!pad->current_surface) {
216 return; 220 return;
@@ -224,7 +228,7 @@ static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
224 228
225static void handle_tablet_pad_button(struct wl_listener *listener, void *data) { 229static void handle_tablet_pad_button(struct wl_listener *listener, void *data) {
226 struct sway_tablet_pad *pad = wl_container_of(listener, pad, button); 230 struct sway_tablet_pad *pad = wl_container_of(listener, pad, button);
227 struct wlr_event_tablet_pad_button *event = data; 231 struct wlr_tablet_pad_button_event *event = data;
228 232
229 if (!pad->current_surface) { 233 if (!pad->current_surface) {
230 return; 234 return;
@@ -246,6 +250,7 @@ struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
246 return NULL; 250 return NULL;
247 } 251 }
248 252
253 tablet_pad->wlr = wlr_tablet_pad_from_input_device(device->input_device->wlr_device);
249 tablet_pad->seat_device = device; 254 tablet_pad->seat_device = device;
250 wl_list_init(&tablet_pad->attach.link); 255 wl_list_init(&tablet_pad->attach.link);
251 wl_list_init(&tablet_pad->button.link); 256 wl_list_init(&tablet_pad->button.link);
@@ -260,40 +265,41 @@ struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
260} 265}
261 266
262void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) { 267void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) {
263 struct wlr_input_device *device = 268 struct wlr_input_device *wlr_device =
264 tablet_pad->seat_device->input_device->wlr_device; 269 tablet_pad->seat_device->input_device->wlr_device;
265 struct sway_seat *seat = tablet_pad->seat_device->sway_seat; 270 struct sway_seat *seat = tablet_pad->seat_device->sway_seat;
266 271
267 if (!tablet_pad->tablet_v2_pad) { 272 if (!tablet_pad->tablet_v2_pad) {
268 tablet_pad->tablet_v2_pad = 273 tablet_pad->tablet_v2_pad =
269 wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, device); 274 wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, wlr_device);
270 } 275 }
271 276
272 wl_list_remove(&tablet_pad->attach.link); 277 wl_list_remove(&tablet_pad->attach.link);
273 tablet_pad->attach.notify = handle_tablet_pad_attach; 278 tablet_pad->attach.notify = handle_tablet_pad_attach;
274 wl_signal_add(&device->tablet_pad->events.attach_tablet, 279 wl_signal_add(&tablet_pad->wlr->events.attach_tablet,
275 &tablet_pad->attach); 280 &tablet_pad->attach);
276 281
277 wl_list_remove(&tablet_pad->button.link); 282 wl_list_remove(&tablet_pad->button.link);
278 tablet_pad->button.notify = handle_tablet_pad_button; 283 tablet_pad->button.notify = handle_tablet_pad_button;
279 wl_signal_add(&device->tablet_pad->events.button, &tablet_pad->button); 284 wl_signal_add(&tablet_pad->wlr->events.button, &tablet_pad->button);
280 285
281 wl_list_remove(&tablet_pad->strip.link); 286 wl_list_remove(&tablet_pad->strip.link);
282 tablet_pad->strip.notify = handle_tablet_pad_strip; 287 tablet_pad->strip.notify = handle_tablet_pad_strip;
283 wl_signal_add(&device->tablet_pad->events.strip, &tablet_pad->strip); 288 wl_signal_add(&tablet_pad->wlr->events.strip, &tablet_pad->strip);
284 289
285 wl_list_remove(&tablet_pad->ring.link); 290 wl_list_remove(&tablet_pad->ring.link);
286 tablet_pad->ring.notify = handle_tablet_pad_ring; 291 tablet_pad->ring.notify = handle_tablet_pad_ring;
287 wl_signal_add(&device->tablet_pad->events.ring, &tablet_pad->ring); 292 wl_signal_add(&tablet_pad->wlr->events.ring, &tablet_pad->ring);
288 293
294#if WLR_HAS_LIBINPUT_BACKEND
289 /* Search for a sibling tablet */ 295 /* Search for a sibling tablet */
290 if (!wlr_input_device_is_libinput(device)) { 296 if (!wlr_input_device_is_libinput(wlr_device)) {
291 /* We can only do this on libinput devices */ 297 /* We can only do this on libinput devices */
292 return; 298 return;
293 } 299 }
294 300
295 struct libinput_device_group *group = 301 struct libinput_device_group *group =
296 libinput_device_get_device_group(wlr_libinput_get_device_handle(device)); 302 libinput_device_get_device_group(wlr_libinput_get_device_handle(wlr_device));
297 struct sway_tablet *tool; 303 struct sway_tablet *tool;
298 wl_list_for_each(tool, &seat->cursor->tablets, link) { 304 wl_list_for_each(tool, &seat->cursor->tablets, link) {
299 struct wlr_input_device *tablet = 305 struct wlr_input_device *tablet =
@@ -310,6 +316,7 @@ void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) {
310 break; 316 break;
311 } 317 }
312 } 318 }
319#endif
313} 320}
314 321
315void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) { 322void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) {
@@ -333,14 +340,10 @@ static void handle_pad_tablet_surface_destroy(struct wl_listener *listener,
333 struct sway_tablet_pad *tablet_pad = 340 struct sway_tablet_pad *tablet_pad =
334 wl_container_of(listener, tablet_pad, surface_destroy); 341 wl_container_of(listener, tablet_pad, surface_destroy);
335 342
336 wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_v2_pad, 343 sway_tablet_pad_set_focus(tablet_pad, NULL);
337 tablet_pad->current_surface);
338 wl_list_remove(&tablet_pad->surface_destroy.link);
339 wl_list_init(&tablet_pad->surface_destroy.link);
340 tablet_pad->current_surface = NULL;
341} 344}
342 345
343void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad, 346void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad,
344 struct wlr_surface *surface) { 347 struct wlr_surface *surface) {
345 if (!tablet_pad || !tablet_pad->tablet) { 348 if (!tablet_pad || !tablet_pad->tablet) {
346 return; 349 return;
@@ -359,7 +362,8 @@ void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
359 tablet_pad->current_surface = NULL; 362 tablet_pad->current_surface = NULL;
360 } 363 }
361 364
362 if (!wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) { 365 if (surface == NULL ||
366 !wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) {
363 return; 367 return;
364 } 368 }
365 369
@@ -367,7 +371,6 @@ void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
367 tablet_pad->tablet->tablet_v2, surface); 371 tablet_pad->tablet->tablet_v2, surface);
368 372
369 tablet_pad->current_surface = surface; 373 tablet_pad->current_surface = surface;
370 wl_list_remove(&tablet_pad->surface_destroy.link);
371 tablet_pad->surface_destroy.notify = handle_pad_tablet_surface_destroy; 374 tablet_pad->surface_destroy.notify = handle_pad_tablet_surface_destroy;
372 wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy); 375 wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy);
373} 376}
diff --git a/sway/input/text_input.c b/sway/input/text_input.c
index f83726ee..58911c2d 100644
--- a/sway/input/text_input.c
+++ b/sway/input/text_input.c
@@ -55,6 +55,35 @@ static void handle_im_commit(struct wl_listener *listener, void *data) {
55 wlr_text_input_v3_send_done(text_input->input); 55 wlr_text_input_v3_send_done(text_input->input);
56} 56}
57 57
58static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, void *data) {
59 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
60 input_method_keyboard_grab_destroy);
61 struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
62 wl_list_remove(&relay->input_method_keyboard_grab_destroy.link);
63
64 if (keyboard_grab->keyboard) {
65 // send modifier state to original client
66 wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat,
67 &keyboard_grab->keyboard->modifiers);
68 }
69}
70
71static void handle_im_grab_keyboard(struct wl_listener *listener, void *data) {
72 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
73 input_method_grab_keyboard);
74 struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
75
76 // send modifier state to grab
77 struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(relay->seat->wlr_seat);
78 wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,
79 active_keyboard);
80
81 wl_signal_add(&keyboard_grab->events.destroy,
82 &relay->input_method_keyboard_grab_destroy);
83 relay->input_method_keyboard_grab_destroy.notify =
84 handle_im_keyboard_grab_destroy;
85}
86
58static void text_input_set_pending_focused_surface( 87static void text_input_set_pending_focused_surface(
59 struct sway_text_input *text_input, struct wlr_surface *surface) { 88 struct sway_text_input *text_input, struct wlr_surface *surface) {
60 wl_list_remove(&text_input->pending_focused_surface_destroy.link); 89 wl_list_remove(&text_input->pending_focused_surface_destroy.link);
@@ -92,13 +121,18 @@ static void relay_send_im_state(struct sway_input_method_relay *relay,
92 return; 121 return;
93 } 122 }
94 // TODO: only send each of those if they were modified 123 // TODO: only send each of those if they were modified
95 wlr_input_method_v2_send_surrounding_text(input_method, 124 if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) {
96 input->current.surrounding.text, input->current.surrounding.cursor, 125 wlr_input_method_v2_send_surrounding_text(input_method,
97 input->current.surrounding.anchor); 126 input->current.surrounding.text, input->current.surrounding.cursor,
127 input->current.surrounding.anchor);
128 }
98 wlr_input_method_v2_send_text_change_cause(input_method, 129 wlr_input_method_v2_send_text_change_cause(input_method,
99 input->current.text_change_cause); 130 input->current.text_change_cause);
100 wlr_input_method_v2_send_content_type(input_method, 131 if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) {
101 input->current.content_type.hint, input->current.content_type.purpose); 132 wlr_input_method_v2_send_content_type(input_method,
133 input->current.content_type.hint,
134 input->current.content_type.purpose);
135 }
102 wlr_input_method_v2_send_done(input_method); 136 wlr_input_method_v2_send_done(input_method);
103 // TODO: pass intent, display popup size 137 // TODO: pass intent, display popup size
104} 138}
@@ -144,6 +178,10 @@ static void handle_text_input_disable(struct wl_listener *listener,
144 void *data) { 178 void *data) {
145 struct sway_text_input *text_input = wl_container_of(listener, text_input, 179 struct sway_text_input *text_input = wl_container_of(listener, text_input,
146 text_input_disable); 180 text_input_disable);
181 if (text_input->input->focused_surface == NULL) {
182 sway_log(SWAY_DEBUG, "Disabling text input, but no longer focused");
183 return;
184 }
147 relay_disable_text_input(text_input->relay, text_input); 185 relay_disable_text_input(text_input->relay, text_input);
148} 186}
149 187
@@ -236,6 +274,9 @@ static void relay_handle_input_method(struct wl_listener *listener,
236 wl_signal_add(&relay->input_method->events.commit, 274 wl_signal_add(&relay->input_method->events.commit,
237 &relay->input_method_commit); 275 &relay->input_method_commit);
238 relay->input_method_commit.notify = handle_im_commit; 276 relay->input_method_commit.notify = handle_im_commit;
277 wl_signal_add(&relay->input_method->events.grab_keyboard,
278 &relay->input_method_grab_keyboard);
279 relay->input_method_grab_keyboard.notify = handle_im_grab_keyboard;
239 wl_signal_add(&relay->input_method->events.destroy, 280 wl_signal_add(&relay->input_method->events.destroy,
240 &relay->input_method_destroy); 281 &relay->input_method_destroy);
241 relay->input_method_destroy.notify = handle_im_destroy; 282 relay->input_method_destroy.notify = handle_im_destroy;
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index fceee84d..58356d4e 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -1,7 +1,12 @@
1#include <ctype.h>
2#include <float.h>
1#include <json.h> 3#include <json.h>
2#include <libevdev/libevdev.h> 4#include <libevdev/libevdev.h>
3#include <stdio.h> 5#include <stdio.h>
4#include <ctype.h> 6#include <wlr/config.h>
7#include <wlr/types/wlr_content_type_v1.h>
8#include <wlr/types/wlr_output.h>
9#include <xkbcommon/xkbcommon.h>
5#include "config.h" 10#include "config.h"
6#include "log.h" 11#include "log.h"
7#include "sway/config.h" 12#include "sway/config.h"
@@ -13,16 +18,30 @@
13#include "sway/input/input-manager.h" 18#include "sway/input/input-manager.h"
14#include "sway/input/cursor.h" 19#include "sway/input/cursor.h"
15#include "sway/input/seat.h" 20#include "sway/input/seat.h"
16#include <wlr/backend/libinput.h>
17#include <wlr/types/wlr_box.h>
18#include <wlr/types/wlr_output.h>
19#include <xkbcommon/xkbcommon.h>
20#include "wlr-layer-shell-unstable-v1-protocol.h" 21#include "wlr-layer-shell-unstable-v1-protocol.h"
21#include "sway/desktop/idle_inhibit_v1.h" 22#include "sway/desktop/idle_inhibit_v1.h"
22 23
24#if WLR_HAS_LIBINPUT_BACKEND
25#include <wlr/backend/libinput.h>
26#endif
27
23static const int i3_output_id = INT32_MAX; 28static const int i3_output_id = INT32_MAX;
24static const int i3_scratch_id = INT32_MAX - 1; 29static const int i3_scratch_id = INT32_MAX - 1;
25 30
31static const char *ipc_json_node_type_description(enum sway_node_type node_type) {
32 switch (node_type) {
33 case N_ROOT:
34 return "root";
35 case N_OUTPUT:
36 return "output";
37 case N_WORKSPACE:
38 return "workspace";
39 case N_CONTAINER:
40 return "con";
41 }
42 return "none";
43}
44
26static const char *ipc_json_layout_description(enum sway_container_layout l) { 45static const char *ipc_json_layout_description(enum sway_container_layout l) {
27 switch (l) { 46 switch (l) {
28 case L_VERT: 47 case L_VERT:
@@ -98,12 +117,43 @@ static const char *ipc_json_output_adaptive_sync_status_description(
98 return "disabled"; 117 return "disabled";
99 case WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED: 118 case WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED:
100 return "enabled"; 119 return "enabled";
101 case WLR_OUTPUT_ADAPTIVE_SYNC_UNKNOWN:
102 return "unknown";
103 } 120 }
104 return NULL; 121 return NULL;
105} 122}
106 123
124static const char *ipc_json_output_mode_aspect_ratio_description(
125 enum wlr_output_mode_aspect_ratio aspect_ratio) {
126 switch (aspect_ratio) {
127 case WLR_OUTPUT_MODE_ASPECT_RATIO_NONE:
128 return "none";
129 case WLR_OUTPUT_MODE_ASPECT_RATIO_4_3:
130 return "4:3";
131 case WLR_OUTPUT_MODE_ASPECT_RATIO_16_9:
132 return "16:9";
133 case WLR_OUTPUT_MODE_ASPECT_RATIO_64_27:
134 return "64:27";
135 case WLR_OUTPUT_MODE_ASPECT_RATIO_256_135:
136 return "256:135";
137 }
138 return NULL;
139}
140
141static json_object *ipc_json_output_mode_description(
142 const struct wlr_output_mode *mode) {
143 const char *pic_ar =
144 ipc_json_output_mode_aspect_ratio_description(mode->picture_aspect_ratio);
145 json_object *mode_object = json_object_new_object();
146 json_object_object_add(mode_object, "width",
147 json_object_new_int(mode->width));
148 json_object_object_add(mode_object, "height",
149 json_object_new_int(mode->height));
150 json_object_object_add(mode_object, "refresh",
151 json_object_new_int(mode->refresh));
152 json_object_object_add(mode_object, "picture_aspect_ratio",
153 json_object_new_string(pic_ar));
154 return mode_object;
155}
156
107#if HAVE_XWAYLAND 157#if HAVE_XWAYLAND
108static const char *ipc_json_xwindow_type_description(struct sway_view *view) { 158static const char *ipc_json_xwindow_type_description(struct sway_view *view) {
109 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; 159 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface;
@@ -156,6 +206,20 @@ static const char *ipc_json_user_idle_inhibitor_description(enum sway_idle_inhib
156 return NULL; 206 return NULL;
157} 207}
158 208
209static const char *ipc_json_content_type_description(enum wp_content_type_v1_type type) {
210 switch (type) {
211 case WP_CONTENT_TYPE_V1_TYPE_NONE:
212 return "none";
213 case WP_CONTENT_TYPE_V1_TYPE_PHOTO:
214 return "photo";
215 case WP_CONTENT_TYPE_V1_TYPE_VIDEO:
216 return "video";
217 case WP_CONTENT_TYPE_V1_TYPE_GAME:
218 return "game";
219 }
220 return NULL;
221}
222
159json_object *ipc_json_get_version(void) { 223json_object *ipc_json_get_version(void) {
160 int major = 0, minor = 0, patch = 0; 224 int major = 0, minor = 0, patch = 0;
161 json_object *version = json_object_new_object(); 225 json_object *version = json_object_new_object();
@@ -189,16 +253,22 @@ static json_object *ipc_json_create_empty_rect(void) {
189 return ipc_json_create_rect(&empty); 253 return ipc_json_create_rect(&empty);
190} 254}
191 255
192static json_object *ipc_json_create_node(int id, char *name, 256static json_object *ipc_json_create_node(int id, const char* type, char *name,
193 bool focused, json_object *focus, struct wlr_box *box) { 257 bool focused, json_object *focus, struct wlr_box *box) {
194 json_object *object = json_object_new_object(); 258 json_object *object = json_object_new_object();
195 259
196 json_object_object_add(object, "id", json_object_new_int(id)); 260 json_object_object_add(object, "id", json_object_new_int(id));
197 json_object_object_add(object, "name", 261 json_object_object_add(object, "type", json_object_new_string(type));
198 name ? json_object_new_string(name) : NULL); 262 json_object_object_add(object, "orientation",
199 json_object_object_add(object, "rect", ipc_json_create_rect(box)); 263 json_object_new_string(
264 ipc_json_orientation_description(L_HORIZ)));
265 json_object_object_add(object, "percent", NULL);
266 json_object_object_add(object, "urgent", json_object_new_boolean(false));
267 json_object_object_add(object, "marks", json_object_new_array());
200 json_object_object_add(object, "focused", json_object_new_boolean(focused)); 268 json_object_object_add(object, "focused", json_object_new_boolean(focused));
201 json_object_object_add(object, "focus", focus); 269 json_object_object_add(object, "layout",
270 json_object_new_string(
271 ipc_json_layout_description(L_HORIZ)));
202 272
203 // set default values to be compatible with i3 273 // set default values to be compatible with i3
204 json_object_object_add(object, "border", 274 json_object_object_add(object, "border",
@@ -206,49 +276,66 @@ static json_object *ipc_json_create_node(int id, char *name,
206 ipc_json_border_description(B_NONE))); 276 ipc_json_border_description(B_NONE)));
207 json_object_object_add(object, "current_border_width", 277 json_object_object_add(object, "current_border_width",
208 json_object_new_int(0)); 278 json_object_new_int(0));
209 json_object_object_add(object, "layout", 279 json_object_object_add(object, "rect", ipc_json_create_rect(box));
210 json_object_new_string(
211 ipc_json_layout_description(L_HORIZ)));
212 json_object_object_add(object, "orientation",
213 json_object_new_string(
214 ipc_json_orientation_description(L_HORIZ)));
215 json_object_object_add(object, "percent", NULL);
216 json_object_object_add(object, "window_rect", ipc_json_create_empty_rect());
217 json_object_object_add(object, "deco_rect", ipc_json_create_empty_rect()); 280 json_object_object_add(object, "deco_rect", ipc_json_create_empty_rect());
281 json_object_object_add(object, "window_rect", ipc_json_create_empty_rect());
218 json_object_object_add(object, "geometry", ipc_json_create_empty_rect()); 282 json_object_object_add(object, "geometry", ipc_json_create_empty_rect());
283 json_object_object_add(object, "name",
284 name ? json_object_new_string(name) : NULL);
219 json_object_object_add(object, "window", NULL); 285 json_object_object_add(object, "window", NULL);
220 json_object_object_add(object, "urgent", json_object_new_boolean(false));
221 json_object_object_add(object, "marks", json_object_new_array());
222 json_object_object_add(object, "fullscreen_mode", json_object_new_int(0));
223 json_object_object_add(object, "nodes", json_object_new_array()); 286 json_object_object_add(object, "nodes", json_object_new_array());
224 json_object_object_add(object, "floating_nodes", json_object_new_array()); 287 json_object_object_add(object, "floating_nodes", json_object_new_array());
288 json_object_object_add(object, "focus", focus);
289 json_object_object_add(object, "fullscreen_mode", json_object_new_int(0));
225 json_object_object_add(object, "sticky", json_object_new_boolean(false)); 290 json_object_object_add(object, "sticky", json_object_new_boolean(false));
226 291
227 return object; 292 return object;
228} 293}
229 294
230static void ipc_json_describe_root(struct sway_root *root, json_object *object) { 295static void ipc_json_describe_wlr_output(struct wlr_output *wlr_output, json_object *object) {
231 json_object_object_add(object, "type", json_object_new_string("root")); 296 json_object_object_add(object, "primary", json_object_new_boolean(false));
297 json_object_object_add(object, "make",
298 json_object_new_string(wlr_output->make ? wlr_output->make : "Unknown"));
299 json_object_object_add(object, "model",
300 json_object_new_string(wlr_output->model ? wlr_output->model : "Unknown"));
301 json_object_object_add(object, "serial",
302 json_object_new_string(wlr_output->serial ? wlr_output->serial : "Unknown"));
303
304 json_object *modes_array = json_object_new_array();
305 struct wlr_output_mode *mode;
306 wl_list_for_each(mode, &wlr_output->modes, link) {
307 json_object *mode_object = json_object_new_object();
308 json_object_object_add(mode_object, "width",
309 json_object_new_int(mode->width));
310 json_object_object_add(mode_object, "height",
311 json_object_new_int(mode->height));
312 json_object_object_add(mode_object, "refresh",
313 json_object_new_int(mode->refresh));
314 json_object_array_add(modes_array, mode_object);
315 }
316 json_object_object_add(object, "modes", modes_array);
232} 317}
233 318
234static void ipc_json_describe_output(struct sway_output *output, 319static void ipc_json_describe_output(struct sway_output *output,
235 json_object *object) { 320 json_object *object) {
321 ipc_json_describe_wlr_output(output->wlr_output, object);
322}
323
324static void ipc_json_describe_enabled_output(struct sway_output *output,
325 json_object *object) {
326 ipc_json_describe_output(output, object);
327
236 struct wlr_output *wlr_output = output->wlr_output; 328 struct wlr_output *wlr_output = output->wlr_output;
237 json_object_object_add(object, "type", json_object_new_string("output")); 329 json_object_object_add(object, "non_desktop", json_object_new_boolean(false));
238 json_object_object_add(object, "active", json_object_new_boolean(true)); 330 json_object_object_add(object, "active", json_object_new_boolean(true));
239 json_object_object_add(object, "dpms", 331 json_object_object_add(object, "dpms",
240 json_object_new_boolean(wlr_output->enabled)); 332 json_object_new_boolean(wlr_output->enabled));
241 json_object_object_add(object, "primary", json_object_new_boolean(false)); 333 json_object_object_add(object, "power",
334 json_object_new_boolean(wlr_output->enabled));
242 json_object_object_add(object, "layout", json_object_new_string("output")); 335 json_object_object_add(object, "layout", json_object_new_string("output"));
243 json_object_object_add(object, "orientation", 336 json_object_object_add(object, "orientation",
244 json_object_new_string( 337 json_object_new_string(
245 ipc_json_orientation_description(L_NONE))); 338 ipc_json_orientation_description(L_NONE)));
246 json_object_object_add(object, "make",
247 json_object_new_string(wlr_output->make));
248 json_object_object_add(object, "model",
249 json_object_new_string(wlr_output->model));
250 json_object_object_add(object, "serial",
251 json_object_new_string(wlr_output->serial));
252 json_object_object_add(object, "scale", 339 json_object_object_add(object, "scale",
253 json_object_new_double(wlr_output->scale)); 340 json_object_new_double(wlr_output->scale));
254 json_object_object_add(object, "scale_filter", 341 json_object_object_add(object, "scale_filter",
@@ -273,25 +360,26 @@ static void ipc_json_describe_output(struct sway_output *output,
273 json_object *modes_array = json_object_new_array(); 360 json_object *modes_array = json_object_new_array();
274 struct wlr_output_mode *mode; 361 struct wlr_output_mode *mode;
275 wl_list_for_each(mode, &wlr_output->modes, link) { 362 wl_list_for_each(mode, &wlr_output->modes, link) {
276 json_object *mode_object = json_object_new_object(); 363 json_object *mode_object =
277 json_object_object_add(mode_object, "width", 364 ipc_json_output_mode_description(mode);
278 json_object_new_int(mode->width));
279 json_object_object_add(mode_object, "height",
280 json_object_new_int(mode->height));
281 json_object_object_add(mode_object, "refresh",
282 json_object_new_int(mode->refresh));
283 json_object_array_add(modes_array, mode_object); 365 json_object_array_add(modes_array, mode_object);
284 } 366 }
285 367
286 json_object_object_add(object, "modes", modes_array); 368 json_object_object_add(object, "modes", modes_array);
287 369
288 json_object *current_mode_object = json_object_new_object(); 370 json_object *current_mode_object;
289 json_object_object_add(current_mode_object, "width", 371 if (wlr_output->current_mode != NULL) {
290 json_object_new_int(wlr_output->width)); 372 current_mode_object =
291 json_object_object_add(current_mode_object, "height", 373 ipc_json_output_mode_description(wlr_output->current_mode);
292 json_object_new_int(wlr_output->height)); 374 } else {
293 json_object_object_add(current_mode_object, "refresh", 375 current_mode_object = json_object_new_object();
294 json_object_new_int(wlr_output->refresh)); 376 json_object_object_add(current_mode_object, "width",
377 json_object_new_int(wlr_output->width));
378 json_object_object_add(current_mode_object, "height",
379 json_object_new_int(wlr_output->height));
380 json_object_object_add(current_mode_object, "refresh",
381 json_object_new_int(wlr_output->refresh));
382 }
295 json_object_object_add(object, "current_mode", current_mode_object); 383 json_object_object_add(object, "current_mode", current_mode_object);
296 384
297 struct sway_node *parent = node_get_parent(&output->node); 385 struct sway_node *parent = node_get_parent(&output->node);
@@ -315,33 +403,15 @@ json_object *ipc_json_describe_disabled_output(struct sway_output *output) {
315 403
316 json_object *object = json_object_new_object(); 404 json_object *object = json_object_new_object();
317 405
406 ipc_json_describe_output(output, object);
407
408 json_object_object_add(object, "non_desktop", json_object_new_boolean(false));
318 json_object_object_add(object, "type", json_object_new_string("output")); 409 json_object_object_add(object, "type", json_object_new_string("output"));
319 json_object_object_add(object, "name", 410 json_object_object_add(object, "name",
320 json_object_new_string(wlr_output->name)); 411 json_object_new_string(wlr_output->name));
321 json_object_object_add(object, "active", json_object_new_boolean(false)); 412 json_object_object_add(object, "active", json_object_new_boolean(false));
322 json_object_object_add(object, "dpms", json_object_new_boolean(false)); 413 json_object_object_add(object, "dpms", json_object_new_boolean(false));
323 json_object_object_add(object, "primary", json_object_new_boolean(false)); 414 json_object_object_add(object, "power", json_object_new_boolean(false));
324 json_object_object_add(object, "make",
325 json_object_new_string(wlr_output->make));
326 json_object_object_add(object, "model",
327 json_object_new_string(wlr_output->model));
328 json_object_object_add(object, "serial",
329 json_object_new_string(wlr_output->serial));
330
331 json_object *modes_array = json_object_new_array();
332 struct wlr_output_mode *mode;
333 wl_list_for_each(mode, &wlr_output->modes, link) {
334 json_object *mode_object = json_object_new_object();
335 json_object_object_add(mode_object, "width",
336 json_object_new_int(mode->width));
337 json_object_object_add(mode_object, "height",
338 json_object_new_int(mode->height));
339 json_object_object_add(mode_object, "refresh",
340 json_object_new_int(mode->refresh));
341 json_object_array_add(modes_array, mode_object);
342 }
343
344 json_object_object_add(object, "modes", modes_array);
345 415
346 json_object_object_add(object, "current_workspace", NULL); 416 json_object_object_add(object, "current_workspace", NULL);
347 417
@@ -357,6 +427,21 @@ json_object *ipc_json_describe_disabled_output(struct sway_output *output) {
357 return object; 427 return object;
358} 428}
359 429
430json_object *ipc_json_describe_non_desktop_output(struct sway_output_non_desktop *output) {
431 struct wlr_output *wlr_output = output->wlr_output;
432
433 json_object *object = json_object_new_object();
434
435 ipc_json_describe_wlr_output(wlr_output, object);
436
437 json_object_object_add(object, "non_desktop", json_object_new_boolean(true));
438 json_object_object_add(object, "type", json_object_new_string("output"));
439 json_object_object_add(object, "name",
440 json_object_new_string(wlr_output->name));
441
442 return object;
443}
444
360static json_object *ipc_json_describe_scratchpad_output(void) { 445static json_object *ipc_json_describe_scratchpad_output(void) {
361 struct wlr_box box; 446 struct wlr_box box;
362 root_get_box(root, &box); 447 root_get_box(root, &box);
@@ -369,11 +454,9 @@ static json_object *ipc_json_describe_scratchpad_output(void) {
369 json_object_new_int(container->node.id)); 454 json_object_new_int(container->node.id));
370 } 455 }
371 456
372 json_object *workspace = ipc_json_create_node(i3_scratch_id, 457 json_object *workspace = ipc_json_create_node(i3_scratch_id, "workspace",
373 "__i3_scratch", false, workspace_focus, &box); 458 "__i3_scratch", false, workspace_focus, &box);
374 json_object_object_add(workspace, "fullscreen_mode", json_object_new_int(1)); 459 json_object_object_add(workspace, "fullscreen_mode", json_object_new_int(1));
375 json_object_object_add(workspace, "type",
376 json_object_new_string("workspace"));
377 460
378 // List all hidden scratchpad containers as floating nodes 461 // List all hidden scratchpad containers as floating nodes
379 json_object *floating_array = json_object_new_array(); 462 json_object *floating_array = json_object_new_array();
@@ -390,10 +473,8 @@ static json_object *ipc_json_describe_scratchpad_output(void) {
390 json_object *output_focus = json_object_new_array(); 473 json_object *output_focus = json_object_new_array();
391 json_object_array_add(output_focus, json_object_new_int(i3_scratch_id)); 474 json_object_array_add(output_focus, json_object_new_int(i3_scratch_id));
392 475
393 json_object *output = ipc_json_create_node(i3_output_id, 476 json_object *output = ipc_json_create_node(i3_output_id, "output",
394 "__i3", false, output_focus, &box); 477 "__i3", false, output_focus, &box);
395 json_object_object_add(output, "type",
396 json_object_new_string("output"));
397 json_object_object_add(output, "layout", 478 json_object_object_add(output, "layout",
398 json_object_new_string("output")); 479 json_object_new_string("output"));
399 480
@@ -423,7 +504,6 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace,
423 json_object_object_add(object, "fullscreen_mode", json_object_new_int(1)); 504 json_object_object_add(object, "fullscreen_mode", json_object_new_int(1));
424 json_object_object_add(object, "output", workspace->output ? 505 json_object_object_add(object, "output", workspace->output ?
425 json_object_new_string(workspace->output->wlr_output->name) : NULL); 506 json_object_new_string(workspace->output->wlr_output->name) : NULL);
426 json_object_object_add(object, "type", json_object_new_string("workspace"));
427 json_object_object_add(object, "urgent", 507 json_object_object_add(object, "urgent",
428 json_object_new_boolean(workspace->urgent)); 508 json_object_new_boolean(workspace->urgent));
429 json_object_object_add(object, "representation", workspace->representation ? 509 json_object_object_add(object, "representation", workspace->representation ?
@@ -448,30 +528,32 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace,
448 528
449static void get_deco_rect(struct sway_container *c, struct wlr_box *deco_rect) { 529static void get_deco_rect(struct sway_container *c, struct wlr_box *deco_rect) {
450 enum sway_container_layout parent_layout = container_parent_layout(c); 530 enum sway_container_layout parent_layout = container_parent_layout(c);
451 bool tab_or_stack = parent_layout == L_TABBED || parent_layout == L_STACKED; 531 list_t *siblings = container_get_siblings(c);
532 bool tab_or_stack = (parent_layout == L_TABBED || parent_layout == L_STACKED)
533 && ((siblings && siblings->length > 1) || !config->hide_lone_tab);
452 if (((!tab_or_stack || container_is_floating(c)) && 534 if (((!tab_or_stack || container_is_floating(c)) &&
453 c->current.border != B_NORMAL) || 535 c->current.border != B_NORMAL) ||
454 c->fullscreen_mode != FULLSCREEN_NONE || 536 c->pending.fullscreen_mode != FULLSCREEN_NONE ||
455 c->workspace == NULL) { 537 c->pending.workspace == NULL) {
456 deco_rect->x = deco_rect->y = deco_rect->width = deco_rect->height = 0; 538 deco_rect->x = deco_rect->y = deco_rect->width = deco_rect->height = 0;
457 return; 539 return;
458 } 540 }
459 541
460 if (c->parent) { 542 if (c->pending.parent) {
461 deco_rect->x = c->x - c->parent->x; 543 deco_rect->x = c->pending.x - c->pending.parent->pending.x;
462 deco_rect->y = c->y - c->parent->y; 544 deco_rect->y = c->pending.y - c->pending.parent->pending.y;
463 } else { 545 } else {
464 deco_rect->x = c->x - c->workspace->x; 546 deco_rect->x = c->pending.x - c->pending.workspace->x;
465 deco_rect->y = c->y - c->workspace->y; 547 deco_rect->y = c->pending.y - c->pending.workspace->y;
466 } 548 }
467 deco_rect->width = c->width; 549 deco_rect->width = c->pending.width;
468 deco_rect->height = container_titlebar_height(); 550 deco_rect->height = container_titlebar_height();
469 551
470 if (!container_is_floating(c)) { 552 if (!container_is_floating(c)) {
471 if (parent_layout == L_TABBED) { 553 if (parent_layout == L_TABBED) {
472 deco_rect->width = c->parent 554 deco_rect->width = c->pending.parent
473 ? c->parent->width / c->parent->children->length 555 ? c->pending.parent->pending.width / c->pending.parent->pending.children->length
474 : c->workspace->width / c->workspace->tiling->length; 556 : c->pending.workspace->width / c->pending.workspace->tiling->length;
475 deco_rect->x += deco_rect->width * container_sibling_index(c); 557 deco_rect->x += deco_rect->width * container_sibling_index(c);
476 } else if (parent_layout == L_STACKED) { 558 } else if (parent_layout == L_STACKED) {
477 if (!c->view) { 559 if (!c->view) {
@@ -494,10 +576,10 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
494 json_object_object_add(object, "visible", json_object_new_boolean(visible)); 576 json_object_object_add(object, "visible", json_object_new_boolean(visible));
495 577
496 struct wlr_box window_box = { 578 struct wlr_box window_box = {
497 c->content_x - c->x, 579 c->pending.content_x - c->pending.x,
498 (c->current.border == B_PIXEL) ? c->current.border_thickness : 0, 580 (c->current.border == B_PIXEL) ? c->pending.content_y - c->pending.y : 0,
499 c->content_width, 581 c->pending.content_width,
500 c->content_height 582 c->pending.content_height
501 }; 583 };
502 584
503 json_object_object_add(object, "window_rect", ipc_json_create_rect(&window_box)); 585 json_object_object_add(object, "window_rect", ipc_json_create_rect(&window_box));
@@ -539,6 +621,16 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
539 621
540 json_object_object_add(object, "idle_inhibitors", idle_inhibitors); 622 json_object_object_add(object, "idle_inhibitors", idle_inhibitors);
541 623
624 enum wp_content_type_v1_type content_type = WP_CONTENT_TYPE_V1_TYPE_NONE;
625 if (c->view->surface != NULL) {
626 content_type = wlr_surface_get_content_type_v1(server.content_type_manager_v1,
627 c->view->surface);
628 }
629 if (content_type != WP_CONTENT_TYPE_V1_TYPE_NONE) {
630 json_object_object_add(object, "content_type",
631 json_object_new_string(ipc_json_content_type_description(content_type)));
632 }
633
542#if HAVE_XWAYLAND 634#if HAVE_XWAYLAND
543 if (c->view->type == SWAY_VIEW_XWAYLAND) { 635 if (c->view->type == SWAY_VIEW_XWAYLAND) {
544 json_object_object_add(object, "window", 636 json_object_object_add(object, "window",
@@ -583,16 +675,18 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
583static void ipc_json_describe_container(struct sway_container *c, json_object *object) { 675static void ipc_json_describe_container(struct sway_container *c, json_object *object) {
584 json_object_object_add(object, "name", 676 json_object_object_add(object, "name",
585 c->title ? json_object_new_string(c->title) : NULL); 677 c->title ? json_object_new_string(c->title) : NULL);
586 json_object_object_add(object, "type", 678 if (container_is_floating(c)) {
587 json_object_new_string(container_is_floating(c) ? "floating_con" : "con")); 679 json_object_object_add(object, "type",
680 json_object_new_string("floating_con"));
681 }
588 682
589 json_object_object_add(object, "layout", 683 json_object_object_add(object, "layout",
590 json_object_new_string( 684 json_object_new_string(
591 ipc_json_layout_description(c->layout))); 685 ipc_json_layout_description(c->pending.layout)));
592 686
593 json_object_object_add(object, "orientation", 687 json_object_object_add(object, "orientation",
594 json_object_new_string( 688 json_object_new_string(
595 ipc_json_orientation_description(c->layout))); 689 ipc_json_orientation_description(c->pending.layout)));
596 690
597 bool urgent = c->view ? 691 bool urgent = c->view ?
598 view_is_urgent(c->view) : container_has_urgent_child(c); 692 view_is_urgent(c->view) : container_has_urgent_child(c);
@@ -600,7 +694,7 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o
600 json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky)); 694 json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky));
601 695
602 json_object_object_add(object, "fullscreen_mode", 696 json_object_object_add(object, "fullscreen_mode",
603 json_object_new_int(c->fullscreen_mode)); 697 json_object_new_int(c->pending.fullscreen_mode));
604 698
605 struct sway_node *parent = node_get_parent(&c->node); 699 struct sway_node *parent = node_get_parent(&c->node);
606 struct wlr_box parent_box = {0, 0, 0, 0}; 700 struct wlr_box parent_box = {0, 0, 0, 0};
@@ -610,8 +704,8 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o
610 } 704 }
611 705
612 if (parent_box.width != 0 && parent_box.height != 0) { 706 if (parent_box.width != 0 && parent_box.height != 0) {
613 double percent = ((double)c->width / parent_box.width) 707 double percent = ((double)c->pending.width / parent_box.width)
614 * ((double)c->height / parent_box.height); 708 * ((double)c->pending.height / parent_box.height);
615 json_object_object_add(object, "percent", json_object_new_double(percent)); 709 json_object_object_add(object, "percent", json_object_new_double(percent));
616 } 710 }
617 711
@@ -692,15 +786,14 @@ json_object *ipc_json_describe_node(struct sway_node *node) {
692 }; 786 };
693 seat_for_each_node(seat, focus_inactive_children_iterator, &data); 787 seat_for_each_node(seat, focus_inactive_children_iterator, &data);
694 788
695 json_object *object = ipc_json_create_node( 789 json_object *object = ipc_json_create_node((int)node->id,
696 (int)node->id, name, focused, focus, &box); 790 ipc_json_node_type_description(node->type), name, focused, focus, &box);
697 791
698 switch (node->type) { 792 switch (node->type) {
699 case N_ROOT: 793 case N_ROOT:
700 ipc_json_describe_root(root, object);
701 break; 794 break;
702 case N_OUTPUT: 795 case N_OUTPUT:
703 ipc_json_describe_output(node->sway_output, object); 796 ipc_json_describe_enabled_output(node->sway_output, object);
704 break; 797 break;
705 case N_CONTAINER: 798 case N_CONTAINER:
706 ipc_json_describe_container(node->sway_container, object); 799 ipc_json_describe_container(node->sway_container, object);
@@ -743,10 +836,10 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) {
743 } 836 }
744 break; 837 break;
745 case N_CONTAINER: 838 case N_CONTAINER:
746 if (node->sway_container->children) { 839 if (node->sway_container->pending.children) {
747 for (i = 0; i < node->sway_container->children->length; ++i) { 840 for (i = 0; i < node->sway_container->pending.children->length; ++i) {
748 struct sway_container *child = 841 struct sway_container *child =
749 node->sway_container->children->items[i]; 842 node->sway_container->pending.children->items[i];
750 json_object_array_add(children, 843 json_object_array_add(children,
751 ipc_json_describe_node_recursive(&child->node)); 844 ipc_json_describe_node_recursive(&child->node));
752 } 845 }
@@ -758,6 +851,7 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) {
758 return object; 851 return object;
759} 852}
760 853
854#if WLR_HAS_LIBINPUT_BACKEND
761static json_object *describe_libinput_device(struct libinput_device *device) { 855static json_object *describe_libinput_device(struct libinput_device *device) {
762 json_object *object = json_object_new_object(); 856 json_object *object = json_object_new_object();
763 857
@@ -841,6 +935,11 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
841 case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: 935 case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE:
842 accel_profile = "adaptive"; 936 accel_profile = "adaptive";
843 break; 937 break;
938#if HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM
939 case LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM:
940 accel_profile = "custom";
941 break;
942#endif
844 } 943 }
845 json_object_object_add(object, "accel_profile", 944 json_object_object_add(object, "accel_profile",
846 json_object_new_string(accel_profile)); 945 json_object_new_string(accel_profile));
@@ -920,6 +1019,17 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
920 uint32_t button = libinput_device_config_scroll_get_button(device); 1019 uint32_t button = libinput_device_config_scroll_get_button(device);
921 json_object_object_add(object, "scroll_button", 1020 json_object_object_add(object, "scroll_button",
922 json_object_new_int(button)); 1021 json_object_new_int(button));
1022 const char *lock = "unknown";
1023 switch (libinput_device_config_scroll_get_button_lock(device)) {
1024 case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED:
1025 lock = "enabled";
1026 break;
1027 case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED:
1028 lock = "disabled";
1029 break;
1030 }
1031 json_object_object_add(object, "scroll_button_lock",
1032 json_object_new_string(lock));
923 } 1033 }
924 } 1034 }
925 1035
@@ -936,6 +1046,19 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
936 json_object_object_add(object, "dwt", json_object_new_string(dwt)); 1046 json_object_object_add(object, "dwt", json_object_new_string(dwt));
937 } 1047 }
938 1048
1049 if (libinput_device_config_dwtp_is_available(device)) {
1050 const char *dwtp = "unknown";
1051 switch (libinput_device_config_dwtp_get_enabled(device)) {
1052 case LIBINPUT_CONFIG_DWTP_ENABLED:
1053 dwtp = "enabled";
1054 break;
1055 case LIBINPUT_CONFIG_DWTP_DISABLED:
1056 dwtp = "disabled";
1057 break;
1058 }
1059 json_object_object_add(object, "dwtp", json_object_new_string(dwtp));
1060 }
1061
939 if (libinput_device_config_calibration_has_matrix(device)) { 1062 if (libinput_device_config_calibration_has_matrix(device)) {
940 float matrix[6]; 1063 float matrix[6];
941 libinput_device_config_calibration_get_matrix(device, matrix); 1064 libinput_device_config_calibration_get_matrix(device, matrix);
@@ -950,6 +1073,7 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
950 1073
951 return object; 1074 return object;
952} 1075}
1076#endif
953 1077
954json_object *ipc_json_describe_input(struct sway_input_device *device) { 1078json_object *ipc_json_describe_input(struct sway_input_device *device) {
955 if (!(sway_assert(device, "Device must not be null"))) { 1079 if (!(sway_assert(device, "Device must not be null"))) {
@@ -971,10 +1095,16 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) {
971 input_device_get_type(device))); 1095 input_device_get_type(device)));
972 1096
973 if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) { 1097 if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) {
974 struct wlr_keyboard *keyboard = device->wlr_device->keyboard; 1098 struct wlr_keyboard *keyboard =
1099 wlr_keyboard_from_input_device(device->wlr_device);
975 struct xkb_keymap *keymap = keyboard->keymap; 1100 struct xkb_keymap *keymap = keyboard->keymap;
976 struct xkb_state *state = keyboard->xkb_state; 1101 struct xkb_state *state = keyboard->xkb_state;
977 1102
1103 json_object_object_add(object, "repeat_delay",
1104 json_object_new_int(keyboard->repeat_info.delay));
1105 json_object_object_add(object, "repeat_rate",
1106 json_object_new_int(keyboard->repeat_info.rate));
1107
978 json_object *layouts_arr = json_object_new_array(); 1108 json_object *layouts_arr = json_object_new_array();
979 json_object_object_add(object, "xkb_layout_names", layouts_arr); 1109 json_object_object_add(object, "xkb_layout_names", layouts_arr);
980 1110
@@ -996,12 +1126,25 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) {
996 } 1126 }
997 } 1127 }
998 1128
1129 if (device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
1130 struct input_config *ic = input_device_get_config(device);
1131 float scroll_factor = 1.0f;
1132 if (ic != NULL && !isnan(ic->scroll_factor) &&
1133 ic->scroll_factor != FLT_MIN) {
1134 scroll_factor = ic->scroll_factor;
1135 }
1136 json_object_object_add(object, "scroll_factor",
1137 json_object_new_double(scroll_factor));
1138 }
1139
1140#if WLR_HAS_LIBINPUT_BACKEND
999 if (wlr_input_device_is_libinput(device->wlr_device)) { 1141 if (wlr_input_device_is_libinput(device->wlr_device)) {
1000 struct libinput_device *libinput_dev; 1142 struct libinput_device *libinput_dev;
1001 libinput_dev = wlr_libinput_get_device_handle(device->wlr_device); 1143 libinput_dev = wlr_libinput_get_device_handle(device->wlr_device);
1002 json_object_object_add(object, "libinput", 1144 json_object_object_add(object, "libinput",
1003 describe_libinput_device(libinput_dev)); 1145 describe_libinput_device(libinput_dev));
1004 } 1146 }
1147#endif
1005 1148
1006 return object; 1149 return object;
1007} 1150}
@@ -1109,7 +1252,9 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
1109 json_object_object_add(json, "verbose", 1252 json_object_object_add(json, "verbose",
1110 json_object_new_boolean(bar->verbose)); 1253 json_object_new_boolean(bar->verbose));
1111 json_object_object_add(json, "pango_markup", 1254 json_object_object_add(json, "pango_markup",
1112 json_object_new_boolean(bar->pango_markup)); 1255 json_object_new_boolean(bar->pango_markup == PANGO_MARKUP_DEFAULT
1256 ? config->pango_markup
1257 : bar->pango_markup));
1113 1258
1114 json_object *colors = json_object_new_object(); 1259 json_object *colors = json_object_new_object();
1115 json_object_object_add(colors, "background", 1260 json_object_object_add(colors, "background",
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index aad9a7b5..9692a77f 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -56,7 +56,6 @@ struct ipc_client {
56 enum ipc_command_type pending_type; 56 enum ipc_command_type pending_type;
57}; 57};
58 58
59struct sockaddr_un *ipc_user_sockaddr(void);
60int ipc_handle_connection(int fd, uint32_t mask, void *data); 59int ipc_handle_connection(int fd, uint32_t mask, void *data);
61int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); 60int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data);
62int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data); 61int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data);
@@ -150,7 +149,6 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
150int ipc_handle_connection(int fd, uint32_t mask, void *data) { 149int ipc_handle_connection(int fd, uint32_t mask, void *data) {
151 (void) fd; 150 (void) fd;
152 struct sway_server *server = data; 151 struct sway_server *server = data;
153 sway_log(SWAY_DEBUG, "Event on IPC listening socket");
154 assert(mask == WL_EVENT_READABLE); 152 assert(mask == WL_EVENT_READABLE);
155 153
156 int client_fd = accept(ipc_socket, NULL, NULL); 154 int client_fd = accept(ipc_socket, NULL, NULL);
@@ -211,13 +209,10 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
211 } 209 }
212 210
213 if (mask & WL_EVENT_HANGUP) { 211 if (mask & WL_EVENT_HANGUP) {
214 sway_log(SWAY_DEBUG, "Client %d hung up", client->fd);
215 ipc_client_disconnect(client); 212 ipc_client_disconnect(client);
216 return 0; 213 return 0;
217 } 214 }
218 215
219 sway_log(SWAY_DEBUG, "Client %d readable", client->fd);
220
221 int read_available; 216 int read_available;
222 if (ioctl(client_fd, FIONREAD, &read_available) == -1) { 217 if (ioctl(client_fd, FIONREAD, &read_available) == -1) {
223 sway_log_errno(SWAY_INFO, "Unable to read IPC socket buffer size"); 218 sway_log_errno(SWAY_INFO, "Unable to read IPC socket buffer size");
@@ -513,6 +508,20 @@ void ipc_event_input(const char *change, struct sway_input_device *device) {
513 json_object_put(json); 508 json_object_put(json);
514} 509}
515 510
511void ipc_event_output(void) {
512 if (!ipc_has_event_listeners(IPC_EVENT_OUTPUT)) {
513 return;
514 }
515 sway_log(SWAY_DEBUG, "Sending output event");
516
517 json_object *json = json_object_new_object();
518 json_object_object_add(json, "change", json_object_new_string("unspecified"));
519
520 const char *json_string = json_object_to_json_string(json);
521 ipc_send_event(json_string, IPC_EVENT_OUTPUT);
522 json_object_put(json);
523}
524
516int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { 525int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
517 struct ipc_client *client = data; 526 struct ipc_client *client = data;
518 527
@@ -523,7 +532,6 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
523 } 532 }
524 533
525 if (mask & WL_EVENT_HANGUP) { 534 if (mask & WL_EVENT_HANGUP) {
526 sway_log(SWAY_DEBUG, "Client %d hung up", client->fd);
527 ipc_client_disconnect(client); 535 ipc_client_disconnect(client);
528 return 0; 536 return 0;
529 } 537 }
@@ -532,8 +540,6 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
532 return 0; 540 return 0;
533 } 541 }
534 542
535 sway_log(SWAY_DEBUG, "Client %d writable", client->fd);
536
537 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); 543 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len);
538 544
539 if (written == -1 && errno == EAGAIN) { 545 if (written == -1 && errno == EAGAIN) {
@@ -687,11 +693,17 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
687 } 693 }
688 struct sway_output *output; 694 struct sway_output *output;
689 wl_list_for_each(output, &root->all_outputs, link) { 695 wl_list_for_each(output, &root->all_outputs, link) {
690 if (!output->enabled && output != root->noop_output) { 696 if (!output->enabled && output != root->fallback_output) {
691 json_object_array_add(outputs, 697 json_object_array_add(outputs,
692 ipc_json_describe_disabled_output(output)); 698 ipc_json_describe_disabled_output(output));
693 } 699 }
694 } 700 }
701
702 for (int i = 0; i < root->non_desktop_outputs->length; i++) {
703 struct sway_output_non_desktop *non_desktop_output = root->non_desktop_outputs->items[i];
704 json_object_array_add(outputs, ipc_json_describe_non_desktop_output(non_desktop_output));
705 }
706
695 const char *json_string = json_object_to_json_string(outputs); 707 const char *json_string = json_object_to_json_string(outputs);
696 ipc_send_reply(client, payload_type, json_string, 708 ipc_send_reply(client, payload_type, json_string,
697 (uint32_t)strlen(json_string)); 709 (uint32_t)strlen(json_string));
@@ -727,6 +739,8 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
727 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); 739 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i));
728 if (strcmp(event_type, "workspace") == 0) { 740 if (strcmp(event_type, "workspace") == 0) {
729 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); 741 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE);
742 } else if (strcmp(event_type, "output") == 0) {
743 client->subscribed_events |= event_mask(IPC_EVENT_OUTPUT);
730 } else if (strcmp(event_type, "barconfig_update") == 0) { 744 } else if (strcmp(event_type, "barconfig_update") == 0) {
731 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); 745 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE);
732 } else if (strcmp(event_type, "bar_state_update") == 0) { 746 } else if (strcmp(event_type, "bar_state_update") == 0) {
@@ -911,7 +925,6 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
911 925
912exit_cleanup: 926exit_cleanup:
913 free(buf); 927 free(buf);
914 return;
915} 928}
916 929
917bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type, 930bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type,
@@ -955,7 +968,5 @@ bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_typ
955 ipc_client_handle_writable, client); 968 ipc_client_handle_writable, client);
956 } 969 }
957 970
958 sway_log(SWAY_DEBUG, "Added IPC reply of type 0x%x to client %d queue: %s",
959 payload_type, client->fd, payload);
960 return true; 971 return true;
961} 972}
diff --git a/sway/lock.c b/sway/lock.c
new file mode 100644
index 00000000..8ad9c3f6
--- /dev/null
+++ b/sway/lock.c
@@ -0,0 +1,354 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h>
3#include <wlr/types/wlr_scene.h>
4#include "log.h"
5#include "sway/input/cursor.h"
6#include "sway/input/keyboard.h"
7#include "sway/input/seat.h"
8#include "sway/layers.h"
9#include "sway/output.h"
10#include "sway/server.h"
11
12struct sway_session_lock_output {
13 struct wlr_scene_tree *tree;
14 struct wlr_scene_rect *background;
15 struct sway_session_lock *lock;
16
17 struct sway_output *output;
18
19 struct wl_list link; // sway_session_lock::outputs
20
21 struct wl_listener destroy;
22 struct wl_listener commit;
23
24 struct wlr_session_lock_surface_v1 *surface;
25
26 // invalid if surface is NULL
27 struct wl_listener surface_destroy;
28 struct wl_listener surface_map;
29};
30
31static void focus_surface(struct sway_session_lock *lock,
32 struct wlr_surface *focused) {
33 lock->focused = focused;
34
35 struct sway_seat *seat;
36 wl_list_for_each(seat, &server.input->seats, link) {
37 seat_set_focus_surface(seat, focused, false);
38 }
39}
40
41static void refocus_output(struct sway_session_lock_output *output) {
42 // Move the seat focus to another surface if one is available
43 if (output->lock->focused == output->surface->surface) {
44 struct wlr_surface *next_focus = NULL;
45
46 struct sway_session_lock_output *candidate;
47 wl_list_for_each(candidate, &output->lock->outputs, link) {
48 if (candidate == output || !candidate->surface) {
49 continue;
50 }
51
52 if (candidate->surface->surface->mapped) {
53 next_focus = candidate->surface->surface;
54 break;
55 }
56 }
57
58 focus_surface(output->lock, next_focus);
59 }
60}
61
62static void handle_surface_map(struct wl_listener *listener, void *data) {
63 struct sway_session_lock_output *surf = wl_container_of(listener, surf, surface_map);
64 if (surf->lock->focused == NULL) {
65 focus_surface(surf->lock, surf->surface->surface);
66 }
67 cursor_rebase_all();
68}
69
70static void handle_surface_destroy(struct wl_listener *listener, void *data) {
71 struct sway_session_lock_output *output =
72 wl_container_of(listener, output, surface_destroy);
73 refocus_output(output);
74
75 sway_assert(output->surface, "Trying to destroy a surface that the lock doesn't think exists");
76 output->surface = NULL;
77 wl_list_remove(&output->surface_destroy.link);
78 wl_list_remove(&output->surface_map.link);
79}
80
81static void lock_output_reconfigure(struct sway_session_lock_output *output) {
82 int width = output->output->width;
83 int height = output->output->height;
84
85 wlr_scene_rect_set_size(output->background, width, height);
86
87 if (output->surface) {
88 wlr_session_lock_surface_v1_configure(output->surface, width, height);
89 }
90}
91
92static void handle_new_surface(struct wl_listener *listener, void *data) {
93 struct sway_session_lock *lock = wl_container_of(listener, lock, new_surface);
94 struct wlr_session_lock_surface_v1 *lock_surface = data;
95 struct sway_output *output = lock_surface->output->data;
96
97 sway_log(SWAY_DEBUG, "new lock layer surface");
98
99 struct sway_session_lock_output *current_lock_output, *lock_output = NULL;
100 wl_list_for_each(current_lock_output, &lock->outputs, link) {
101 if (current_lock_output->output == output) {
102 lock_output = current_lock_output;
103 break;
104 }
105 }
106 sway_assert(lock_output, "Couldn't find output to lock");
107 sway_assert(!lock_output->surface, "Tried to reassign a surface to an existing output");
108
109 lock_output->surface = lock_surface;
110
111 wlr_scene_subsurface_tree_create(lock_output->tree, lock_surface->surface);
112
113 lock_output->surface_destroy.notify = handle_surface_destroy;
114 wl_signal_add(&lock_surface->events.destroy, &lock_output->surface_destroy);
115 lock_output->surface_map.notify = handle_surface_map;
116 wl_signal_add(&lock_surface->surface->events.map, &lock_output->surface_map);
117
118 lock_output_reconfigure(lock_output);
119}
120
121static void sway_session_lock_output_destroy(struct sway_session_lock_output *output) {
122 if (output->surface) {
123 refocus_output(output);
124 wl_list_remove(&output->surface_destroy.link);
125 wl_list_remove(&output->surface_map.link);
126 }
127
128 wl_list_remove(&output->commit.link);
129 wl_list_remove(&output->destroy.link);
130 wl_list_remove(&output->link);
131
132 free(output);
133}
134
135static void lock_node_handle_destroy(struct wl_listener *listener, void *data) {
136 struct sway_session_lock_output *output =
137 wl_container_of(listener, output, destroy);
138 sway_session_lock_output_destroy(output);
139}
140
141static void lock_output_handle_commit(struct wl_listener *listener, void *data) {
142 struct wlr_output_event_commit *event = data;
143 struct sway_session_lock_output *output =
144 wl_container_of(listener, output, commit);
145 if (event->state->committed & (
146 WLR_OUTPUT_STATE_MODE |
147 WLR_OUTPUT_STATE_SCALE |
148 WLR_OUTPUT_STATE_TRANSFORM)) {
149 lock_output_reconfigure(output);
150 }
151}
152
153static struct sway_session_lock_output *session_lock_output_create(
154 struct sway_session_lock *lock, struct sway_output *output) {
155 struct sway_session_lock_output *lock_output = calloc(1, sizeof(*lock_output));
156 if (!lock_output) {
157 sway_log(SWAY_ERROR, "failed to allocate a session lock output");
158 return NULL;
159 }
160
161 struct wlr_scene_tree *tree = wlr_scene_tree_create(output->layers.session_lock);
162 if (!tree) {
163 sway_log(SWAY_ERROR, "failed to allocate a session lock output scene tree");
164 free(lock_output);
165 return NULL;
166 }
167
168 struct wlr_scene_rect *background = wlr_scene_rect_create(tree, 0, 0, (float[4]){
169 lock->abandoned ? 1.f : 0.f,
170 0.f,
171 0.f,
172 1.f,
173 });
174 if (!background) {
175 sway_log(SWAY_ERROR, "failed to allocate a session lock output scene background");
176 wlr_scene_node_destroy(&tree->node);
177 free(lock_output);
178 return NULL;
179 }
180
181 lock_output->output = output;
182 lock_output->tree = tree;
183 lock_output->background = background;
184 lock_output->lock = lock;
185
186 lock_output->destroy.notify = lock_node_handle_destroy;
187 wl_signal_add(&tree->node.events.destroy, &lock_output->destroy);
188
189 lock_output->commit.notify = lock_output_handle_commit;
190 wl_signal_add(&output->wlr_output->events.commit, &lock_output->commit);
191
192 lock_output_reconfigure(lock_output);
193
194 wl_list_insert(&lock->outputs, &lock_output->link);
195
196 return lock_output;
197}
198
199static void sway_session_lock_destroy(struct sway_session_lock* lock) {
200 struct sway_session_lock_output *lock_output, *tmp_lock_output;
201 wl_list_for_each_safe(lock_output, tmp_lock_output, &lock->outputs, link) {
202 // destroying the node will also destroy the whole lock output
203 wlr_scene_node_destroy(&lock_output->tree->node);
204 }
205
206 if (server.session_lock.lock == lock) {
207 server.session_lock.lock = NULL;
208 }
209
210 if (!lock->abandoned) {
211 wl_list_remove(&lock->destroy.link);
212 wl_list_remove(&lock->unlock.link);
213 wl_list_remove(&lock->new_surface.link);
214 }
215
216 free(lock);
217}
218
219static void handle_unlock(struct wl_listener *listener, void *data) {
220 struct sway_session_lock *lock = wl_container_of(listener, lock, unlock);
221 sway_log(SWAY_DEBUG, "session unlocked");
222
223 sway_session_lock_destroy(lock);
224
225 struct sway_seat *seat;
226 wl_list_for_each(seat, &server.input->seats, link) {
227 // copied from seat_set_focus_layer -- deduplicate?
228 struct sway_node *previous = seat_get_focus_inactive(seat, &root->node);
229 if (previous) {
230 // Hack to get seat to re-focus the return value of get_focus
231 seat_set_focus(seat, NULL);
232 seat_set_focus(seat, previous);
233 }
234 }
235
236 // Triggers a refocus of the topmost surface layer if necessary
237 // TODO: Make layer surface focus per-output based on cursor position
238 for (int i = 0; i < root->outputs->length; ++i) {
239 struct sway_output *output = root->outputs->items[i];
240 arrange_layers(output);
241 }
242}
243
244static void handle_abandon(struct wl_listener *listener, void *data) {
245 struct sway_session_lock *lock = wl_container_of(listener, lock, destroy);
246 sway_log(SWAY_INFO, "session lock abandoned");
247
248 struct sway_session_lock_output *lock_output;
249 wl_list_for_each(lock_output, &lock->outputs, link) {
250 wlr_scene_rect_set_color(lock_output->background,
251 (float[4]){ 1.f, 0.f, 0.f, 1.f });
252 }
253
254 lock->abandoned = true;
255 wl_list_remove(&lock->destroy.link);
256 wl_list_remove(&lock->unlock.link);
257 wl_list_remove(&lock->new_surface.link);
258}
259
260static void handle_session_lock(struct wl_listener *listener, void *data) {
261 struct wlr_session_lock_v1 *lock = data;
262 struct wl_client *client = wl_resource_get_client(lock->resource);
263
264 if (server.session_lock.lock) {
265 if (server.session_lock.lock->abandoned) {
266 sway_log(SWAY_INFO, "Replacing abandoned lock");
267 sway_session_lock_destroy(server.session_lock.lock);
268 } else {
269 sway_log(SWAY_ERROR, "Cannot lock an already locked session");
270 wlr_session_lock_v1_destroy(lock);
271 return;
272 }
273 }
274
275 struct sway_session_lock *sway_lock = calloc(1, sizeof(*sway_lock));
276 if (!sway_lock) {
277 sway_log(SWAY_ERROR, "failed to allocate a session lock object");
278 wlr_session_lock_v1_destroy(lock);
279 return;
280 }
281
282 wl_list_init(&sway_lock->outputs);
283
284 sway_log(SWAY_DEBUG, "session locked");
285
286 struct sway_seat *seat;
287 wl_list_for_each(seat, &server.input->seats, link) {
288 seat_unfocus_unless_client(seat, client);
289 }
290
291 struct sway_output *output;
292 wl_list_for_each(output, &root->all_outputs, link) {
293 sway_session_lock_add_output(sway_lock, output);
294 }
295
296 sway_lock->new_surface.notify = handle_new_surface;
297 wl_signal_add(&lock->events.new_surface, &sway_lock->new_surface);
298 sway_lock->unlock.notify = handle_unlock;
299 wl_signal_add(&lock->events.unlock, &sway_lock->unlock);
300 sway_lock->destroy.notify = handle_abandon;
301 wl_signal_add(&lock->events.destroy, &sway_lock->destroy);
302
303 wlr_session_lock_v1_send_locked(lock);
304 server.session_lock.lock = sway_lock;
305}
306
307static void handle_session_lock_destroy(struct wl_listener *listener, void *data) {
308 // if the server shuts down while a lock is active, destroy the lock
309 if (server.session_lock.lock) {
310 sway_session_lock_destroy(server.session_lock.lock);
311 }
312
313 wl_list_remove(&server.session_lock.new_lock.link);
314 wl_list_remove(&server.session_lock.manager_destroy.link);
315
316 server.session_lock.manager = NULL;
317}
318
319void sway_session_lock_add_output(struct sway_session_lock *lock,
320 struct sway_output *output) {
321 struct sway_session_lock_output *lock_output =
322 session_lock_output_create(lock, output);
323
324 // if we run out of memory while trying to lock the screen, the best we
325 // can do is kill the sway process. Security conscious users will have
326 // the sway session fall back to a login shell.
327 if (!lock_output) {
328 sway_log(SWAY_ERROR, "aborting: failed to allocate a lock output");
329 abort();
330 }
331}
332
333bool sway_session_lock_has_surface(struct sway_session_lock *lock,
334 struct wlr_surface *surface) {
335 struct sway_session_lock_output *lock_output;
336 wl_list_for_each(lock_output, &lock->outputs, link) {
337 if (lock_output->surface && lock_output->surface->surface == surface) {
338 return true;
339 }
340 }
341
342 return false;
343}
344
345void sway_session_lock_init(void) {
346 server.session_lock.manager = wlr_session_lock_manager_v1_create(server.wl_display);
347
348 server.session_lock.new_lock.notify = handle_session_lock;
349 server.session_lock.manager_destroy.notify = handle_session_lock_destroy;
350 wl_signal_add(&server.session_lock.manager->events.new_lock,
351 &server.session_lock.new_lock);
352 wl_signal_add(&server.session_lock.manager->events.destroy,
353 &server.session_lock.manager_destroy);
354}
diff --git a/sway/main.c b/sway/main.c
index 0c219fb3..73254dc2 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -6,12 +6,14 @@
6#include <stdio.h> 6#include <stdio.h>
7#include <stdlib.h> 7#include <stdlib.h>
8#include <string.h> 8#include <string.h>
9#include <sys/resource.h>
9#include <sys/stat.h> 10#include <sys/stat.h>
10#include <sys/types.h> 11#include <sys/types.h>
11#include <sys/wait.h> 12#include <sys/wait.h>
12#include <sys/un.h> 13#include <sys/un.h>
13#include <unistd.h> 14#include <unistd.h>
14#include <wlr/util/log.h> 15#include <wlr/util/log.h>
16#include <wlr/version.h>
15#include "sway/commands.h" 17#include "sway/commands.h"
16#include "sway/config.h" 18#include "sway/config.h"
17#include "sway/server.h" 19#include "sway/server.h"
@@ -26,6 +28,7 @@
26 28
27static bool terminate_request = false; 29static bool terminate_request = false;
28static int exit_value = 0; 30static int exit_value = 0;
31static struct rlimit original_nofile_rlimit = {0};
29struct sway_server server = {0}; 32struct sway_server server = {0};
30struct sway_debug debug = {0}; 33struct sway_debug debug = {0};
31 34
@@ -46,81 +49,6 @@ void sig_handler(int signal) {
46 sway_terminate(EXIT_SUCCESS); 49 sway_terminate(EXIT_SUCCESS);
47} 50}
48 51
49void detect_raspi(void) {
50 bool raspi = false;
51 FILE *f = fopen("/sys/firmware/devicetree/base/model", "r");
52 if (!f) {
53 return;
54 }
55 char *line = NULL;
56 size_t line_size = 0;
57 while (getline(&line, &line_size, f) != -1) {
58 if (strstr(line, "Raspberry Pi")) {
59 raspi = true;
60 break;
61 }
62 }
63 fclose(f);
64 FILE *g = fopen("/proc/modules", "r");
65 if (!g) {
66 free(line);
67 return;
68 }
69 bool vc4 = false;
70 while (getline(&line, &line_size, g) != -1) {
71 if (strstr(line, "vc4")) {
72 vc4 = true;
73 break;
74 }
75 }
76 free(line);
77 fclose(g);
78 if (!vc4 && raspi) {
79 fprintf(stderr, "\x1B[1;31mWarning: You have a "
80 "Raspberry Pi, but the vc4 Module is "
81 "not loaded! Set 'dtoverlay=vc4-kms-v3d'"
82 "in /boot/config.txt and reboot.\x1B[0m\n");
83 }
84}
85
86void detect_proprietary(int allow_unsupported_gpu) {
87 FILE *f = fopen("/proc/modules", "r");
88 if (!f) {
89 return;
90 }
91 char *line = NULL;
92 size_t line_size = 0;
93 while (getline(&line, &line_size, f) != -1) {
94 if (strncmp(line, "nvidia ", 7) == 0) {
95 if (allow_unsupported_gpu) {
96 sway_log(SWAY_ERROR,
97 "!!! Proprietary Nvidia drivers are in use !!!");
98 } else {
99 sway_log(SWAY_ERROR,
100 "Proprietary Nvidia drivers are NOT supported. "
101 "Use Nouveau. To launch sway anyway, launch with "
102 "--my-next-gpu-wont-be-nvidia and DO NOT report issues.");
103 exit(EXIT_FAILURE);
104 }
105 break;
106 }
107 if (strstr(line, "fglrx")) {
108 if (allow_unsupported_gpu) {
109 sway_log(SWAY_ERROR,
110 "!!! Proprietary AMD drivers are in use !!!");
111 } else {
112 sway_log(SWAY_ERROR, "Proprietary AMD drivers do NOT support "
113 "Wayland. Use radeon. To try anyway, launch sway with "
114 "--unsupported-gpu and DO NOT report issues.");
115 exit(EXIT_FAILURE);
116 }
117 break;
118 }
119 }
120 free(line);
121 fclose(f);
122}
123
124void run_as_ipc_client(char *command, char *socket_path) { 52void run_as_ipc_client(char *command, char *socket_path) {
125 int socketfd = ipc_open_socket(socket_path); 53 int socketfd = ipc_open_socket(socket_path);
126 uint32_t len = strlen(command); 54 uint32_t len = strlen(command);
@@ -184,33 +112,49 @@ static void log_kernel(void) {
184 pclose(f); 112 pclose(f);
185} 113}
186 114
187 115static bool detect_suid(void) {
188static bool drop_permissions(void) { 116 if (geteuid() != 0 && getegid() != 0) {
189 if (getuid() != geteuid() || getgid() != getegid()) { 117 return false;
190 // Set the gid and uid in the correct order.
191 if (setgid(getgid()) != 0) {
192 sway_log(SWAY_ERROR, "Unable to drop root group, refusing to start");
193 return false;
194 }
195 if (setuid(getuid()) != 0) {
196 sway_log(SWAY_ERROR, "Unable to drop root user, refusing to start");
197 return false;
198 }
199 } 118 }
200 if (setgid(0) != -1 || setuid(0) != -1) { 119
201 sway_log(SWAY_ERROR, "Unable to drop root (we shouldn't be able to " 120 if (getuid() == geteuid() && getgid() == getegid()) {
202 "restore it after setuid), refusing to start");
203 return false; 121 return false;
204 } 122 }
123
124 sway_log(SWAY_ERROR, "SUID operation is no longer supported, refusing to start. "
125 "This check will be removed in a future release.");
205 return true; 126 return true;
206} 127}
207 128
129static void increase_nofile_limit(void) {
130 if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) {
131 sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: "
132 "getrlimit(NOFILE) failed");
133 return;
134 }
135
136 struct rlimit new_rlimit = original_nofile_rlimit;
137 new_rlimit.rlim_cur = new_rlimit.rlim_max;
138 if (setrlimit(RLIMIT_NOFILE, &new_rlimit) != 0) {
139 sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: "
140 "setrlimit(NOFILE) failed");
141 sway_log(SWAY_INFO, "Running with %d max open files",
142 (int)original_nofile_rlimit.rlim_cur);
143 }
144}
145
146void restore_nofile_limit(void) {
147 if (original_nofile_rlimit.rlim_cur == 0) {
148 return;
149 }
150 if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) {
151 sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: "
152 "setrlimit(NOFILE) failed");
153 }
154}
155
208void enable_debug_flag(const char *flag) { 156void enable_debug_flag(const char *flag) {
209 if (strcmp(flag, "damage=highlight") == 0) { 157 if (strcmp(flag, "noatomic") == 0) {
210 debug.damage = DAMAGE_HIGHLIGHT;
211 } else if (strcmp(flag, "damage=rerender") == 0) {
212 debug.damage = DAMAGE_RERENDER;
213 } else if (strcmp(flag, "noatomic") == 0) {
214 debug.noatomic = true; 158 debug.noatomic = true;
215 } else if (strcmp(flag, "txn-wait") == 0) { 159 } else if (strcmp(flag, "txn-wait") == 0) {
216 debug.txn_wait = true; 160 debug.txn_wait = true;
@@ -218,6 +162,8 @@ void enable_debug_flag(const char *flag) {
218 debug.txn_timings = true; 162 debug.txn_timings = true;
219 } else if (strncmp(flag, "txn-timeout=", 12) == 0) { 163 } else if (strncmp(flag, "txn-timeout=", 12) == 0) {
220 server.txn_timeout_ms = atoi(&flag[12]); 164 server.txn_timeout_ms = atoi(&flag[12]);
165 } else if (strcmp(flag, "legacy-wl-drm") == 0) {
166 debug.legacy_wl_drm = true;
221 } else { 167 } else {
222 sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag); 168 sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag);
223 } 169 }
@@ -242,36 +188,35 @@ static void handle_wlr_log(enum wlr_log_importance importance,
242 _sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args); 188 _sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args);
243} 189}
244 190
191static const struct option long_options[] = {
192 {"help", no_argument, NULL, 'h'},
193 {"config", required_argument, NULL, 'c'},
194 {"validate", no_argument, NULL, 'C'},
195 {"debug", no_argument, NULL, 'd'},
196 {"version", no_argument, NULL, 'v'},
197 {"verbose", no_argument, NULL, 'V'},
198 {"get-socketpath", no_argument, NULL, 'p'},
199 {"unsupported-gpu", no_argument, NULL, 'u'},
200 {0, 0, 0, 0}
201};
202
203static const char usage[] =
204 "Usage: sway [options] [command]\n"
205 "\n"
206 " -h, --help Show help message and quit.\n"
207 " -c, --config <config> Specify a config file.\n"
208 " -C, --validate Check the validity of the config file, then exit.\n"
209 " -d, --debug Enables full logging, including debug information.\n"
210 " -v, --version Show the version number and quit.\n"
211 " -V, --verbose Enables more verbose logging.\n"
212 " --get-socketpath Gets the IPC socket path and prints it, then exits.\n"
213 "\n";
214
245int main(int argc, char **argv) { 215int main(int argc, char **argv) {
246 static int verbose = 0, debug = 0, validate = 0, allow_unsupported_gpu = 0; 216 static bool verbose = false, debug = false, validate = false;
247
248 static struct option long_options[] = {
249 {"help", no_argument, NULL, 'h'},
250 {"config", required_argument, NULL, 'c'},
251 {"validate", no_argument, NULL, 'C'},
252 {"debug", no_argument, NULL, 'd'},
253 {"version", no_argument, NULL, 'v'},
254 {"verbose", no_argument, NULL, 'V'},
255 {"get-socketpath", no_argument, NULL, 'p'},
256 {"unsupported-gpu", no_argument, NULL, 'u'},
257 {"my-next-gpu-wont-be-nvidia", no_argument, NULL, 'u'},
258 {0, 0, 0, 0}
259 };
260 217
261 char *config_path = NULL; 218 char *config_path = NULL;
262 219
263 const char* usage =
264 "Usage: sway [options] [command]\n"
265 "\n"
266 " -h, --help Show help message and quit.\n"
267 " -c, --config <config> Specify a config file.\n"
268 " -C, --validate Check the validity of the config file, then exit.\n"
269 " -d, --debug Enables full logging, including debug information.\n"
270 " -v, --version Show the version number and quit.\n"
271 " -V, --verbose Enables more verbose logging.\n"
272 " --get-socketpath Gets the IPC socket path and prints it, then exits.\n"
273 "\n";
274
275 int c; 220 int c;
276 while (1) { 221 while (1) {
277 int option_index = 0; 222 int option_index = 0;
@@ -289,25 +234,25 @@ int main(int argc, char **argv) {
289 config_path = strdup(optarg); 234 config_path = strdup(optarg);
290 break; 235 break;
291 case 'C': // validate 236 case 'C': // validate
292 validate = 1; 237 validate = true;
293 break; 238 break;
294 case 'd': // debug 239 case 'd': // debug
295 debug = 1; 240 debug = true;
296 break; 241 break;
297 case 'D': // extended debug options 242 case 'D': // extended debug options
298 enable_debug_flag(optarg); 243 enable_debug_flag(optarg);
299 break; 244 break;
300 case 'u': 245 case 'u':
301 allow_unsupported_gpu = 1; 246 allow_unsupported_gpu = true;
302 break; 247 break;
303 case 'v': // version 248 case 'v': // version
304 printf("sway version " SWAY_VERSION "\n"); 249 printf("sway version " SWAY_VERSION "\n");
305 exit(EXIT_SUCCESS); 250 exit(EXIT_SUCCESS);
306 break; 251 break;
307 case 'V': // verbose 252 case 'V': // verbose
308 verbose = 1; 253 verbose = true;
309 break; 254 break;
310 case 'p': ; // --get-socketpath 255 case 'p': // --get-socketpath
311 if (getenv("SWAYSOCK")) { 256 if (getenv("SWAYSOCK")) {
312 printf("%s\n", getenv("SWAYSOCK")); 257 printf("%s\n", getenv("SWAYSOCK"));
313 exit(EXIT_SUCCESS); 258 exit(EXIT_SUCCESS);
@@ -322,6 +267,11 @@ int main(int argc, char **argv) {
322 } 267 }
323 } 268 }
324 269
270 // SUID operation is deprecated, so block it for now.
271 if (detect_suid()) {
272 exit(EXIT_FAILURE);
273 }
274
325 // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the 275 // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the
326 // clear error message (when not running as an IPC client). 276 // clear error message (when not running as an IPC client).
327 if (!getenv("XDG_RUNTIME_DIR") && optind == argc) { 277 if (!getenv("XDG_RUNTIME_DIR") && optind == argc) {
@@ -344,11 +294,10 @@ int main(int argc, char **argv) {
344 } 294 }
345 295
346 sway_log(SWAY_INFO, "Sway version " SWAY_VERSION); 296 sway_log(SWAY_INFO, "Sway version " SWAY_VERSION);
297 sway_log(SWAY_INFO, "wlroots version " WLR_VERSION_STR);
347 log_kernel(); 298 log_kernel();
348 log_distro(); 299 log_distro();
349 log_env(); 300 log_env();
350 detect_proprietary(allow_unsupported_gpu);
351 detect_raspi();
352 301
353 if (optind < argc) { // Behave as IPC client 302 if (optind < argc) { // Behave as IPC client
354 if (optind != 1) { 303 if (optind != 1) {
@@ -361,9 +310,6 @@ int main(int argc, char **argv) {
361 "`sway -d 2>sway.log`."); 310 "`sway -d 2>sway.log`.");
362 exit(EXIT_FAILURE); 311 exit(EXIT_FAILURE);
363 } 312 }
364 if (!drop_permissions()) {
365 exit(EXIT_FAILURE);
366 }
367 char *socket_path = getenv("SWAYSOCK"); 313 char *socket_path = getenv("SWAYSOCK");
368 if (!socket_path) { 314 if (!socket_path) {
369 sway_log(SWAY_ERROR, "Unable to retrieve socket path"); 315 sway_log(SWAY_ERROR, "Unable to retrieve socket path");
@@ -375,14 +321,7 @@ int main(int argc, char **argv) {
375 return 0; 321 return 0;
376 } 322 }
377 323
378 if (!server_privileged_prepare(&server)) { 324 increase_nofile_limit();
379 return 1;
380 }
381
382 if (!drop_permissions()) {
383 server_fini(&server);
384 exit(EXIT_FAILURE);
385 }
386 325
387 // handle SIGTERM signals 326 // handle SIGTERM signals
388 signal(SIGTERM, sig_handler); 327 signal(SIGTERM, sig_handler);
@@ -393,12 +332,14 @@ int main(int argc, char **argv) {
393 332
394 sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION); 333 sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION);
395 334
396 root = root_create();
397
398 if (!server_init(&server)) { 335 if (!server_init(&server)) {
399 return 1; 336 return 1;
400 } 337 }
401 338
339 if (server.linux_dmabuf_v1) {
340 wlr_scene_set_linux_dmabuf_v1(root->root_scene, server.linux_dmabuf_v1);
341 }
342
402 if (validate) { 343 if (validate) {
403 bool valid = load_main_config(config_path, false, true); 344 bool valid = load_main_config(config_path, false, true);
404 free(config_path); 345 free(config_path);
@@ -413,6 +354,8 @@ int main(int argc, char **argv) {
413 goto shutdown; 354 goto shutdown;
414 } 355 }
415 356
357 set_rr_scheduling();
358
416 if (!server_start(&server)) { 359 if (!server_start(&server)) {
417 sway_terminate(EXIT_FAILURE); 360 sway_terminate(EXIT_FAILURE);
418 goto shutdown; 361 goto shutdown;
diff --git a/sway/meson.build b/sway/meson.build
index 6e138101..d937e425 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -5,24 +5,26 @@ sway_sources = files(
5 'decoration.c', 5 'decoration.c',
6 'ipc-json.c', 6 'ipc-json.c',
7 'ipc-server.c', 7 'ipc-server.c',
8 'lock.c',
8 'main.c', 9 'main.c',
10 'realtime.c',
11 'scene_descriptor.c',
9 'server.c', 12 'server.c',
13 'sway_text_node.c',
10 'swaynag.c', 14 'swaynag.c',
15 'xdg_activation_v1.c',
11 'xdg_decoration.c', 16 'xdg_decoration.c',
12 17
13 'desktop/desktop.c',
14 'desktop/idle_inhibit_v1.c', 18 'desktop/idle_inhibit_v1.c',
15 'desktop/layer_shell.c', 19 'desktop/layer_shell.c',
16 'desktop/output.c', 20 'desktop/output.c',
17 'desktop/render.c',
18 'desktop/surface.c',
19 'desktop/transaction.c', 21 'desktop/transaction.c',
20 'desktop/xdg_shell.c', 22 'desktop/xdg_shell.c',
23 'desktop/launcher.c',
21 24
22 'input/input-manager.c', 25 'input/input-manager.c',
23 'input/cursor.c', 26 'input/cursor.c',
24 'input/keyboard.c', 27 'input/keyboard.c',
25 'input/libinput.c',
26 'input/seat.c', 28 'input/seat.c',
27 'input/seatop_default.c', 29 'input/seatop_default.c',
28 'input/seatop_down.c', 30 'input/seatop_down.c',
@@ -64,6 +66,7 @@ sway_sources = files(
64 'commands/force_focus_wrapping.c', 66 'commands/force_focus_wrapping.c',
65 'commands/fullscreen.c', 67 'commands/fullscreen.c',
66 'commands/gaps.c', 68 'commands/gaps.c',
69 'commands/gesture.c',
67 'commands/hide_edge_borders.c', 70 'commands/hide_edge_borders.c',
68 'commands/inhibit_idle.c', 71 'commands/inhibit_idle.c',
69 'commands/kill.c', 72 'commands/kill.c',
@@ -82,6 +85,7 @@ sway_sources = files(
82 'commands/nop.c', 85 'commands/nop.c',
83 'commands/output.c', 86 'commands/output.c',
84 'commands/popup_during_fullscreen.c', 87 'commands/popup_during_fullscreen.c',
88 'commands/primary_selection.c',
85 'commands/reload.c', 89 'commands/reload.c',
86 'commands/rename.c', 90 'commands/rename.c',
87 'commands/resize.c', 91 'commands/resize.c',
@@ -153,6 +157,7 @@ sway_sources = files(
153 'commands/input/drag.c', 157 'commands/input/drag.c',
154 'commands/input/drag_lock.c', 158 'commands/input/drag_lock.c',
155 'commands/input/dwt.c', 159 'commands/input/dwt.c',
160 'commands/input/dwtp.c',
156 'commands/input/events.c', 161 'commands/input/events.c',
157 'commands/input/left_handed.c', 162 'commands/input/left_handed.c',
158 'commands/input/map_from_region.c', 163 'commands/input/map_from_region.c',
@@ -161,9 +166,11 @@ sway_sources = files(
161 'commands/input/middle_emulation.c', 166 'commands/input/middle_emulation.c',
162 'commands/input/natural_scroll.c', 167 'commands/input/natural_scroll.c',
163 'commands/input/pointer_accel.c', 168 'commands/input/pointer_accel.c',
169 'commands/input/rotation_angle.c',
164 'commands/input/repeat_delay.c', 170 'commands/input/repeat_delay.c',
165 'commands/input/repeat_rate.c', 171 'commands/input/repeat_rate.c',
166 'commands/input/scroll_button.c', 172 'commands/input/scroll_button.c',
173 'commands/input/scroll_button_lock.c',
167 'commands/input/scroll_factor.c', 174 'commands/input/scroll_factor.c',
168 'commands/input/scroll_method.c', 175 'commands/input/scroll_method.c',
169 'commands/input/tap.c', 176 'commands/input/tap.c',
@@ -187,11 +194,14 @@ sway_sources = files(
187 'commands/output/max_render_time.c', 194 'commands/output/max_render_time.c',
188 'commands/output/mode.c', 195 'commands/output/mode.c',
189 'commands/output/position.c', 196 'commands/output/position.c',
197 'commands/output/power.c',
198 'commands/output/render_bit_depth.c',
190 'commands/output/scale.c', 199 'commands/output/scale.c',
191 'commands/output/scale_filter.c', 200 'commands/output/scale_filter.c',
192 'commands/output/subpixel.c', 201 'commands/output/subpixel.c',
193 'commands/output/toggle.c', 202 'commands/output/toggle.c',
194 'commands/output/transform.c', 203 'commands/output/transform.c',
204 'commands/output/unplug.c',
195 205
196 'tree/arrange.c', 206 'tree/arrange.c',
197 'tree/container.c', 207 'tree/container.c',
@@ -204,28 +214,34 @@ sway_sources = files(
204 214
205sway_deps = [ 215sway_deps = [
206 cairo, 216 cairo,
217 drm,
207 jsonc, 218 jsonc,
208 libevdev, 219 libevdev,
209 libinput, 220 libinput,
221 libudev,
210 math, 222 math,
211 pango, 223 pango,
212 pcre, 224 pcre2,
213 glesv2,
214 pixman, 225 pixman,
215 server_protos, 226 threads,
216 wayland_server, 227 wayland_server,
217 wlroots, 228 wlroots,
218 xkbcommon, 229 xkbcommon,
230 xcb,
231 xcb_icccm,
219] 232]
220 233
221if have_xwayland 234if have_xwayland
222 sway_sources += 'desktop/xwayland.c' 235 sway_sources += 'desktop/xwayland.c'
223 sway_deps += xcb 236endif
237
238if wlroots_features['libinput_backend']
239 sway_sources += 'input/libinput.c'
224endif 240endif
225 241
226executable( 242executable(
227 'sway', 243 'sway',
228 sway_sources, 244 sway_sources + wl_protos_src,
229 include_directories: [sway_inc], 245 include_directories: [sway_inc],
230 dependencies: sway_deps, 246 dependencies: sway_deps,
231 link_with: [lib_sway_common], 247 link_with: [lib_sway_common],
diff --git a/sway/realtime.c b/sway/realtime.c
new file mode 100644
index 00000000..11154af0
--- /dev/null
+++ b/sway/realtime.c
@@ -0,0 +1,40 @@
1#include <sys/resource.h>
2#include <sched.h>
3#include <unistd.h>
4#include <pthread.h>
5#include "sway/server.h"
6#include "log.h"
7
8static void child_fork_callback(void) {
9 struct sched_param param;
10
11 param.sched_priority = 0;
12
13 int ret = pthread_setschedparam(pthread_self(), SCHED_OTHER, &param);
14 if (ret != 0) {
15 sway_log(SWAY_ERROR, "Failed to reset scheduler policy on fork");
16 }
17}
18
19void set_rr_scheduling(void) {
20 int prio = sched_get_priority_min(SCHED_RR);
21 int old_policy;
22 int ret;
23 struct sched_param param;
24
25 ret = pthread_getschedparam(pthread_self(), &old_policy, &param);
26 if (ret != 0) {
27 sway_log(SWAY_DEBUG, "Failed to get old scheduling priority");
28 return;
29 }
30
31 param.sched_priority = prio;
32
33 ret = pthread_setschedparam(pthread_self(), SCHED_RR, &param);
34 if (ret != 0) {
35 sway_log(SWAY_INFO, "Failed to set scheduling priority to %d", prio);
36 return;
37 }
38
39 pthread_atfork(NULL, NULL, child_fork_callback);
40}
diff --git a/sway/scene_descriptor.c b/sway/scene_descriptor.c
new file mode 100644
index 00000000..a30d4664
--- /dev/null
+++ b/sway/scene_descriptor.c
@@ -0,0 +1,66 @@
1#include <stdlib.h>
2#include <wlr/util/addon.h>
3#include "log.h"
4#include "sway/scene_descriptor.h"
5
6struct scene_descriptor {
7 void *data;
8 struct wlr_addon addon;
9};
10
11static const struct wlr_addon_interface addon_interface;
12
13static struct scene_descriptor *scene_node_get_descriptor(
14 struct wlr_scene_node *node, enum sway_scene_descriptor_type type) {
15 struct wlr_addon *addon = wlr_addon_find(&node->addons, (void *)type, &addon_interface);
16 if (!addon) {
17 return NULL;
18 }
19
20 struct scene_descriptor *desc = wl_container_of(addon, desc, addon);
21 return desc;
22}
23
24static void descriptor_destroy(struct scene_descriptor *desc) {
25 wlr_addon_finish(&desc->addon);
26 free(desc);
27}
28
29void *scene_descriptor_try_get(struct wlr_scene_node *node,
30 enum sway_scene_descriptor_type type) {
31 struct scene_descriptor *desc = scene_node_get_descriptor(node, type);
32 if (!desc) {
33 return NULL;
34 }
35
36 return desc->data;
37}
38
39void scene_descriptor_destroy(struct wlr_scene_node *node,
40 enum sway_scene_descriptor_type type) {
41 struct scene_descriptor *desc = scene_node_get_descriptor(node, type);
42 descriptor_destroy(desc);
43}
44
45static void addon_handle_destroy(struct wlr_addon *addon) {
46 struct scene_descriptor *desc = wl_container_of(addon, desc, addon);
47 descriptor_destroy(desc);
48}
49
50static const struct wlr_addon_interface addon_interface = {
51 .name = "sway_scene_descriptor",
52 .destroy = addon_handle_destroy,
53};
54
55bool scene_descriptor_assign(struct wlr_scene_node *node,
56 enum sway_scene_descriptor_type type, void *data) {
57 struct scene_descriptor *desc = calloc(1, sizeof(*desc));
58 if (!desc) {
59 sway_log(SWAY_ERROR, "Could not allocate a scene descriptor");
60 return false;
61 }
62
63 wlr_addon_init(&desc->addon, &node->addons, (void *)type, &addon_interface);
64 desc->data = data;
65 return true;
66}
diff --git a/sway/server.c b/sway/server.c
index f51fcfe2..cc20e89d 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -7,25 +7,37 @@
7#include <wlr/backend.h> 7#include <wlr/backend.h>
8#include <wlr/backend/headless.h> 8#include <wlr/backend/headless.h>
9#include <wlr/backend/multi.h> 9#include <wlr/backend/multi.h>
10#include <wlr/backend/noop.h> 10#include <wlr/config.h>
11#include <wlr/backend/session.h>
12#include <wlr/render/wlr_renderer.h> 11#include <wlr/render/wlr_renderer.h>
13#include <wlr/types/wlr_compositor.h> 12#include <wlr/types/wlr_compositor.h>
13#include <wlr/types/wlr_content_type_v1.h>
14#include <wlr/types/wlr_cursor_shape_v1.h>
14#include <wlr/types/wlr_data_control_v1.h> 15#include <wlr/types/wlr_data_control_v1.h>
16#include <wlr/types/wlr_drm.h>
15#include <wlr/types/wlr_export_dmabuf_v1.h> 17#include <wlr/types/wlr_export_dmabuf_v1.h>
18#include <wlr/types/wlr_fractional_scale_v1.h>
16#include <wlr/types/wlr_gamma_control_v1.h> 19#include <wlr/types/wlr_gamma_control_v1.h>
17#include <wlr/types/wlr_idle.h> 20#include <wlr/types/wlr_idle_notify_v1.h>
18#include <wlr/types/wlr_layer_shell_v1.h> 21#include <wlr/types/wlr_layer_shell_v1.h>
22#include <wlr/types/wlr_linux_dmabuf_v1.h>
19#include <wlr/types/wlr_pointer_constraints_v1.h> 23#include <wlr/types/wlr_pointer_constraints_v1.h>
20#include <wlr/types/wlr_primary_selection_v1.h> 24#include <wlr/types/wlr_primary_selection_v1.h>
21#include <wlr/types/wlr_relative_pointer_v1.h> 25#include <wlr/types/wlr_relative_pointer_v1.h>
22#include <wlr/types/wlr_screencopy_v1.h> 26#include <wlr/types/wlr_screencopy_v1.h>
27#include <wlr/types/wlr_security_context_v1.h>
23#include <wlr/types/wlr_server_decoration.h> 28#include <wlr/types/wlr_server_decoration.h>
29#include <wlr/types/wlr_single_pixel_buffer_v1.h>
30#include <wlr/types/wlr_subcompositor.h>
24#include <wlr/types/wlr_tablet_v2.h> 31#include <wlr/types/wlr_tablet_v2.h>
25#include <wlr/types/wlr_viewporter.h> 32#include <wlr/types/wlr_viewporter.h>
26#include <wlr/types/wlr_xcursor_manager.h> 33#include <wlr/types/wlr_xcursor_manager.h>
34#include <wlr/types/wlr_xdg_activation_v1.h>
27#include <wlr/types/wlr_xdg_decoration_v1.h> 35#include <wlr/types/wlr_xdg_decoration_v1.h>
36#include <wlr/types/wlr_xdg_foreign_registry.h>
37#include <wlr/types/wlr_xdg_foreign_v1.h>
38#include <wlr/types/wlr_xdg_foreign_v2.h>
28#include <wlr/types/wlr_xdg_output_v1.h> 39#include <wlr/types/wlr_xdg_output_v1.h>
40#include <xf86drm.h>
29#include "config.h" 41#include "config.h"
30#include "list.h" 42#include "list.h"
31#include "log.h" 43#include "log.h"
@@ -34,41 +46,177 @@
34#include "sway/input/input-manager.h" 46#include "sway/input/input-manager.h"
35#include "sway/output.h" 47#include "sway/output.h"
36#include "sway/server.h" 48#include "sway/server.h"
49#include "sway/input/cursor.h"
37#include "sway/tree/root.h" 50#include "sway/tree/root.h"
51
38#if HAVE_XWAYLAND 52#if HAVE_XWAYLAND
53#include <wlr/xwayland/shell.h>
39#include "sway/xwayland.h" 54#include "sway/xwayland.h"
40#endif 55#endif
41 56
42bool server_privileged_prepare(struct sway_server *server) { 57#if WLR_HAS_DRM_BACKEND
43 sway_log(SWAY_DEBUG, "Preparing Wayland server initialization"); 58#include <wlr/types/wlr_drm_lease_v1.h>
59#endif
60
61#define SWAY_XDG_SHELL_VERSION 2
62#define SWAY_LAYER_SHELL_VERSION 4
63
64bool allow_unsupported_gpu = false;
65
66#if WLR_HAS_DRM_BACKEND
67static void handle_drm_lease_request(struct wl_listener *listener, void *data) {
68 /* We only offer non-desktop outputs, but in the future we might want to do
69 * more logic here. */
70
71 struct wlr_drm_lease_request_v1 *req = data;
72 struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req);
73 if (!lease) {
74 sway_log(SWAY_ERROR, "Failed to grant lease request");
75 wlr_drm_lease_request_v1_reject(req);
76 }
77}
78#endif
79
80static bool is_privileged(const struct wl_global *global) {
81#if WLR_HAS_DRM_BACKEND
82 if (server.drm_lease_manager != NULL) {
83 struct wlr_drm_lease_device_v1 *drm_lease_dev;
84 wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) {
85 if (drm_lease_dev->global == global) {
86 return true;
87 }
88 }
89 }
90#endif
91
92 return
93 global == server.output_manager_v1->global ||
94 global == server.output_power_manager_v1->global ||
95 global == server.input_method->global ||
96 global == server.foreign_toplevel_manager->global ||
97 global == server.data_control_manager_v1->global ||
98 global == server.screencopy_manager_v1->global ||
99 global == server.export_dmabuf_manager_v1->global ||
100 global == server.security_context_manager_v1->global ||
101 global == server.gamma_control_manager_v1->global ||
102 global == server.layer_shell->global ||
103 global == server.session_lock.manager->global ||
104 global == server.input->keyboard_shortcuts_inhibit->global ||
105 global == server.input->virtual_keyboard->global ||
106 global == server.input->virtual_pointer->global;
107}
108
109static bool filter_global(const struct wl_client *client,
110 const struct wl_global *global, void *data) {
111#if HAVE_XWAYLAND
112 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
113 if (xwayland && global == xwayland->shell_v1->global) {
114 return xwayland->server != NULL && client == xwayland->server->client;
115 }
116#endif
117
118 // Restrict usage of privileged protocols to unsandboxed clients
119 // TODO: add a way for users to configure an allow-list
120 const struct wlr_security_context_v1_state *security_context =
121 wlr_security_context_manager_v1_lookup_client(
122 server.security_context_manager_v1, (struct wl_client *)client);
123 if (is_privileged(global)) {
124 return security_context == NULL;
125 }
126
127 return true;
128}
129
130static void detect_proprietary(struct wlr_backend *backend, void *data) {
131 int drm_fd = wlr_backend_get_drm_fd(backend);
132 if (drm_fd < 0) {
133 return;
134 }
135
136 drmVersion *version = drmGetVersion(drm_fd);
137 if (version == NULL) {
138 sway_log(SWAY_ERROR, "drmGetVersion() failed");
139 return;
140 }
141
142 bool is_unsupported = false;
143 if (strcmp(version->name, "nvidia-drm") == 0) {
144 is_unsupported = true;
145 sway_log(SWAY_ERROR, "!!! Proprietary Nvidia drivers are in use !!!");
146 if (!allow_unsupported_gpu) {
147 sway_log(SWAY_ERROR, "Use Nouveau instead");
148 }
149 }
150
151 if (strcmp(version->name, "evdi") == 0) {
152 is_unsupported = true;
153 sway_log(SWAY_ERROR, "!!! Proprietary DisplayLink drivers are in use !!!");
154 }
155
156 if (!allow_unsupported_gpu && is_unsupported) {
157 sway_log(SWAY_ERROR,
158 "Proprietary drivers are NOT supported. To launch sway anyway, "
159 "launch with --unsupported-gpu and DO NOT report issues.");
160 exit(EXIT_FAILURE);
161 }
162
163 drmFreeVersion(version);
164}
165
166bool server_init(struct sway_server *server) {
167 sway_log(SWAY_DEBUG, "Initializing Wayland server");
44 server->wl_display = wl_display_create(); 168 server->wl_display = wl_display_create();
45 server->wl_event_loop = wl_display_get_event_loop(server->wl_display); 169 server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
46 server->backend = wlr_backend_autocreate(server->wl_display);
47 170
171 wl_display_set_global_filter(server->wl_display, filter_global, NULL);
172
173 root = root_create(server->wl_display);
174
175 server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session);
48 if (!server->backend) { 176 if (!server->backend) {
49 sway_log(SWAY_ERROR, "Unable to create backend"); 177 sway_log(SWAY_ERROR, "Unable to create backend");
50 return false; 178 return false;
51 } 179 }
52 return true;
53}
54 180
55bool server_init(struct sway_server *server) { 181 wlr_multi_for_each_backend(server->backend, detect_proprietary, NULL);
56 sway_log(SWAY_DEBUG, "Initializing Wayland server"); 182
183 server->renderer = wlr_renderer_autocreate(server->backend);
184 if (!server->renderer) {
185 sway_log(SWAY_ERROR, "Failed to create renderer");
186 return false;
187 }
188
189 wlr_renderer_init_wl_shm(server->renderer, server->wl_display);
190
191 if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL) {
192 server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
193 server->wl_display, 4, server->renderer);
194 }
195 if (wlr_renderer_get_dmabuf_texture_formats(server->renderer) != NULL &&
196 debug.legacy_wl_drm) {
197 wlr_drm_create(server->wl_display, server->renderer);
198 }
57 199
58 struct wlr_renderer *renderer = wlr_backend_get_renderer(server->backend); 200 server->allocator = wlr_allocator_autocreate(server->backend,
59 assert(renderer); 201 server->renderer);
202 if (!server->allocator) {
203 sway_log(SWAY_ERROR, "Failed to create allocator");
204 return false;
205 }
60 206
61 wlr_renderer_init_wl_display(renderer, server->wl_display); 207 server->compositor = wlr_compositor_create(server->wl_display, 6,
208 server->renderer);
62 209
63 server->compositor = wlr_compositor_create(server->wl_display, renderer); 210 wlr_subcompositor_create(server->wl_display);
64 server->compositor_new_surface.notify = handle_compositor_new_surface;
65 wl_signal_add(&server->compositor->events.new_surface,
66 &server->compositor_new_surface);
67 211
68 server->data_device_manager = 212 server->data_device_manager =
69 wlr_data_device_manager_create(server->wl_display); 213 wlr_data_device_manager_create(server->wl_display);
70 214
71 wlr_gamma_control_manager_v1_create(server->wl_display); 215 server->gamma_control_manager_v1 =
216 wlr_gamma_control_manager_v1_create(server->wl_display);
217 server->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma;
218 wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma,
219 &server->gamma_control_set_gamma);
72 220
73 server->new_output.notify = handle_new_output; 221 server->new_output.notify = handle_new_output;
74 wl_signal_add(&server->backend->events.new_output, &server->new_output); 222 wl_signal_add(&server->backend->events.new_output, &server->new_output);
@@ -78,19 +226,20 @@ bool server_init(struct sway_server *server) {
78 226
79 wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); 227 wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout);
80 228
81 server->idle = wlr_idle_create(server->wl_display); 229 server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display);
82 server->idle_inhibit_manager_v1 = 230 sway_idle_inhibit_manager_v1_init();
83 sway_idle_inhibit_manager_v1_create(server->wl_display, server->idle);
84 231
85 server->layer_shell = wlr_layer_shell_v1_create(server->wl_display); 232 server->layer_shell = wlr_layer_shell_v1_create(server->wl_display,
233 SWAY_LAYER_SHELL_VERSION);
86 wl_signal_add(&server->layer_shell->events.new_surface, 234 wl_signal_add(&server->layer_shell->events.new_surface,
87 &server->layer_shell_surface); 235 &server->layer_shell_surface);
88 server->layer_shell_surface.notify = handle_layer_shell_surface; 236 server->layer_shell_surface.notify = handle_layer_shell_surface;
89 237
90 server->xdg_shell = wlr_xdg_shell_create(server->wl_display); 238 server->xdg_shell = wlr_xdg_shell_create(server->wl_display,
91 wl_signal_add(&server->xdg_shell->events.new_surface, 239 SWAY_XDG_SHELL_VERSION);
92 &server->xdg_shell_surface); 240 wl_signal_add(&server->xdg_shell->events.new_toplevel,
93 server->xdg_shell_surface.notify = handle_xdg_shell_surface; 241 &server->xdg_shell_toplevel);
242 server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel;
94 243
95 server->tablet_v2 = wlr_tablet_v2_create(server->wl_display); 244 server->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
96 245
@@ -121,8 +270,7 @@ bool server_init(struct sway_server *server) {
121 wl_signal_add(&server->pointer_constraints->events.new_constraint, 270 wl_signal_add(&server->pointer_constraints->events.new_constraint,
122 &server->pointer_constraint); 271 &server->pointer_constraint);
123 272
124 server->presentation = 273 wlr_presentation_create(server->wl_display, server->backend);
125 wlr_presentation_create(server->wl_display, server->backend);
126 274
127 server->output_manager_v1 = 275 server->output_manager_v1 =
128 wlr_output_manager_v1_create(server->wl_display); 276 wlr_output_manager_v1_create(server->wl_display);
@@ -144,16 +292,57 @@ bool server_init(struct sway_server *server) {
144 server->foreign_toplevel_manager = 292 server->foreign_toplevel_manager =
145 wlr_foreign_toplevel_manager_v1_create(server->wl_display); 293 wlr_foreign_toplevel_manager_v1_create(server->wl_display);
146 294
147 wlr_export_dmabuf_manager_v1_create(server->wl_display); 295 sway_session_lock_init();
148 wlr_screencopy_manager_v1_create(server->wl_display); 296
149 wlr_data_control_manager_v1_create(server->wl_display); 297#if WLR_HAS_DRM_BACKEND
150 wlr_primary_selection_v1_device_manager_create(server->wl_display); 298 server->drm_lease_manager=
299 wlr_drm_lease_v1_manager_create(server->wl_display, server->backend);
300 if (server->drm_lease_manager) {
301 server->drm_lease_request.notify = handle_drm_lease_request;
302 wl_signal_add(&server->drm_lease_manager->events.request,
303 &server->drm_lease_request);
304 } else {
305 sway_log(SWAY_DEBUG, "Failed to create wlr_drm_lease_device_v1");
306 sway_log(SWAY_INFO, "VR will not be available");
307 }
308#endif
309
310 server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display);
311 server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display);
312 server->data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display);
313 server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display);
151 wlr_viewporter_create(server->wl_display); 314 wlr_viewporter_create(server->wl_display);
315 wlr_single_pixel_buffer_manager_v1_create(server->wl_display);
316 server->content_type_manager_v1 =
317 wlr_content_type_manager_v1_create(server->wl_display, 1);
318 wlr_fractional_scale_manager_v1_create(server->wl_display, 1);
319
320 struct wlr_xdg_foreign_registry *foreign_registry =
321 wlr_xdg_foreign_registry_create(server->wl_display);
322 wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry);
323 wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry);
324
325 server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display);
326 server->xdg_activation_v1_request_activate.notify =
327 xdg_activation_v1_handle_request_activate;
328 wl_signal_add(&server->xdg_activation_v1->events.request_activate,
329 &server->xdg_activation_v1_request_activate);
330 server->xdg_activation_v1_new_token.notify =
331 xdg_activation_v1_handle_new_token;
332 wl_signal_add(&server->xdg_activation_v1->events.new_token,
333 &server->xdg_activation_v1_new_token);
334
335 struct wlr_cursor_shape_manager_v1 *cursor_shape_manager =
336 wlr_cursor_shape_manager_v1_create(server->wl_display, 1);
337 server->request_set_cursor_shape.notify = handle_request_set_cursor_shape;
338 wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape);
339
340 wl_list_init(&server->pending_launcher_ctxs);
152 341
153 // Avoid using "wayland-0" as display socket 342 // Avoid using "wayland-0" as display socket
154 char name_candidate[16]; 343 char name_candidate[16];
155 for (int i = 1; i <= 32; ++i) { 344 for (unsigned int i = 1; i <= 32; ++i) {
156 sprintf(name_candidate, "wayland-%d", i); 345 snprintf(name_candidate, sizeof(name_candidate), "wayland-%u", i);
157 if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) { 346 if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) {
158 server->socket = strdup(name_candidate); 347 server->socket = strdup(name_candidate);
159 break; 348 break;
@@ -166,27 +355,26 @@ bool server_init(struct sway_server *server) {
166 return false; 355 return false;
167 } 356 }
168 357
169 server->noop_backend = wlr_noop_backend_create(server->wl_display); 358 server->headless_backend = wlr_headless_backend_create(server->wl_event_loop);
170
171 struct wlr_output *wlr_output = wlr_noop_add_output(server->noop_backend);
172 root->noop_output = output_create(wlr_output);
173
174 server->headless_backend =
175 wlr_headless_backend_create_with_renderer(server->wl_display, renderer);
176 if (!server->headless_backend) { 359 if (!server->headless_backend) {
177 sway_log(SWAY_INFO, "Failed to create secondary headless backend, " 360 sway_log(SWAY_ERROR, "Failed to create secondary headless backend");
178 "starting without it"); 361 wlr_backend_destroy(server->backend);
362 return false;
179 } else { 363 } else {
180 wlr_multi_backend_add(server->backend, server->headless_backend); 364 wlr_multi_backend_add(server->backend, server->headless_backend);
181 } 365 }
182 366
367 struct wlr_output *wlr_output =
368 wlr_headless_add_output(server->headless_backend, 800, 600);
369 wlr_output_set_name(wlr_output, "FALLBACK");
370 root->fallback_output = output_create(wlr_output);
371
183 // This may have been set already via -Dtxn-timeout 372 // This may have been set already via -Dtxn-timeout
184 if (!server->txn_timeout_ms) { 373 if (!server->txn_timeout_ms) {
185 server->txn_timeout_ms = 200; 374 server->txn_timeout_ms = 200;
186 } 375 }
187 376
188 server->dirty_nodes = create_list(); 377 server->dirty_nodes = create_list();
189 server->transactions = create_list();
190 378
191 server->input = input_manager_create(server); 379 server->input = input_manager_create(server);
192 input_manager_get_default_seat(); // create seat0 380 input_manager_get_default_seat(); // create seat0
@@ -202,7 +390,6 @@ void server_fini(struct sway_server *server) {
202 wl_display_destroy_clients(server->wl_display); 390 wl_display_destroy_clients(server->wl_display);
203 wl_display_destroy(server->wl_display); 391 wl_display_destroy(server->wl_display);
204 list_free(server->dirty_nodes); 392 list_free(server->dirty_nodes);
205 list_free(server->transactions);
206} 393}
207 394
208bool server_start(struct sway_server *server) { 395bool server_start(struct sway_server *server) {
@@ -231,6 +418,10 @@ bool server_start(struct sway_server *server) {
231 } 418 }
232#endif 419#endif
233 420
421 if (config->primary_selection) {
422 wlr_primary_selection_v1_device_manager_create(server->wl_display);
423 }
424
234 sway_log(SWAY_INFO, "Starting backend on wayland display '%s'", 425 sway_log(SWAY_INFO, "Starting backend on wayland display '%s'",
235 server->socket); 426 server->socket);
236 if (!wlr_backend_start(server->backend)) { 427 if (!wlr_backend_start(server->backend)) {
@@ -238,6 +429,7 @@ bool server_start(struct sway_server *server) {
238 wlr_backend_destroy(server->backend); 429 wlr_backend_destroy(server->backend);
239 return false; 430 return false;
240 } 431 }
432
241 return true; 433 return true;
242} 434}
243 435
diff --git a/sway/sway-bar.5.scd b/sway/sway-bar.5.scd
index 80d08449..42e59d57 100644
--- a/sway/sway-bar.5.scd
+++ b/sway/sway-bar.5.scd
@@ -40,7 +40,7 @@ runtime.
40*font* <font> 40*font* <font>
41 Specifies the font to be used in the bar. _font_ should be specified as a 41 Specifies the font to be used in the bar. _font_ should be specified as a
42 pango font description. For more information on pango font descriptions, 42 pango font description. For more information on pango font descriptions,
43 see https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string 43 see https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html#description
44 44
45*gaps* <all> | <horizontal> <vertical> | <top> <right> <bottom> <left> 45*gaps* <all> | <horizontal> <vertical> | <top> <right> <bottom> <left>
46 Sets the gaps from the edge of the screen for the bar. Gaps can either be 46 Sets the gaps from the edge of the screen for the bar. Gaps can either be
diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd
index dbf21d93..442311bb 100644
--- a/sway/sway-input.5.scd
+++ b/sway/sway-input.5.scd
@@ -111,6 +111,9 @@ The following commands may only be used in the configuration file.
111 Maps inputs from this device to the specified output. Only meaningful if the 111 Maps inputs from this device to the specified output. Only meaningful if the
112 device is a pointer, touch, or drawing tablet device. 112 device is a pointer, touch, or drawing tablet device.
113 113
114 The wildcard _\*_ can be used to map the input device to the whole desktop
115 layout.
116
114*input* <identifier> map_to_region <X> <Y> <width> <height> 117*input* <identifier> map_to_region <X> <Y> <width> <height>
115 Maps inputs from this device to the specified region of the global output 118 Maps inputs from this device to the specified region of the global output
116 layout. Only meaningful if the device is a pointer, touch, or drawing tablet 119 layout. Only meaningful if the device is a pointer, touch, or drawing tablet
@@ -118,11 +121,16 @@ The following commands may only be used in the configuration file.
118 121
119*input* <identifier> map_from_region <X1xY1> <X2xY2> 122*input* <identifier> map_from_region <X1xY1> <X2xY2>
120 Ignores inputs from this device that do not occur within the specified 123 Ignores inputs from this device that do not occur within the specified
121 region. Can be in millimeters (e.g. 10x20mm 20x40mm) or in terms of 0..1 124 region. Can be in millimeters (e.g. 10x20mm 20x40mm) or the fraction of the
122 (e.g. 0.5x0.5 0.7x0.7). Not all devices support millimeters. Only meaningful 125 full available space in terms of 0..1 (e.g. 0.5x0.5 0.7x0.7). Not all
123 if the device is not a keyboard and provides events in absolute terms (such 126 devices support millimeters. Only meaningful if the device is not a
124 as a drawing tablet or touch screen - most pointers provide events relative 127 keyboard and provides events in absolute terms (such as a drawing tablet
125 to the previous frame). 128 or touch screen - most pointers provide events relative to the previous
129 frame).
130
131 Commonly used to maintain the aspect ratio of the input device and screen.
132 Cropping a 16:10 input region to match a 16:9 display can use 0x0 1x0.9 as
133 the argument.
126 134
127## LIBINPUT CONFIGURATION 135## LIBINPUT CONFIGURATION
128 136
@@ -144,6 +152,10 @@ The following commands may only be used in the configuration file.
144*input* <identifier> dwt enabled|disabled 152*input* <identifier> dwt enabled|disabled
145 Enables or disables disable-while-typing for the specified input device. 153 Enables or disables disable-while-typing for the specified input device.
146 154
155*input* <identifier> dwtp enabled|disabled
156 Enables or disables disable-while-trackpointing for the specified input
157 device.
158
147*input* <identifier> events enabled|disabled|disabled_on_external_mouse|toggle [<toggle-modes>] 159*input* <identifier> events enabled|disabled|disabled_on_external_mouse|toggle [<toggle-modes>]
148 Enables or disables send_events for specified input device. Disabling 160 Enables or disables send_events for specified input device. Disabling
149 send_events disables the input device. 161 send_events disables the input device.
@@ -168,12 +180,19 @@ The following commands may only be used in the configuration file.
168*input* <identifier> pointer_accel [<-1|1>] 180*input* <identifier> pointer_accel [<-1|1>]
169 Changes the pointer acceleration for the specified input device. 181 Changes the pointer acceleration for the specified input device.
170 182
183*input* <identifier> rotation_angle <angle>
184 Sets the rotation angle of the device to the given clockwise angle in
185 degrees. The angle must be between 0.0 (inclusive) and 360.0 (exclusive).
186
171*input* <identifier> scroll_button disable|button[1-3,8,9]|<event-code-or-name> 187*input* <identifier> scroll_button disable|button[1-3,8,9]|<event-code-or-name>
172 Sets the button used for scroll_method on_button_down. The button can 188 Sets the button used for scroll_method on_button_down. The button can
173 be given as an event name or code, which can be obtained from *libinput 189 be given as an event name or code, which can be obtained from *libinput
174 debug-events*, or as a x11 mouse button (button[1-3,8,9]). If set to 190 debug-events*, or as a x11 mouse button (button[1-3,8,9]). If set to
175 _disable_, it disables the scroll_method on_button_down. 191 _disable_, it disables the scroll_method on_button_down.
176 192
193*input* <identifier> scroll_button_lock enabled|disabled
194 Enables or disables scroll button lock for specified input device.
195
177*input* <identifier> scroll_factor <floating point value> 196*input* <identifier> scroll_factor <floating point value>
178 Changes the scroll factor for the specified input device. Scroll speed will 197 Changes the scroll factor for the specified input device. Scroll speed will
179 be scaled by the given value, which must be non-negative. 198 be scaled by the given value, which must be non-negative.
@@ -217,6 +236,8 @@ correct seat.
217 absolute coordinates (with respect to the global coordinate space). 236 absolute coordinates (with respect to the global coordinate space).
218 Specifying either value as 0 will not update that coordinate. 237 Specifying either value as 0 will not update that coordinate.
219 238
239 Deprecated: use the virtual-pointer Wayland protocol instead.
240
220*seat* <seat> cursor press|release button[1-9]|<event-name-or-code> 241*seat* <seat> cursor press|release button[1-9]|<event-name-or-code>
221 Simulate pressing (or releasing) the specified mouse button on the 242 Simulate pressing (or releasing) the specified mouse button on the
222 specified seat. The button can either be provided as a button event name or 243 specified seat. The button can either be provided as a button event name or
@@ -225,12 +246,14 @@ correct seat.
225 event will be simulated, however _press_ and _release_ will be ignored and 246 event will be simulated, however _press_ and _release_ will be ignored and
226 both will occur. 247 both will occur.
227 248
249 Deprecated: use the virtual-pointer Wayland protocol instead.
250
228*seat* <name> fallback true|false 251*seat* <name> fallback true|false
229 Set this seat as the fallback seat. A fallback seat will attach any device 252 Set this seat as the fallback seat. A fallback seat will attach any device
230 not explicitly attached to another seat (similar to a "default" seat). 253 not explicitly attached to another seat (similar to a "default" seat).
231 254
232*seat* <name> hide_cursor <timeout>|when-typing [enable|disable] 255*seat* <name> hide_cursor <timeout>|when-typing [enable|disable]
233 Hides the cursor image after the specified event occured. 256 Hides the cursor image after the specified event occurred.
234 257
235 If _timeout_ is specified, then the cursor will be hidden after _timeout_ 258 If _timeout_ is specified, then the cursor will be hidden after _timeout_
236 (in milliseconds) has elapsed with no activity on the cursor. A timeout of 0 259 (in milliseconds) has elapsed with no activity on the cursor. A timeout of 0
@@ -240,18 +263,16 @@ correct seat.
240 If _when-typing_ is enabled, then the cursor will be hidden whenever a key 263 If _when-typing_ is enabled, then the cursor will be hidden whenever a key
241 is pressed. 264 is pressed.
242 265
266 Be aware that this setting can interfere with input handling in games and
267 certain types of software (Gimp, Blender etc) that rely on simultaneous
268 input from mouse and keyboard.
269
243*seat* <name> idle_inhibit <sources...> 270*seat* <name> idle_inhibit <sources...>
244 Sets the set of input event sources which can prevent the seat from 271 Sets the set of input event sources which can prevent the seat from
245 becoming idle, as a space separated list of source names. Valid names are 272 becoming idle, as a space separated list of source names. Valid names are
246 "keyboard", "pointer", "touchpad", "touch", "tablet_pad", "tablet_tool", 273 "keyboard", "pointer", "touchpad", "touch", "tablet_pad", "tablet_tool",
247 and "switch". The default behavior is to prevent idle on any event. 274 and "switch". The default behavior is to prevent idle on any event.
248 275
249*seat* <name> idle_wake <sources...>
250 Sets the set of input event sources which can wake the seat from
251 its idle state, as a space separated list of source names. Valid names are
252 "keyboard", "pointer", "touchpad", "touch", "tablet_pad", "tablet_tool",
253 and "switch". The default behavior is to wake from idle on any event.
254
255*seat* <name> keyboard_grouping none|smart 276*seat* <name> keyboard_grouping none|smart
256 Set how the keyboards in the seat are grouped together. Currently, there 277 Set how the keyboards in the seat are grouped together. Currently, there
257 are two options. _none_ will disable all keyboard grouping. This will make 278 are two options. _none_ will disable all keyboard grouping. This will make
@@ -274,7 +295,7 @@ correct seat.
274 whether future inhibitors are honoured by default, i.e. activated 295 whether future inhibitors are honoured by default, i.e. activated
275 automatically, the default being _enable_. When used at runtime, 296 automatically, the default being _enable_. When used at runtime,
276 _disable_ also disables any currently active inhibitors. _activate_, 297 _disable_ also disables any currently active inhibitors. _activate_,
277 _deactivate_ and _toggle_ are only useable at runtime and change the 298 _deactivate_ and _toggle_ are only usable at runtime and change the
278 state of a potentially existing inhibitor on the currently focused 299 state of a potentially existing inhibitor on the currently focused
279 window. This can be used with the current seat alias (_-_) to affect 300 window. This can be used with the current seat alias (_-_) to affect
280 only the currently focused window of the current seat. Subcommand 301 only the currently focused window of the current seat. Subcommand
diff --git a/sway/sway-ipc.7.scd b/sway/sway-ipc.7.scd
index 1b855959..f4a5ccff 100644
--- a/sway/sway-ipc.7.scd
+++ b/sway/sway-ipc.7.scd
@@ -213,7 +213,10 @@ following properties:
213: Whether this output is active/enabled 213: Whether this output is active/enabled
214|- dpms 214|- dpms
215: boolean 215: boolean
216: Whether this output is on/off (via DPMS) 216: (Deprecated, use _power_ instead) Whether this output is on/off (via DPMS)
217|- power
218: boolean
219: Whether this output is on/off
217|- primary 220|- primary
218: boolean 221: boolean
219: For i3 compatibility, this will be false. It does not make sense in Wayland 222: For i3 compatibility, this will be false. It does not make sense in Wayland
@@ -294,7 +297,7 @@ following properties:
294Retrieve a JSON representation of the tree 297Retrieve a JSON representation of the tree
295 298
296*REPLY*++ 299*REPLY*++
297An array of object the represent the current tree. Each object represents one 300An array of objects that represent the current tree. Each object represents one
298node and will have the following properties: 301node and will have the following properties:
299 302
300[- *PROPERTY* 303[- *PROPERTY*
@@ -334,8 +337,9 @@ node and will have the following properties:
334 this, but borders are included. 337 this, but borders are included.
335|- window_rect 338|- window_rect
336: object 339: object
337: The geometry of the contents inside the node. The window decorations are 340: The geometry of the content inside the node. These coordinates are relative
338 excluded from this calculation, but borders are included. 341 to the node itself. Window decorations and borders are outside the
342 _window_rect_
339|- deco_rect 343|- deco_rect
340: object 344: object
341: The geometry of the decorations for the node relative to the parent node 345: The geometry of the decorations for the node relative to the parent node
@@ -370,7 +374,7 @@ node and will have the following properties:
370 that can be used as an aid in submitting reproduction steps for bug reports 374 that can be used as an aid in submitting reproduction steps for bug reports
371|- fullscreen_mode 375|- fullscreen_mode
372: integer 376: integer
373: (Only containers and views) The fullscreen mode of the node. 0 means none, 1 means 377: (Only containers and views) The fullscreen mode of the node. 0 means none, 1 means
374 full workspace, and 2 means global fullscreen 378 full workspace, and 2 means global fullscreen
375|- app_id 379|- app_id
376: string 380: string
@@ -1131,6 +1135,9 @@ following properties:
1131|- xkb_active_layout_index 1135|- xkb_active_layout_index
1132: integer 1136: integer
1133: (Only keyboards) The index of the active keyboard layout in use 1137: (Only keyboards) The index of the active keyboard layout in use
1138|- scroll_factor
1139: floating
1140: (Only pointers) Multiplier applied on scroll event values.
1134|- libinput 1141|- libinput
1135: object 1142: object
1136: (Only libinput devices) An object describing the current device settings. 1143: (Only libinput devices) An object describing the current device settings.
@@ -1188,9 +1195,16 @@ following properties will be included for devices that support them:
1188: int 1195: int
1189: The scroll button to use when _scroll_method_ is _on_button_down_. This 1196: The scroll button to use when _scroll_method_ is _on_button_down_. This
1190 will be given as an input event code 1197 will be given as an input event code
1198|- scroll_button_lock
1199: string
1200: Whether scroll button lock is enabled. It can be _enabled_ or _disabled_
1191|- dwt 1201|- dwt
1192: string 1202: string
1193: Whether disable-while-typing is enabled. It can be _enabled_ or _disabled_ 1203: Whether disable-while-typing is enabled. It can be _enabled_ or _disabled_
1204|- dwtp
1205: string
1206: Whether disable-while-trackpointing is enabled. It can be _enabled_ or
1207 _disabled_
1194|- calibration_matrix 1208|- calibration_matrix
1195: array 1209: array
1196: An array of 6 floats representing the calibration matrix for absolute 1210: An array of 6 floats representing the calibration matrix for absolute
@@ -1230,7 +1244,8 @@ following properties will be included for devices that support them:
1230 "click_method": "button_areas", 1244 "click_method": "button_areas",
1231 "middle_emulation": "disabled", 1245 "middle_emulation": "disabled",
1232 "scroll_method": "edge", 1246 "scroll_method": "edge",
1233 "dwt": "enabled" 1247 "dwt": "enabled",
1248 "dwtp": "enabled"
1234 } 1249 }
1235 }, 1250 },
1236 { 1251 {
@@ -1357,7 +1372,8 @@ one seat. Each object has the following properties:
1357 "click_method": "button_areas", 1372 "click_method": "button_areas",
1358 "middle_emulation": "disabled", 1373 "middle_emulation": "disabled",
1359 "scroll_method": "edge", 1374 "scroll_method": "edge",
1360 "dwt": "enabled" 1375 "dwt": "enabled",
1376 "dwtp": "enabled"
1361 } 1377 }
1362 }, 1378 },
1363 { 1379 {
@@ -1433,6 +1449,9 @@ available:
1433: workspace 1449: workspace
1434:[ Sent whenever an event involving a workspace occurs such as initialization 1450:[ Sent whenever an event involving a workspace occurs such as initialization
1435 of a new workspace or a different workspace gains focus 1451 of a new workspace or a different workspace gains focus
1452|- 0x80000001
1453: output
1454: Sent when outputs are updated
1436|- 0x80000002 1455|- 0x80000002
1437: mode 1456: mode
1438: Sent whenever the binding mode changes 1457: Sent whenever the binding mode changes
@@ -1553,6 +1572,20 @@ The following change types are currently available:
1553} 1572}
1554``` 1573```
1555 1574
1575## 0x80000001. OUTPUT
1576
1577Sent whenever an output is added, removed, or its configuration is changed.
1578The event is a single object with the property _change_, which is a string
1579containing the reason for the change. Currently, the only value for _change_ is
1580_unspecified_.
1581
1582*Example Event:*
1583```
1584{
1585 "change": "unspecified"
1586}
1587```
1588
1556## 0x80000002. MODE 1589## 0x80000002. MODE
1557 1590
1558Sent whenever the binding mode changes. The event consists of a single object 1591Sent whenever the binding mode changes. The event consists of a single object
diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd
index 69f529fe..028cb7ab 100644
--- a/sway/sway-output.5.scd
+++ b/sway/sway-output.5.scd
@@ -24,7 +24,7 @@ must be separated by one space. For example:
24 24
25# COMMANDS 25# COMMANDS
26 26
27*output* <name> mode|resolution|res [--custom] <WIDTHxHEIGHT>[@<RATE>Hz] 27*output* <name> mode|resolution|res [--custom] <width>x<height>[@<rate>Hz]
28 Configures the specified output to use the given mode. Modes are a 28 Configures the specified output to use the given mode. Modes are a
29 combination of width and height (in pixels) and a refresh rate that your 29 combination of width and height (in pixels) and a refresh rate that your
30 display can be configured to use. For a list of available modes for each 30 display can be configured to use. For a list of available modes for each
@@ -40,6 +40,16 @@ must be separated by one space. For example:
40 40
41 output HDMI-A-1 mode 1920x1080@60Hz 41 output HDMI-A-1 mode 1920x1080@60Hz
42 42
43*output* <name> modeline <clock> <hdisplay> <hsync_start> <hsync_end> <htotal> <vdisplay> <vsync_start> <vsync_end> <vtotal> <hsync> <vsync>
44 Configures the specified output to use the given modeline. It can be
45 generated using *cvt*(1) and *gtf*(1) commands. See *xorg.conf*(5).
46 Only supported on DRM backend.
47
48 Example:
49
50 output HDMI-A-1 modeline 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync
51
52
43*output* <name> position|pos <X> <Y> 53*output* <name> position|pos <X> <Y>
44 Places the specified output at the specific position in the global 54 Places the specified output at the specific position in the global
45 coordinate space. The cursor may only be moved between immediately 55 coordinate space. The cursor may only be moved between immediately
@@ -109,12 +119,20 @@ must be separated by one space. For example:
109 Enables or disables the specified output (all outputs are enabled by 119 Enables or disables the specified output (all outputs are enabled by
110 default). 120 default).
111 121
122 As opposed to the _power_ command, the output will lose its current
123 workspace and windows.
124
112*output* <name> toggle 125*output* <name> toggle
113 Toggle the specified output. 126 Toggle the specified output.
114 127
115*output* <name> dpms on|off 128*output* <name> power on|off|toggle
116 Enables or disables the specified output via DPMS. To turn an output off 129 Turns on or off the specified output.
117 (ie. blank the screen but keep workspaces as-is), one can set DPMS to off. 130
131 As opposed to the _enable_ and _disable_ commands, the output keeps its
132 current workspaces and windows.
133
134*output* <name> dpms on|off|toggle
135 Deprecated. Alias for _power_.
118 136
119*output* <name> max_render_time off|<msec> 137*output* <name> max_render_time off|<msec>
120 Controls when sway composites the output, as a positive number of 138 Controls when sway composites the output, as a positive number of
@@ -142,11 +160,26 @@ must be separated by one space. For example:
142 Enables or disables adaptive synchronization (often referred to as Variable 160 Enables or disables adaptive synchronization (often referred to as Variable
143 Refresh Rate, or by the vendor-specific names FreeSync/G-Sync). 161 Refresh Rate, or by the vendor-specific names FreeSync/G-Sync).
144 162
145 Adaptive sync allows clients to submit frames a little to late without 163 Adaptive sync allows clients to submit frames a little too late without
146 having to wait a whole refresh period to display it on screen. Enabling 164 having to wait a whole refresh period to display it on screen. Enabling
147 adaptive sync can improve latency, but can cause flickering on some 165 adaptive sync can improve latency, but can cause flickering on some
148 hardware. 166 hardware.
149 167
168*output* <name> render_bit_depth 8|10
169 Controls the color channel bit depth at which frames are rendered; the
170 default is currently 8 bits per channel.
171
172 Setting higher values will not have an effect if hardware and software lack
173 support for such bit depths. Successfully increasing the render bit depth
174 will not necessarily increase the bit depth of the frames sent to a display.
175 An increased render bit depth may provide smoother rendering of gradients,
176 and screenshots which can more precisely store the colors of programs
177 which display high bit depth colors.
178
179 Warnings: this can break screenshot/screencast programs which have not been
180 updated to work with different bit depths. This command is experimental,
181 and may be removed or changed in the future.
182
150# SEE ALSO 183# SEE ALSO
151 184
152*sway*(5) *sway-input*(5) 185*sway*(5) *sway-input*(5)
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 02592b5f..7e58b528 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -155,7 +155,7 @@ They are expected to be used with *bindsym* or at runtime through *swaymsg*(1).
155 is specified, the view will be fullscreen across all outputs. 155 is specified, the view will be fullscreen across all outputs.
156 156
157*gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current 157*gaps* inner|outer|horizontal|vertical|top|right|bottom|left all|current
158set|plus|minus <amount> 158set|plus|minus|toggle <amount>
159 Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the 159 Changes the _inner_ or _outer_ gaps for either _all_ workspaces or the
160 _current_ workspace. _outer_ gaps can be altered per side with _top_, 160 _current_ workspace. _outer_ gaps can be altered per side with _top_,
161 _right_, _bottom_, and _left_ or per direction with _horizontal_ and 161 _right_, _bottom_, and _left_ or per direction with _horizontal_ and
@@ -176,6 +176,12 @@ set|plus|minus <amount>
176*layout* default|splith|splitv|stacking|tabbed 176*layout* default|splith|splitv|stacking|tabbed
177 Sets the layout mode of the focused container. 177 Sets the layout mode of the focused container.
178 178
179 When using the _stacking_ layout, only the focused window in the container is
180 displayed, with the opened windows' list on the top of the container.
181
182 The _tabbed_ layout is similar to _stacking_, but the windows’ list is vertically
183 split.
184
179*layout* toggle [split|all] 185*layout* toggle [split|all]
180 Cycles the layout mode of the focused container though a preset list of 186 Cycles the layout mode of the focused container though a preset list of
181 layouts. If no argument is given, then it cycles through stacking, tabbed 187 layouts. If no argument is given, then it cycles through stacking, tabbed
@@ -210,15 +216,14 @@ set|plus|minus <amount>
210 further details. 216 further details.
211 217
212*move* left|right|up|down [<px> px] 218*move* left|right|up|down [<px> px]
213 Moves the focused container in the direction specified. If the container, 219 Moves the focused container in the direction specified. The optional _px_
214 the optional _px_ argument specifies how many pixels to move the container. 220 argument specifies how many pixels to move the container. If unspecified,
215 If unspecified, the default is 10 pixels. Pixels are ignored when moving 221 the default is 10 pixels. Pixels are ignored when moving tiled containers.
216 tiled containers.
217 222
218*move* [absolute] position <pos_x> [px|ppt] <pos_y> [px|ptt] 223*move* [absolute] position <pos_x> [px|ppt] <pos_y> [px|ppt]
219 Moves the focused container to the specified position in the workspace. 224 Moves the focused container to the specified position in the workspace.
220 The position can be specified in pixels or percentage points, omitting 225 The position can be specified in pixels or percentage points, omitting
221 the unit defaults to pixels. If _absolute_ is used, the position is 226 the unit defaults to pixels. If _absolute_ is used, the position is
222 relative to all outputs. _absolute_ can not be used with percentage points. 227 relative to all outputs. _absolute_ can not be used with percentage points.
223 228
224*move* [absolute] position center 229*move* [absolute] position center
@@ -319,8 +324,10 @@ set|plus|minus <amount>
319 established by the *seat* subcommand of the same name. See 324 established by the *seat* subcommand of the same name. See
320 *sway-input*(5) for more ways to affect inhibitors. 325 *sway-input*(5) for more ways to affect inhibitors.
321 326
322*split* vertical|v|horizontal|h|toggle|t 327*split* vertical|v|horizontal|h|none|n|toggle|t
323 Splits the current container, vertically or horizontally. When _toggle_ is 328 Splits the current container, vertically or horizontally. When _none_ is
329 specified, the effect of a previous split is undone if the current
330 container is the only child of a split parent. When _toggle_ is
324 specified, the current container is split opposite to the parent 331 specified, the current container is split opposite to the parent
325 container's layout. 332 container's layout.
326 333
@@ -382,8 +389,8 @@ runtime.
382 for_window <criteria> move container to output <output> 389 for_window <criteria> move container to output <output>
383 390
384*bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ 391*bindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
385[--to-code] [--input-device=<device>] [--no-warn] [--no-repeat] [Group<1-4>+]<key combo> \ 392[--to-code] [--input-device=<device>] [--no-warn] [--no-repeat] [--inhibited] \
386<command> 393[Group<1-4>+]<key combo> <command>
387 Binds _key combo_ to execute the sway command _command_ when pressed. You 394 Binds _key combo_ to execute the sway command _command_ when pressed. You
388 may use XKB key names here (*wev*(1) is a good tool for discovering these). 395 may use XKB key names here (*wev*(1) is a good tool for discovering these).
389 With the flag _--release_, the command is executed when the key combo is 396 With the flag _--release_, the command is executed when the key combo is
@@ -404,7 +411,7 @@ runtime.
404 a keyboard shortcuts inhibitor is active for the currently focused 411 a keyboard shortcuts inhibitor is active for the currently focused
405 window. Such inhibitors are usually requested by remote desktop and 412 window. Such inhibitors are usually requested by remote desktop and
406 virtualization software to enable the user to send keyboard shortcuts 413 virtualization software to enable the user to send keyboard shortcuts
407 to the remote or virtual session. The _--inhibited_ flag allows to 414 to the remote or virtual session. The _--inhibited_ flag allows one to
408 define bindings which will be exempt from pass-through to such 415 define bindings which will be exempt from pass-through to such
409 software. The same preference logic as for _--locked_ applies. 416 software. The same preference logic as for _--locked_ applies.
410 417
@@ -447,7 +454,8 @@ runtime.
447``` 454```
448 455
449 *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \ 456 *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \
450[--locked] [--input-device=<device>] [--no-warn] [Group<1-4>+]<code> <command> 457[--locked] [--input-device=<device>] [--no-warn] [--no-repeat] [--inhibited] \
458[Group<1-4>+]<code> <command>
451 is also available for binding with key/button codes instead of key/button names. 459 is also available for binding with key/button codes instead of key/button names.
452 460
453*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command> 461*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command>
@@ -480,6 +488,62 @@ runtime.
480 bindswitch lid:toggle exec echo "Lid moved" 488 bindswitch lid:toggle exec echo "Lid moved"
481``` 489```
482 490
491*bindgesture* [--exact] [--input-device=<device>] [--no-warn] \
492<gesture>[:<fingers>][:directions] <command>
493 Binds _gesture_ to execute the sway command _command_ when detected.
494 Currently supports the _hold_, _pinch_ or _swipe_ gesture. Optionally
495 can be limited to bind to a certain number of _fingers_ or, for a
496 _pinch_ or _swipe_ gesture, to certain _directions_.
497
498[[ *type*
499:[ *fingers*
500:< *direction*
501| hold
502:- 1 - 5
503: none
504| swipe
505: 3 - 5
506: up, down, left, right
507| pinch
508: 2 - 5
509: all above + inward, outward, clockwise, counterclockwise
510
511 The _fingers_ can be limited to any sensible number or left empty to accept
512 any finger counts.
513 Valid directions are _up_, _down_, _left_ and _right_, as well as _inward_,
514 _outward_, _clockwise_, _counterclockwise_ for the _pinch_ gesture.
515 Multiple directions can be combined by a plus.
516
517 If a _input-device_ is given, the binding will only be executed for
518 that input device and will be executed instead of any binding that is
519 generic to all devices. By default, if you overwrite a binding,
520 swaynag will give you a warning. To silence this, use the _--no-warn_ flag.
521
522 The _--exact_ flag can be used to ensure a binding only matches when exactly
523 all specified directions are matched and nothing more. If there is matching
524 binding with _--exact_, it will be preferred.
525
526 The priority for matching bindings is as follows: input device, then
527 exact matches followed by matches with the highest number of matching
528 directions.
529
530 Gestures executed while the pointer is above a bar are not handled by sway.
531 See the respective documentation, e.g. *bindgesture* in *sway-bar*(5).
532
533 Example:
534```
535 # Allow switching between workspaces with left and right swipes
536 bindgesture swipe:right workspace prev
537 bindgesture swipe:left workspace next
538
539 # Allow container movements by pinching them
540 bindgesture pinch:inward+up move up
541 bindgesture pinch:inward+down move down
542 bindgesture pinch:inward+left move left
543 bindgesture pinch:inward+right move right
544
545```
546
483*client.background* <color> 547*client.background* <color>
484 This command is ignored and is only present for i3 compatibility. 548 This command is ignored and is only present for i3 compatibility.
485 549
@@ -497,6 +561,12 @@ runtime.
497 *client.focused_inactive* 561 *client.focused_inactive*
498 The most recently focused view within a container which is not focused. 562 The most recently focused view within a container which is not focused.
499 563
564 *client.focused_tab_title*
565 A view that has focused descendant container.
566 Tab or stack container title that is the parent of the focused container
567 but is not directly focused. Defaults to focused_inactive if not
568 specified and does not use the indicator and child_border colors.
569
500 *client.placeholder* 570 *client.placeholder*
501 Ignored (present for i3 compatibility). 571 Ignored (present for i3 compatibility).
502 572
@@ -552,6 +622,12 @@ The default colors are:
552: #ffffff 622: #ffffff
553: #484e50 623: #484e50
554: #5f676a 624: #5f676a
625| *focused_tab_title*
626: #333333
627: #5f676a
628: #ffffff
629: n/a
630: n/a
555| *unfocused* 631| *unfocused*
556: #333333 632: #333333
557: #222222 633: #222222
@@ -573,7 +649,8 @@ The default colors are:
573 649
574 650
575*default_border* normal|none|pixel [<n>] 651*default_border* normal|none|pixel [<n>]
576 Set default border style for new tiled windows. 652 Set default border style for new tiled windows. Config reload won't affect
653 existing windows, only newly created ones after the reload.
577 654
578*default_floating_border* normal|none|pixel [<n>] 655*default_floating_border* normal|none|pixel [<n>]
579 Set default border style for new floating windows. This only applies to 656 Set default border style for new floating windows. This only applies to
@@ -606,11 +683,11 @@ The default colors are:
606 after switching between workspaces. 683 after switching between workspaces.
607 684
608*focus_on_window_activation* smart|urgent|focus|none 685*focus_on_window_activation* smart|urgent|focus|none
609 This option determines what to do when an xwayland client requests 686 This option determines what to do when a client requests window activation.
610 window activation. If set to _urgent_, the urgent state will be set 687 If set to _urgent_, the urgent state will be set for that window. If set to
611 for that window. If set to _focus_, the window will become focused. 688 _focus_, the window will become focused. If set to _smart_, the window will
612 If set to _smart_, the window will become focused only if it is already 689 become focused only if it is already visible, otherwise the urgent state
613 visible, otherwise the urgent state will be set. Default is _urgent_. 690 will be set. Default is _urgent_.
614 691
615*focus_wrapping* yes|no|force|workspace 692*focus_wrapping* yes|no|force|workspace
616 This option determines what to do when attempting to focus over the edge 693 This option determines what to do when attempting to focus over the edge
@@ -630,14 +707,14 @@ The default colors are:
630 should be used instead. Regardless of whether pango markup is enabled, 707 should be used instead. Regardless of whether pango markup is enabled,
631 _font_ should be specified as a pango font description. For more 708 _font_ should be specified as a pango font description. For more
632 information on pango font descriptions, see 709 information on pango font descriptions, see
633 https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string 710 https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html#description
634 711
635*force_display_urgency_hint* <timeout> [ms] 712*force_display_urgency_hint* <timeout> [ms]
636 If an application on another workspace sets an urgency hint, switching to this 713 If an application on another workspace sets an urgency hint, switching to this
637 workspace may lead to immediate focus of the application, which also means the 714 workspace may lead to immediate focus of the application, which also means the
638 window decoration color would be immediately resetted to *client.focused*. This 715 window decoration color would be immediately reset to *client.focused*. This
639 may make it unnecessarily hard to tell which window originally raised the 716 may make it unnecessarily hard to tell which window originally raised the
640 event. This option allows to set a _timeout_ in ms to delay the urgency hint reset. 717 event. This option allows one to set a _timeout_ in ms to delay the urgency hint reset.
641 718
642*titlebar_border_thickness* <thickness> 719*titlebar_border_thickness* <thickness>
643 Thickness of the titlebar border in pixels 720 Thickness of the titlebar border in pixels
@@ -690,9 +767,10 @@ The default colors are:
690 borders will only be enabled if the workspace has more than one visible 767 borders will only be enabled if the workspace has more than one visible
691 child and gaps equal to zero. 768 child and gaps equal to zero.
692 769
693*smart_gaps* on|off 770*smart_gaps* on|off|toggle|inverse_outer
694 If smart_gaps are _on_ gaps will only be enabled if a workspace has more 771 If smart_gaps are _on_ gaps will only be enabled if a workspace has more
695 than one child. 772 than one child. If smart_gaps are _inverse_outer_ outer gaps will only
773 be enabled if a workspace has exactly one child.
696 774
697*mark* --add|--replace [--toggle] <identifier> 775*mark* --add|--replace [--toggle] <identifier>
698 Marks are arbitrary labels that can be used to identify certain windows and 776 Marks are arbitrary labels that can be used to identify certain windows and
@@ -731,6 +809,10 @@ The default colors are:
731 dialog will not be rendered. If _leave_fullscreen_, the view will exit 809 dialog will not be rendered. If _leave_fullscreen_, the view will exit
732 fullscreen mode and the dialog will be rendered. 810 fullscreen mode and the dialog will be rendered.
733 811
812*primary_selection* enabled|disabled
813 Enable or disable the primary selection clipboard. May only be configured
814 at launch. Default is _enabled_.
815
734*set* $<name> <value> 816*set* $<name> <value>
735 Sets variable $_name_ to _value_. You can use the new variable in the 817 Sets variable $_name_ to _value_. You can use the new variable in the
736 arguments of future commands. When the variable is used, it can be escaped 818 arguments of future commands. When the variable is used, it can be escaped
@@ -771,6 +853,11 @@ The default colors are:
771*unbindswitch* <switch>:<state> 853*unbindswitch* <switch>:<state>
772 Removes a binding for when <switch> changes to <state>. 854 Removes a binding for when <switch> changes to <state>.
773 855
856*unbindgesture* [--exact] [--input-device=<device>] \
857<gesture>[:<fingers>][:directions]
858 Removes a binding for the specified _gesture_, _fingers_
859 and _directions_ combination.
860
774*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ 861*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
775[--to-code] [--input-device=<device>] <key combo> 862[--to-code] [--input-device=<device>] <key combo>
776 Removes the binding for _key combo_ that was previously bound with the 863 Removes the binding for _key combo_ that was previously bound with the
@@ -876,6 +963,9 @@ properties in practice for your applications.
876 963
877The following attributes may be matched with: 964The following attributes may be matched with:
878 965
966*all*
967 Matches all windows.
968
879*app_id* 969*app_id*
880 Compare value against the app id. Can be a regular expression. If value is 970 Compare value against the app id. Can be a regular expression. If value is
881 \_\_focused\_\_, then the app id must be the same as that of the currently 971 \_\_focused\_\_, then the app id must be the same as that of the currently
@@ -884,7 +974,8 @@ The following attributes may be matched with:
884*class* 974*class*
885 Compare value against the window class. Can be a regular expression. If 975 Compare value against the window class. Can be a regular expression. If
886 value is \_\_focused\_\_, then the window class must be the same as that of 976 value is \_\_focused\_\_, then the window class must be the same as that of
887 the currently focused window. _class_ are specific to X11 applications. 977 the currently focused window. _class_ are specific to X11 applications and
978 require XWayland.
888 979
889*con_id* 980*con_id*
890 Compare against the internal container ID, which you can find via IPC. If 981 Compare against the internal container ID, which you can find via IPC. If
@@ -898,12 +989,14 @@ The following attributes may be matched with:
898 Matches floating windows. 989 Matches floating windows.
899 990
900*id* 991*id*
901 Compare value against the X11 window ID. Must be numeric. 992 Compare value against the X11 window ID. Must be numeric. id is specific to
993 X11 applications and requires XWayland.
902 994
903*instance* 995*instance*
904 Compare value against the window instance. Can be a regular expression. If 996 Compare value against the window instance. Can be a regular expression. If
905 value is \_\_focused\_\_, then the window instance must be the same as that 997 value is \_\_focused\_\_, then the window instance must be the same as that
906 of the currently focused window. 998 of the currently focused window. instance is specific to X11 applications and
999 requires XWayland.
907 1000
908*pid* 1001*pid*
909 Compare value against the window's process ID. Must be numeric. 1002 Compare value against the window's process ID. Must be numeric.
@@ -928,12 +1021,14 @@ The following attributes may be matched with:
928*window_role* 1021*window_role*
929 Compare against the window role (WM_WINDOW_ROLE). Can be a regular 1022 Compare against the window role (WM_WINDOW_ROLE). Can be a regular
930 expression. If value is \_\_focused\_\_, then the window role must be the 1023 expression. If value is \_\_focused\_\_, then the window role must be the
931 same as that of the currently focused window. 1024 same as that of the currently focused window. window_role is specific to X11
1025 applications and requires XWayland.
932 1026
933*window_type* 1027*window_type*
934 Compare against the window type (\_NET_WM_WINDOW_TYPE). Possible values 1028 Compare against the window type (\_NET_WM_WINDOW_TYPE). Possible values
935 are normal, dialog, utility, toolbar, splash, menu, dropdown_menu, 1029 are normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
936 popup_menu, tooltip and notification. 1030 popup_menu, tooltip and notification. window_type is specific to X11
1031 applications and requires XWayland.
937 1032
938*workspace* 1033*workspace*
939 Compare against the workspace name for this view. Can be a regular 1034 Compare against the workspace name for this view. Can be a regular
diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c
new file mode 100644
index 00000000..b9a77d94
--- /dev/null
+++ b/sway/sway_text_node.c
@@ -0,0 +1,303 @@
1#define _POSIX_C_SOURCE 200809L
2#include <drm_fourcc.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <wlr/types/wlr_buffer.h>
7#include <wlr/interfaces/wlr_buffer.h>
8#include "cairo_util.h"
9#include "log.h"
10#include "pango.h"
11#include "sway/config.h"
12#include "sway/sway_text_node.h"
13
14struct cairo_buffer {
15 struct wlr_buffer base;
16 cairo_surface_t *surface;
17 cairo_t *cairo;
18};
19
20static void cairo_buffer_handle_destroy(struct wlr_buffer *wlr_buffer) {
21 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
22
23 cairo_surface_destroy(buffer->surface);
24 cairo_destroy(buffer->cairo);
25 free(buffer);
26}
27
28static bool cairo_buffer_handle_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
29 uint32_t flags, void **data, uint32_t *format, size_t *stride) {
30 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
31 *data = cairo_image_surface_get_data(buffer->surface);
32 *stride = cairo_image_surface_get_stride(buffer->surface);
33 *format = DRM_FORMAT_ARGB8888;
34 return true;
35}
36
37static void cairo_buffer_handle_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
38 // This space is intentionally left blank
39}
40
41static const struct wlr_buffer_impl cairo_buffer_impl = {
42 .destroy = cairo_buffer_handle_destroy,
43 .begin_data_ptr_access = cairo_buffer_handle_begin_data_ptr_access,
44 .end_data_ptr_access = cairo_buffer_handle_end_data_ptr_access,
45};
46
47struct text_buffer {
48 struct wlr_scene_buffer *buffer_node;
49 char *text;
50 struct sway_text_node props;
51
52 bool visible;
53 float scale;
54 enum wl_output_subpixel subpixel;
55
56 struct wl_listener outputs_update;
57 struct wl_listener destroy;
58};
59
60static int get_text_width(struct sway_text_node *props) {
61 if (props->max_width) {
62 return MIN(props->max_width, props->width);
63 }
64
65 return props->width;
66}
67
68static void update_source_box(struct text_buffer *buffer) {
69 struct sway_text_node *props = &buffer->props;
70 struct wlr_fbox source_box = {
71 .x = 0,
72 .y = 0,
73 .width = ceil(get_text_width(props) * buffer->scale),
74 .height = ceil(props->height * buffer->scale),
75 };
76
77 wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box);
78}
79
80static void render_backing_buffer(struct text_buffer *buffer) {
81 if (!buffer->visible) {
82 return;
83 }
84
85 float scale = buffer->scale;
86 int width = ceil(buffer->props.width * scale);
87 int height = ceil(buffer->props.height * scale);
88 float *color = (float *)&buffer->props.color;
89 float *background = (float *)&buffer->props.background;
90 PangoContext *pango = NULL;
91
92 cairo_font_options_t *fo = cairo_font_options_create();
93 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
94 enum wl_output_subpixel subpixel = buffer->subpixel;
95 if (subpixel == WL_OUTPUT_SUBPIXEL_NONE || subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
96 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
97 } else {
98 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
99 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel));
100 }
101
102 cairo_surface_t *surface = cairo_image_surface_create(
103 CAIRO_FORMAT_ARGB32, width, height);
104 cairo_status_t status = cairo_surface_status(surface);
105 if (status != CAIRO_STATUS_SUCCESS) {
106 sway_log(SWAY_ERROR, "cairo_image_surface_create failed: %s",
107 cairo_status_to_string(status));
108 goto err;
109 }
110
111 struct cairo_buffer *cairo_buffer = calloc(1, sizeof(*cairo_buffer));
112 if (!cairo_buffer) {
113 sway_log(SWAY_ERROR, "cairo_buffer allocation failed");
114 goto err;
115 }
116
117 cairo_t *cairo = cairo_create(surface);
118 if (!cairo) {
119 sway_log(SWAY_ERROR, "cairo_create failed");
120 free(cairo_buffer);
121 goto err;
122 }
123
124 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
125 cairo_set_font_options(cairo, fo);
126 pango = pango_cairo_create_context(cairo);
127
128 cairo_set_source_rgba(cairo, background[0], background[1], background[2], background[3]);
129 cairo_rectangle(cairo, 0, 0, width, height);
130 cairo_fill(cairo);
131
132 cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]);
133 cairo_move_to(cairo, 0, (config->font_baseline - buffer->props.baseline) * scale);
134
135 render_text(cairo, config->font_description, scale, buffer->props.pango_markup,
136 "%s", buffer->text);
137
138 cairo_surface_flush(surface);
139
140 wlr_buffer_init(&cairo_buffer->base, &cairo_buffer_impl, width, height);
141 cairo_buffer->surface = surface;
142 cairo_buffer->cairo = cairo;
143
144 wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base);
145 wlr_buffer_drop(&cairo_buffer->base);
146 update_source_box(buffer);
147
148 pixman_region32_t opaque;
149 pixman_region32_init(&opaque);
150 if (background[3] == 1) {
151 pixman_region32_union_rect(&opaque, &opaque, 0, 0,
152 buffer->props.width, buffer->props.height);
153 }
154 wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque);
155 pixman_region32_fini(&opaque);
156
157err:
158 if (pango) g_object_unref(pango);
159 cairo_font_options_destroy(fo);
160}
161
162static void handle_outputs_update(struct wl_listener *listener, void *data) {
163 struct text_buffer *buffer = wl_container_of(listener, buffer, outputs_update);
164 struct wlr_scene_outputs_update_event *event = data;
165
166 float scale = 0;
167 enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
168
169 for (size_t i = 0; i < event->size; i++) {
170 struct wlr_scene_output *output = event->active[i];
171 if (subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
172 subpixel = output->output->subpixel;
173 } else if (subpixel != output->output->subpixel) {
174 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
175 }
176
177 if (scale != 0 && scale != output->output->scale) {
178 // drop down to gray scale if we encounter outputs with different
179 // scales or else we will have chromatic aberations
180 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
181 }
182
183 if (scale < output->output->scale) {
184 scale = output->output->scale;
185 }
186 }
187
188 buffer->visible = event->size > 0;
189
190 if (scale != buffer->scale || subpixel != buffer->subpixel) {
191 buffer->scale = scale;
192 buffer->subpixel = subpixel;
193 render_backing_buffer(buffer);
194 }
195}
196
197static void handle_destroy(struct wl_listener *listener, void *data) {
198 struct text_buffer *buffer = wl_container_of(listener, buffer, destroy);
199
200 wl_list_remove(&buffer->outputs_update.link);
201 wl_list_remove(&buffer->destroy.link);
202
203 free(buffer->text);
204 free(buffer);
205}
206
207static void text_calc_size(struct text_buffer *buffer) {
208 struct sway_text_node *props = &buffer->props;
209
210 cairo_t *c = cairo_create(NULL);
211 if (!c) {
212 sway_log(SWAY_ERROR, "cairo_t allocation failed");
213 return;
214 }
215
216 cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
217 get_text_size(c, config->font_description, &props->width, NULL,
218 &props->baseline, 1, props->pango_markup, "%s", buffer->text);
219 cairo_destroy(c);
220
221 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
222 get_text_width(props), props->height);
223}
224
225struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent,
226 char *text, float color[4], bool pango_markup) {
227 struct text_buffer *buffer = calloc(1, sizeof(*buffer));
228 if (!buffer) {
229 return NULL;
230 }
231
232 struct wlr_scene_buffer *node = wlr_scene_buffer_create(parent, NULL);
233 if (!node) {
234 free(buffer);
235 return NULL;
236 }
237
238 buffer->buffer_node = node;
239 buffer->props.node = &node->node;
240 buffer->text = strdup(text);
241 if (!buffer->text) {
242 free(buffer);
243 wlr_scene_node_destroy(&node->node);
244 return NULL;
245 }
246
247 buffer->props.height = config->font_height;
248 buffer->props.pango_markup = pango_markup;
249 memcpy(&buffer->props.color, color, sizeof(*color) * 4);
250
251 buffer->destroy.notify = handle_destroy;
252 wl_signal_add(&node->node.events.destroy, &buffer->destroy);
253 buffer->outputs_update.notify = handle_outputs_update;
254 wl_signal_add(&node->events.outputs_update, &buffer->outputs_update);
255
256 text_calc_size(buffer);
257
258 return &buffer->props;
259}
260
261void sway_text_node_set_color(struct sway_text_node *node, float color[4]) {
262 if (memcmp(&node->color, color, sizeof(*color) * 4) == 0) {
263 return;
264 }
265
266 memcpy(&node->color, color, sizeof(*color) * 4);
267 struct text_buffer *buffer = wl_container_of(node, buffer, props);
268
269 render_backing_buffer(buffer);
270}
271
272void sway_text_node_set_text(struct sway_text_node *node, char *text) {
273 struct text_buffer *buffer = wl_container_of(node, buffer, props);
274 if (strcmp(buffer->text, text) == 0) {
275 return;
276 }
277
278 char *new_text = strdup(text);
279 if (!new_text) {
280 return;
281 }
282
283 free(buffer->text);
284 buffer->text = new_text;
285
286 text_calc_size(buffer);
287 render_backing_buffer(buffer);
288}
289
290void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) {
291 struct text_buffer *buffer = wl_container_of(node, buffer, props);
292 buffer->props.max_width = max_width;
293 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
294 get_text_width(&buffer->props), buffer->props.height);
295 update_source_box(buffer);
296 render_backing_buffer(buffer);
297}
298
299void sway_text_node_set_background(struct sway_text_node *node, float background[4]) {
300 struct text_buffer *buffer = wl_container_of(node, buffer, props);
301 memcpy(&node->background, background, sizeof(*background) * 4);
302 render_backing_buffer(buffer);
303}
diff --git a/sway/swaynag.c b/sway/swaynag.c
index db5a919a..6031174d 100644
--- a/sway/swaynag.c
+++ b/sway/swaynag.c
@@ -64,6 +64,8 @@ bool swaynag_spawn(const char *swaynag_command,
64 sway_log(SWAY_ERROR, "Failed to create fork for swaynag"); 64 sway_log(SWAY_ERROR, "Failed to create fork for swaynag");
65 goto failed; 65 goto failed;
66 } else if (pid == 0) { 66 } else if (pid == 0) {
67 restore_nofile_limit();
68
67 pid = fork(); 69 pid = fork();
68 if (pid < 0) { 70 if (pid < 0) {
69 sway_log_errno(SWAY_ERROR, "fork failed"); 71 sway_log_errno(SWAY_ERROR, "fork failed");
@@ -87,8 +89,8 @@ bool swaynag_spawn(const char *swaynag_command,
87 size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2; 89 size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2;
88 char *cmd = malloc(length); 90 char *cmd = malloc(length);
89 snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args); 91 snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args);
90 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); 92 execlp("sh", "sh", "-c", cmd, NULL);
91 sway_log_errno(SWAY_ERROR, "execl failed"); 93 sway_log_errno(SWAY_ERROR, "execlp failed");
92 _exit(EXIT_FAILURE); 94 _exit(EXIT_FAILURE);
93 } 95 }
94 _exit(EXIT_SUCCESS); 96 _exit(EXIT_SUCCESS);
@@ -143,22 +145,16 @@ void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag,
143 145
144 va_list args; 146 va_list args;
145 va_start(args, fmt); 147 va_start(args, fmt);
146 size_t length = vsnprintf(NULL, 0, fmt, args) + 1; 148 char *str = vformat_str(fmt, args);
147 va_end(args); 149 va_end(args);
148 150 if (!str) {
149 char *temp = malloc(length + 1);
150 if (!temp) {
151 sway_log(SWAY_ERROR, "Failed to allocate buffer for swaynag log entry."); 151 sway_log(SWAY_ERROR, "Failed to allocate buffer for swaynag log entry.");
152 return; 152 return;
153 } 153 }
154 154
155 va_start(args, fmt); 155 write(swaynag->fd[1], str, strlen(str));
156 vsnprintf(temp, length, fmt, args);
157 va_end(args);
158
159 write(swaynag->fd[1], temp, length);
160 156
161 free(temp); 157 free(str);
162} 158}
163 159
164void swaynag_show(struct swaynag_instance *swaynag) { 160void swaynag_show(struct swaynag_instance *swaynag) {
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index bac9f2fa..af925d05 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -55,7 +55,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
55 // Calculate gap size 55 // Calculate gap size
56 double inner_gap = 0; 56 double inner_gap = 0;
57 struct sway_container *child = children->items[0]; 57 struct sway_container *child = children->items[0];
58 struct sway_workspace *ws = child->workspace; 58 struct sway_workspace *ws = child->pending.workspace;
59 if (ws) { 59 if (ws) {
60 inner_gap = ws->gaps_inner; 60 inner_gap = ws->gaps_inner;
61 } 61 }
@@ -66,7 +66,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
66 if (layout == L_TABBED || layout == L_STACKED) { 66 if (layout == L_TABBED || layout == L_STACKED) {
67 inner_gap = 0; 67 inner_gap = 0;
68 } 68 }
69 temp = temp->parent; 69 temp = temp->pending.parent;
70 } 70 }
71 double total_gap = fmin(inner_gap * (children->length - 1), 71 double total_gap = fmin(inner_gap * (children->length - 1),
72 fmax(0, parent->width - MIN_SANE_W * children->length)); 72 fmax(0, parent->width - MIN_SANE_W * children->length));
@@ -79,15 +79,15 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
79 for (int i = 0; i < children->length; ++i) { 79 for (int i = 0; i < children->length; ++i) {
80 struct sway_container *child = children->items[i]; 80 struct sway_container *child = children->items[i];
81 child->child_total_width = child_total_width; 81 child->child_total_width = child_total_width;
82 child->x = child_x; 82 child->pending.x = child_x;
83 child->y = parent->y; 83 child->pending.y = parent->y;
84 child->width = round(child->width_fraction * child_total_width); 84 child->pending.width = round(child->width_fraction * child_total_width);
85 child->height = parent->height; 85 child->pending.height = parent->height;
86 child_x += child->width + inner_gap; 86 child_x += child->pending.width + inner_gap;
87 87
88 // Make last child use remaining width of parent 88 // Make last child use remaining width of parent
89 if (i == children->length - 1) { 89 if (i == children->length - 1) {
90 child->width = parent->x + parent->width - child->x; 90 child->pending.width = parent->x + parent->width - child->pending.x;
91 } 91 }
92 } 92 }
93} 93}
@@ -134,7 +134,7 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
134 // Calculate gap size 134 // Calculate gap size
135 double inner_gap = 0; 135 double inner_gap = 0;
136 struct sway_container *child = children->items[0]; 136 struct sway_container *child = children->items[0];
137 struct sway_workspace *ws = child->workspace; 137 struct sway_workspace *ws = child->pending.workspace;
138 if (ws) { 138 if (ws) {
139 inner_gap = ws->gaps_inner; 139 inner_gap = ws->gaps_inner;
140 } 140 }
@@ -145,7 +145,7 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
145 if (layout == L_TABBED || layout == L_STACKED) { 145 if (layout == L_TABBED || layout == L_STACKED) {
146 inner_gap = 0; 146 inner_gap = 0;
147 } 147 }
148 temp = temp->parent; 148 temp = temp->pending.parent;
149 } 149 }
150 double total_gap = fmin(inner_gap * (children->length - 1), 150 double total_gap = fmin(inner_gap * (children->length - 1),
151 fmax(0, parent->height - MIN_SANE_H * children->length)); 151 fmax(0, parent->height - MIN_SANE_H * children->length));
@@ -158,15 +158,15 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
158 for (int i = 0; i < children->length; ++i) { 158 for (int i = 0; i < children->length; ++i) {
159 struct sway_container *child = children->items[i]; 159 struct sway_container *child = children->items[i];
160 child->child_total_height = child_total_height; 160 child->child_total_height = child_total_height;
161 child->x = parent->x; 161 child->pending.x = parent->x;
162 child->y = child_y; 162 child->pending.y = child_y;
163 child->width = parent->width; 163 child->pending.width = parent->width;
164 child->height = round(child->height_fraction * child_total_height); 164 child->pending.height = round(child->height_fraction * child_total_height);
165 child_y += child->height + inner_gap; 165 child_y += child->pending.height + inner_gap;
166 166
167 // Make last child use remaining height of parent 167 // Make last child use remaining height of parent
168 if (i == children->length - 1) { 168 if (i == children->length - 1) {
169 child->height = parent->y + parent->height - child->y; 169 child->pending.height = parent->y + parent->height - child->pending.y;
170 } 170 }
171 } 171 }
172} 172}
@@ -178,10 +178,10 @@ static void apply_tabbed_layout(list_t *children, struct wlr_box *parent) {
178 for (int i = 0; i < children->length; ++i) { 178 for (int i = 0; i < children->length; ++i) {
179 struct sway_container *child = children->items[i]; 179 struct sway_container *child = children->items[i];
180 int parent_offset = child->view ? 0 : container_titlebar_height(); 180 int parent_offset = child->view ? 0 : container_titlebar_height();
181 child->x = parent->x; 181 child->pending.x = parent->x;
182 child->y = parent->y + parent_offset; 182 child->pending.y = parent->y + parent_offset;
183 child->width = parent->width; 183 child->pending.width = parent->width;
184 child->height = parent->height - parent_offset; 184 child->pending.height = parent->height - parent_offset;
185 } 185 }
186} 186}
187 187
@@ -193,10 +193,10 @@ static void apply_stacked_layout(list_t *children, struct wlr_box *parent) {
193 struct sway_container *child = children->items[i]; 193 struct sway_container *child = children->items[i];
194 int parent_offset = child->view ? 0 : 194 int parent_offset = child->view ? 0 :
195 container_titlebar_height() * children->length; 195 container_titlebar_height() * children->length;
196 child->x = parent->x; 196 child->pending.x = parent->x;
197 child->y = parent->y + parent_offset; 197 child->pending.y = parent->y + parent_offset;
198 child->width = parent->width; 198 child->pending.width = parent->width;
199 child->height = parent->height - parent_offset; 199 child->pending.height = parent->height - parent_offset;
200 } 200 }
201} 201}
202 202
@@ -246,7 +246,7 @@ void arrange_container(struct sway_container *container) {
246 } 246 }
247 struct wlr_box box; 247 struct wlr_box box;
248 container_get_box(container, &box); 248 container_get_box(container, &box);
249 arrange_children(container->children, container->layout, &box); 249 arrange_children(container->pending.children, container->pending.layout, &box);
250 node_set_dirty(&container->node); 250 node_set_dirty(&container->node);
251} 251}
252 252
@@ -264,6 +264,9 @@ void arrange_workspace(struct sway_workspace *workspace) {
264 area->width, area->height, area->x, area->y); 264 area->width, area->height, area->x, area->y);
265 265
266 bool first_arrange = workspace->width == 0 && workspace->height == 0; 266 bool first_arrange = workspace->width == 0 && workspace->height == 0;
267 struct wlr_box prev_box;
268 workspace_get_box(workspace, &prev_box);
269
267 double prev_x = workspace->x - workspace->current_gaps.left; 270 double prev_x = workspace->x - workspace->current_gaps.left;
268 double prev_y = workspace->y - workspace->current_gaps.top; 271 double prev_y = workspace->y - workspace->current_gaps.top;
269 workspace->width = area->width; 272 workspace->width = area->width;
@@ -277,13 +280,14 @@ void arrange_workspace(struct sway_workspace *workspace) {
277 if (!first_arrange && (diff_x != 0 || diff_y != 0)) { 280 if (!first_arrange && (diff_x != 0 || diff_y != 0)) {
278 for (int i = 0; i < workspace->floating->length; ++i) { 281 for (int i = 0; i < workspace->floating->length; ++i) {
279 struct sway_container *floater = workspace->floating->items[i]; 282 struct sway_container *floater = workspace->floating->items[i];
280 container_floating_translate(floater, diff_x, diff_y);
281 double center_x = floater->x + floater->width / 2;
282 double center_y = floater->y + floater->height / 2;
283 struct wlr_box workspace_box; 283 struct wlr_box workspace_box;
284 workspace_get_box(workspace, &workspace_box); 284 workspace_get_box(workspace, &workspace_box);
285 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) { 285 floating_fix_coordinates(floater, &prev_box, &workspace_box);
286 container_floating_move_to_center(floater); 286 // Set transformation for scratchpad windows.
287 if (floater->scratchpad) {
288 struct wlr_box output_box;
289 output_get_box(output, &output_box);
290 floater->transform = output_box;
287 } 291 }
288 } 292 }
289 } 293 }
@@ -294,10 +298,10 @@ void arrange_workspace(struct sway_workspace *workspace) {
294 workspace->x, workspace->y); 298 workspace->x, workspace->y);
295 if (workspace->fullscreen) { 299 if (workspace->fullscreen) {
296 struct sway_container *fs = workspace->fullscreen; 300 struct sway_container *fs = workspace->fullscreen;
297 fs->x = output->lx; 301 fs->pending.x = output->lx;
298 fs->y = output->ly; 302 fs->pending.y = output->ly;
299 fs->width = output->width; 303 fs->pending.width = output->width;
300 fs->height = output->height; 304 fs->pending.height = output->height;
301 arrange_container(fs); 305 arrange_container(fs);
302 } else { 306 } else {
303 struct wlr_box box; 307 struct wlr_box box;
@@ -311,12 +315,13 @@ void arrange_output(struct sway_output *output) {
311 if (config->reloading) { 315 if (config->reloading) {
312 return; 316 return;
313 } 317 }
314 const struct wlr_box *output_box = wlr_output_layout_get_box( 318 struct wlr_box output_box;
315 root->output_layout, output->wlr_output); 319 wlr_output_layout_get_box(root->output_layout,
316 output->lx = output_box->x; 320 output->wlr_output, &output_box);
317 output->ly = output_box->y; 321 output->lx = output_box.x;
318 output->width = output_box->width; 322 output->ly = output_box.y;
319 output->height = output_box->height; 323 output->width = output_box.width;
324 output->height = output_box.height;
320 325
321 for (int i = 0; i < output->workspaces->length; ++i) { 326 for (int i = 0; i < output->workspaces->length; ++i) {
322 struct sway_workspace *workspace = output->workspaces->items[i]; 327 struct sway_workspace *workspace = output->workspaces->items[i];
@@ -328,19 +333,19 @@ void arrange_root(void) {
328 if (config->reloading) { 333 if (config->reloading) {
329 return; 334 return;
330 } 335 }
331 const struct wlr_box *layout_box = 336 struct wlr_box layout_box;
332 wlr_output_layout_get_box(root->output_layout, NULL); 337 wlr_output_layout_get_box(root->output_layout, NULL, &layout_box);
333 root->x = layout_box->x; 338 root->x = layout_box.x;
334 root->y = layout_box->y; 339 root->y = layout_box.y;
335 root->width = layout_box->width; 340 root->width = layout_box.width;
336 root->height = layout_box->height; 341 root->height = layout_box.height;
337 342
338 if (root->fullscreen_global) { 343 if (root->fullscreen_global) {
339 struct sway_container *fs = root->fullscreen_global; 344 struct sway_container *fs = root->fullscreen_global;
340 fs->x = root->x; 345 fs->pending.x = root->x;
341 fs->y = root->y; 346 fs->pending.y = root->y;
342 fs->width = root->width; 347 fs->pending.width = root->width;
343 fs->height = root->height; 348 fs->pending.height = root->height;
344 arrange_container(fs); 349 arrange_container(fs);
345 } else { 350 } else {
346 for (int i = 0; i < root->outputs->length; ++i) { 351 for (int i = 0; i < root->outputs->length; ++i) {
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 6a9ce1c4..30cb97ba 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -1,28 +1,77 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <drm_fourcc.h>
3#include <stdint.h> 4#include <stdint.h>
4#include <stdlib.h> 5#include <stdlib.h>
5#include <string.h>
6#include <strings.h>
7#include <wayland-server-core.h> 6#include <wayland-server-core.h>
7#include <wlr/types/wlr_linux_dmabuf_v1.h>
8#include <wlr/types/wlr_output_layout.h> 8#include <wlr/types/wlr_output_layout.h>
9#include "cairo.h" 9#include <wlr/types/wlr_subcompositor.h>
10#include "pango.h" 10#include "linux-dmabuf-unstable-v1-protocol.h"
11#include "sway/config.h" 11#include "sway/config.h"
12#include "sway/desktop.h"
13#include "sway/desktop/transaction.h" 12#include "sway/desktop/transaction.h"
14#include "sway/input/input-manager.h" 13#include "sway/input/input-manager.h"
15#include "sway/input/seat.h" 14#include "sway/input/seat.h"
16#include "sway/ipc-server.h" 15#include "sway/ipc-server.h"
16#include "sway/scene_descriptor.h"
17#include "sway/sway_text_node.h"
17#include "sway/output.h" 18#include "sway/output.h"
18#include "sway/server.h" 19#include "sway/server.h"
19#include "sway/tree/arrange.h" 20#include "sway/tree/arrange.h"
20#include "sway/tree/view.h" 21#include "sway/tree/view.h"
21#include "sway/tree/workspace.h" 22#include "sway/tree/workspace.h"
23#include "sway/xdg_decoration.h"
22#include "list.h" 24#include "list.h"
23#include "log.h" 25#include "log.h"
24#include "stringop.h" 26#include "stringop.h"
25 27
28static void handle_output_enter(
29 struct wl_listener *listener, void *data) {
30 struct sway_container *con = wl_container_of(
31 listener, con, output_enter);
32 struct wlr_scene_output *output = data;
33
34 if (con->view->foreign_toplevel) {
35 wlr_foreign_toplevel_handle_v1_output_enter(
36 con->view->foreign_toplevel, output->output);
37 }
38}
39
40static void handle_output_leave(
41 struct wl_listener *listener, void *data) {
42 struct sway_container *con = wl_container_of(
43 listener, con, output_leave);
44 struct wlr_scene_output *output = data;
45
46 if (con->view->foreign_toplevel) {
47 wlr_foreign_toplevel_handle_v1_output_leave(
48 con->view->foreign_toplevel, output->output);
49 }
50}
51
52static bool handle_point_accepts_input(
53 struct wlr_scene_buffer *buffer, double *x, double *y) {
54 return false;
55}
56
57static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent,
58 bool *failed) {
59 if (*failed) {
60 return NULL;
61 }
62
63 // just pass in random values. These will be overwritten when
64 // they need to be used.
65 struct wlr_scene_rect *rect = wlr_scene_rect_create(
66 parent, 0, 0, (float[4]){0.f, 0.f, 0.f, 1.f});
67 if (!rect) {
68 sway_log(SWAY_ERROR, "Failed to allocate a wlr_scene_rect");
69 *failed = true;
70 }
71
72 return rect;
73}
74
26struct sway_container *container_create(struct sway_view *view) { 75struct sway_container *container_create(struct sway_view *view) {
27 struct sway_container *c = calloc(1, sizeof(struct sway_container)); 76 struct sway_container *c = calloc(1, sizeof(struct sway_container));
28 if (!c) { 77 if (!c) {
@@ -30,23 +79,411 @@ struct sway_container *container_create(struct sway_view *view) {
30 return NULL; 79 return NULL;
31 } 80 }
32 node_init(&c->node, N_CONTAINER, c); 81 node_init(&c->node, N_CONTAINER, c);
33 c->layout = L_NONE; 82
34 c->view = view; 83 // Container tree structure
35 c->alpha = 1.0f; 84 // - scene tree
85 // - title bar
86 // - border
87 // - background
88 // - title text
89 // - marks text
90 // - border
91 // - border top/bottom/left/right
92 // - content_tree (we put the content node here so when we disable the
93 // border everything gets disabled. We only render the content iff there
94 // is a border as well)
95 // - buffer used for output enter/leave events for foreign_toplevel
96 bool failed = false;
97 c->scene_tree = alloc_scene_tree(root->staging, &failed);
98
99 c->title_bar.tree = alloc_scene_tree(c->scene_tree, &failed);
100 c->title_bar.border = alloc_scene_tree(c->title_bar.tree, &failed);
101 c->title_bar.background = alloc_scene_tree(c->title_bar.tree, &failed);
102
103 // for opacity purposes we need to carfully create the scene such that
104 // none of our rect nodes as well as text buffers don't overlap. To do
105 // this we have to create rects such that they go around text buffers
106 for (int i = 0; i < 4; i++) {
107 alloc_rect_node(c->title_bar.border, &failed);
108 }
109
110 for (int i = 0; i < 5; i++) {
111 alloc_rect_node(c->title_bar.background, &failed);
112 }
113
114 c->border.tree = alloc_scene_tree(c->scene_tree, &failed);
115 c->content_tree = alloc_scene_tree(c->border.tree, &failed);
116
117 if (view) {
118 // only containers with views can have borders
119 c->border.top = alloc_rect_node(c->border.tree, &failed);
120 c->border.bottom = alloc_rect_node(c->border.tree, &failed);
121 c->border.left = alloc_rect_node(c->border.tree, &failed);
122 c->border.right = alloc_rect_node(c->border.tree, &failed);
123
124 c->output_handler = wlr_scene_buffer_create(c->border.tree, NULL);
125 if (!c->output_handler) {
126 sway_log(SWAY_ERROR, "Failed to allocate a scene node");
127 failed = true;
128 }
129
130 if (!failed) {
131 c->output_enter.notify = handle_output_enter;
132 wl_signal_add(&c->output_handler->events.output_enter,
133 &c->output_enter);
134 c->output_leave.notify = handle_output_leave;
135 wl_signal_add(&c->output_handler->events.output_leave,
136 &c->output_leave);
137 c->output_handler->point_accepts_input = handle_point_accepts_input;
138 }
139 }
140
141 if (!failed && !scene_descriptor_assign(&c->scene_tree->node,
142 SWAY_SCENE_DESC_CONTAINER, c)) {
143 failed = true;
144 }
145
146 if (failed) {
147 wlr_scene_node_destroy(&c->scene_tree->node);
148 free(c);
149 return NULL;
150 }
36 151
37 if (!view) { 152 if (!view) {
38 c->children = create_list(); 153 c->pending.children = create_list();
39 c->current.children = create_list(); 154 c->current.children = create_list();
40 } 155 }
156
157 c->pending.layout = L_NONE;
158 c->view = view;
159 c->alpha = 1.0f;
41 c->marks = create_list(); 160 c->marks = create_list();
42 c->outputs = create_list();
43 161
44 wl_signal_init(&c->events.destroy); 162 wl_signal_init(&c->events.destroy);
45 wl_signal_emit(&root->events.new_node, &c->node); 163 wl_signal_emit_mutable(&root->events.new_node, &c->node);
164
165 container_update(c);
46 166
47 return c; 167 return c;
48} 168}
49 169
170static bool container_is_focused(struct sway_container *con, void *data) {
171 return con->current.focused;
172}
173
174static bool container_has_focused_child(struct sway_container *con) {
175 return container_find_child(con, container_is_focused, NULL);
176}
177
178static bool container_is_current_parent_focused(struct sway_container *con) {
179 if (con->current.parent) {
180 struct sway_container *parent = con->current.parent;
181 return parent->current.focused || container_is_current_parent_focused(parent);
182 } else if (con->current.workspace) {
183 struct sway_workspace *ws = con->current.workspace;
184 return ws->current.focused;
185 }
186
187 return false;
188}
189
190static struct border_colors *container_get_current_colors(
191 struct sway_container *con) {
192 struct border_colors *colors;
193
194 bool urgent = con->view ?
195 view_is_urgent(con->view) : container_has_urgent_child(con);
196 struct sway_container *active_child;
197
198 if (con->current.parent) {
199 active_child = con->current.parent->current.focused_inactive_child;
200 } else if (con->current.workspace) {
201 active_child = con->current.workspace->current.focused_inactive_child;
202 } else {
203 active_child = NULL;
204 }
205
206 if (urgent) {
207 colors = &config->border_colors.urgent;
208 } else if (con->current.focused || container_is_current_parent_focused(con)) {
209 colors = &config->border_colors.focused;
210 } else if (config->has_focused_tab_title && container_has_focused_child(con)) {
211 colors = &config->border_colors.focused_tab_title;
212 } else if (con == active_child) {
213 colors = &config->border_colors.focused_inactive;
214 } else {
215 colors = &config->border_colors.unfocused;
216 }
217
218 return colors;
219}
220
221static bool container_is_current_floating(struct sway_container *container) {
222 if (!container->current.parent && container->current.workspace &&
223 list_find(container->current.workspace->floating, container) != -1) {
224 return true;
225 }
226 if (container->scratchpad) {
227 return true;
228 }
229 return false;
230}
231
232// scene rect wants premultiplied colors
233static void scene_rect_set_color(struct wlr_scene_rect *rect,
234 const float color[4], float opacity) {
235 const float premultiplied[] = {
236 color[0] * color[3] * opacity,
237 color[1] * color[3] * opacity,
238 color[2] * color[3] * opacity,
239 color[3] * opacity,
240 };
241
242 wlr_scene_rect_set_color(rect, premultiplied);
243}
244
245void container_update(struct sway_container *con) {
246 struct border_colors *colors = container_get_current_colors(con);
247 list_t *siblings = NULL;
248 enum sway_container_layout layout = L_NONE;
249 float alpha = con->alpha;
250
251 if (con->current.parent) {
252 siblings = con->current.parent->current.children;
253 layout = con->current.parent->current.layout;
254 } else if (con->current.workspace) {
255 siblings = con->current.workspace->current.tiling;
256 layout = con->current.workspace->current.layout;
257 }
258
259 float bottom[4], right[4];
260 memcpy(bottom, colors->child_border, sizeof(bottom));
261 memcpy(right, colors->child_border, sizeof(right));
262
263 if (!container_is_current_floating(con) && siblings && siblings->length == 1) {
264 if (layout == L_HORIZ) {
265 memcpy(right, colors->indicator, sizeof(right));
266 } else if (layout == L_VERT) {
267 memcpy(bottom, colors->indicator, sizeof(bottom));
268 }
269 }
270
271 struct wlr_scene_node *node;
272 wl_list_for_each(node, &con->title_bar.border->children, link) {
273 struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
274 scene_rect_set_color(rect, colors->border, alpha);
275 }
276
277 wl_list_for_each(node, &con->title_bar.background->children, link) {
278 struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
279 scene_rect_set_color(rect, colors->background, alpha);
280 }
281
282 if (con->view) {
283 scene_rect_set_color(con->border.top, colors->child_border, alpha);
284 scene_rect_set_color(con->border.bottom, bottom, alpha);
285 scene_rect_set_color(con->border.left, colors->child_border, alpha);
286 scene_rect_set_color(con->border.right, right, alpha);
287 }
288
289 if (con->title_bar.title_text) {
290 sway_text_node_set_color(con->title_bar.title_text, colors->text);
291 sway_text_node_set_background(con->title_bar.title_text, colors->background);
292 }
293
294 if (con->title_bar.marks_text) {
295 sway_text_node_set_color(con->title_bar.marks_text, colors->text);
296 sway_text_node_set_background(con->title_bar.marks_text, colors->background);
297 }
298}
299
300void container_update_itself_and_parents(struct sway_container *con) {
301 container_update(con);
302
303 if (con->current.parent) {
304 container_update_itself_and_parents(con->current.parent);
305 }
306}
307
308static void update_rect_list(struct wlr_scene_tree *tree, pixman_region32_t *region) {
309 int len;
310 const pixman_box32_t *rects = pixman_region32_rectangles(region, &len);
311
312 wlr_scene_node_set_enabled(&tree->node, len > 0);
313 if (len == 0) {
314 return;
315 }
316
317 int i = 0;
318 struct wlr_scene_node *node;
319 wl_list_for_each(node, &tree->children, link) {
320 struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
321 wlr_scene_node_set_enabled(&rect->node, i < len);
322
323 if (i < len) {
324 const pixman_box32_t *box = &rects[i++];
325 wlr_scene_node_set_position(&rect->node, box->x1, box->y1);
326 wlr_scene_rect_set_size(rect, box->x2 - box->x1, box->y2 - box->y1);
327 }
328 }
329}
330
331void container_arrange_title_bar(struct sway_container *con) {
332 enum alignment title_align = config->title_align;
333 int marks_buffer_width = 0;
334 int width = con->title_width;
335 int height = container_titlebar_height();
336
337 pixman_region32_t text_area;
338 pixman_region32_init(&text_area);
339
340 if (con->title_bar.marks_text) {
341 struct sway_text_node *node = con->title_bar.marks_text;
342 marks_buffer_width = node->width;
343
344 int h_padding;
345 if (title_align == ALIGN_RIGHT) {
346 h_padding = config->titlebar_h_padding;
347 } else {
348 h_padding = width - config->titlebar_h_padding - marks_buffer_width;
349 }
350
351 h_padding = MAX(h_padding, 0);
352
353 int alloc_width = MIN((int)node->width,
354 width - h_padding - config->titlebar_h_padding);
355 sway_text_node_set_max_width(node, alloc_width);
356 wlr_scene_node_set_position(node->node,
357 h_padding, (height - node->height) >> 1);
358
359 pixman_region32_union_rect(&text_area, &text_area,
360 node->node->x, node->node->y, alloc_width, node->height);
361 }
362
363 if (con->title_bar.title_text) {
364 struct sway_text_node *node = con->title_bar.title_text;
365
366 int h_padding;
367 if (title_align == ALIGN_RIGHT) {
368 h_padding = width - config->titlebar_h_padding - node->width;
369 } else if (title_align == ALIGN_CENTER) {
370 h_padding = ((int)width - marks_buffer_width - node->width) >> 1;
371 } else {
372 h_padding = config->titlebar_h_padding;
373 }
374
375 h_padding = MAX(h_padding, 0);
376
377 int alloc_width = MIN((int) node->width,
378 width - h_padding - config->titlebar_h_padding);
379 sway_text_node_set_max_width(node, alloc_width);
380 wlr_scene_node_set_position(node->node,
381 h_padding, (height - node->height) >> 1);
382
383 pixman_region32_union_rect(&text_area, &text_area,
384 node->node->x, node->node->y, alloc_width, node->height);
385 }
386
387 // silence pixman errors
388 if (width <= 0 || height <= 0) {
389 pixman_region32_fini(&text_area);
390 return;
391 }
392
393 pixman_region32_t background, border;
394
395 int thickness = config->titlebar_border_thickness;
396 pixman_region32_init_rect(&background,
397 thickness, thickness,
398 width - thickness * 2, height - thickness * 2);
399 pixman_region32_init_rect(&border, 0, 0, width, height);
400 pixman_region32_subtract(&border, &border, &background);
401
402 pixman_region32_subtract(&background, &background, &text_area);
403 pixman_region32_fini(&text_area);
404
405 update_rect_list(con->title_bar.background, &background);
406 pixman_region32_fini(&background);
407
408 update_rect_list(con->title_bar.border, &border);
409 pixman_region32_fini(&border);
410
411 container_update(con);
412}
413
414void container_update_marks(struct sway_container *con) {
415 char *buffer = NULL;
416
417 if (config->show_marks && con->marks->length) {
418 size_t len = 0;
419 for (int i = 0; i < con->marks->length; ++i) {
420 char *mark = con->marks->items[i];
421 if (mark[0] != '_') {
422 len += strlen(mark) + 2;
423 }
424 }
425 buffer = calloc(len + 1, 1);
426 char *part = malloc(len + 1);
427
428 if (!sway_assert(buffer && part, "Unable to allocate memory")) {
429 free(buffer);
430 return;
431 }
432
433 for (int i = 0; i < con->marks->length; ++i) {
434 char *mark = con->marks->items[i];
435 if (mark[0] != '_') {
436 snprintf(part, len + 1, "[%s]", mark);
437 strcat(buffer, part);
438 }
439 }
440 free(part);
441 }
442
443 if (!buffer) {
444 if (con->title_bar.marks_text) {
445 wlr_scene_node_destroy(con->title_bar.marks_text->node);
446 con->title_bar.marks_text = NULL;
447 }
448 } else if (!con->title_bar.marks_text) {
449 struct border_colors *colors = container_get_current_colors(con);
450
451 con->title_bar.marks_text = sway_text_node_create(con->title_bar.tree,
452 buffer, colors->text, false);
453 } else {
454 sway_text_node_set_text(con->title_bar.marks_text, buffer);
455 }
456
457 container_arrange_title_bar(con);
458 free(buffer);
459}
460
461void container_update_title_bar(struct sway_container *con) {
462 if (!con->formatted_title) {
463 return;
464 }
465
466 struct border_colors *colors = container_get_current_colors(con);
467
468 if (con->title_bar.title_text) {
469 wlr_scene_node_destroy(con->title_bar.title_text->node);
470 con->title_bar.title_text = NULL;
471 }
472
473 con->title_bar.title_text = sway_text_node_create(con->title_bar.tree,
474 con->formatted_title, colors->text, config->pango_markup);
475
476 // we always have to remake these text buffers completely for text font
477 // changes etc...
478 if (con->title_bar.marks_text) {
479 wlr_scene_node_destroy(con->title_bar.marks_text->node);
480 con->title_bar.marks_text = NULL;
481 }
482
483 container_update_marks(con);
484 container_arrange_title_bar(con);
485}
486
50void container_destroy(struct sway_container *con) { 487void container_destroy(struct sway_container *con) {
51 if (!sway_assert(con->node.destroying, 488 if (!sway_assert(con->node.destroying,
52 "Tried to free container which wasn't marked as destroying")) { 489 "Tried to free container which wasn't marked as destroying")) {
@@ -58,29 +495,21 @@ void container_destroy(struct sway_container *con) {
58 } 495 }
59 free(con->title); 496 free(con->title);
60 free(con->formatted_title); 497 free(con->formatted_title);
61 wlr_texture_destroy(con->title_focused); 498 list_free(con->pending.children);
62 wlr_texture_destroy(con->title_focused_inactive);
63 wlr_texture_destroy(con->title_unfocused);
64 wlr_texture_destroy(con->title_urgent);
65 list_free(con->children);
66 list_free(con->current.children); 499 list_free(con->current.children);
67 list_free(con->outputs);
68 500
69 list_free_items_and_destroy(con->marks); 501 list_free_items_and_destroy(con->marks);
70 wlr_texture_destroy(con->marks_focused);
71 wlr_texture_destroy(con->marks_focused_inactive);
72 wlr_texture_destroy(con->marks_unfocused);
73 wlr_texture_destroy(con->marks_urgent);
74 502
75 if (con->view) { 503 if (con->view && con->view->container == con) {
76 if (con->view->container == con) { 504 con->view->container = NULL;
77 con->view->container = NULL; 505 wlr_scene_node_destroy(&con->output_handler->node);
78 }
79 if (con->view->destroying) { 506 if (con->view->destroying) {
80 view_destroy(con->view); 507 view_destroy(con->view);
81 } 508 }
82 } 509 }
83 510
511 scene_node_disown_children(con->content_tree);
512 wlr_scene_node_destroy(&con->scene_tree->node);
84 free(con); 513 free(con);
85} 514}
86 515
@@ -90,14 +519,14 @@ void container_begin_destroy(struct sway_container *con) {
90 } 519 }
91 // The workspace must have the fullscreen pointer cleared so that the 520 // The workspace must have the fullscreen pointer cleared so that the
92 // seat code can find an appropriate new focus. 521 // seat code can find an appropriate new focus.
93 if (con->fullscreen_mode == FULLSCREEN_WORKSPACE && con->workspace) { 522 if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE && con->pending.workspace) {
94 con->workspace->fullscreen = NULL; 523 con->pending.workspace->fullscreen = NULL;
95 } 524 }
96 if (con->scratchpad && con->fullscreen_mode == FULLSCREEN_GLOBAL) { 525 if (con->scratchpad && con->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
97 container_fullscreen_disable(con); 526 container_fullscreen_disable(con);
98 } 527 }
99 528
100 wl_signal_emit(&con->node.events.destroy, &con->node); 529 wl_signal_emit_mutable(&con->node.events.destroy, &con->node);
101 530
102 container_end_mouse_operation(con); 531 container_end_mouse_operation(con);
103 532
@@ -108,11 +537,11 @@ void container_begin_destroy(struct sway_container *con) {
108 root_scratchpad_remove_container(con); 537 root_scratchpad_remove_container(con);
109 } 538 }
110 539
111 if (con->fullscreen_mode == FULLSCREEN_GLOBAL) { 540 if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
112 container_fullscreen_disable(con); 541 container_fullscreen_disable(con);
113 } 542 }
114 543
115 if (con->parent || con->workspace) { 544 if (con->pending.parent || con->pending.workspace) {
116 container_detach(con); 545 container_detach(con);
117 } 546 }
118} 547}
@@ -121,12 +550,12 @@ void container_reap_empty(struct sway_container *con) {
121 if (con->view) { 550 if (con->view) {
122 return; 551 return;
123 } 552 }
124 struct sway_workspace *ws = con->workspace; 553 struct sway_workspace *ws = con->pending.workspace;
125 while (con) { 554 while (con) {
126 if (con->children->length) { 555 if (con->pending.children->length) {
127 return; 556 return;
128 } 557 }
129 struct sway_container *parent = con->parent; 558 struct sway_container *parent = con->pending.parent;
130 container_begin_destroy(con); 559 container_begin_destroy(con);
131 con = parent; 560 con = parent;
132 } 561 }
@@ -139,9 +568,9 @@ struct sway_container *container_flatten(struct sway_container *container) {
139 if (container->view) { 568 if (container->view) {
140 return NULL; 569 return NULL;
141 } 570 }
142 while (container && container->children->length == 1) { 571 while (container && container->pending.children->length == 1) {
143 struct sway_container *child = container->children->items[0]; 572 struct sway_container *child = container->pending.children->items[0];
144 struct sway_container *parent = container->parent; 573 struct sway_container *parent = container->pending.parent;
145 container_replace(container, child); 574 container_replace(container, child);
146 container_begin_destroy(container); 575 container_begin_destroy(container);
147 container = parent; 576 container = parent;
@@ -151,11 +580,11 @@ struct sway_container *container_flatten(struct sway_container *container) {
151 580
152struct sway_container *container_find_child(struct sway_container *container, 581struct sway_container *container_find_child(struct sway_container *container,
153 bool (*test)(struct sway_container *con, void *data), void *data) { 582 bool (*test)(struct sway_container *con, void *data), void *data) {
154 if (!container->children) { 583 if (!container->pending.children) {
155 return NULL; 584 return NULL;
156 } 585 }
157 for (int i = 0; i < container->children->length; ++i) { 586 for (int i = 0; i < container->pending.children->length; ++i) {
158 struct sway_container *child = container->children->items[i]; 587 struct sway_container *child = container->pending.children->items[i];
159 if (test(child, data)) { 588 if (test(child, data)) {
160 return child; 589 return child;
161 } 590 }
@@ -167,260 +596,44 @@ struct sway_container *container_find_child(struct sway_container *container,
167 return NULL; 596 return NULL;
168} 597}
169 598
170static struct sway_container *surface_at_view(struct sway_container *con, double lx, double ly, 599void container_for_each_child(struct sway_container *container,
171 struct wlr_surface **surface, double *sx, double *sy) { 600 void (*f)(struct sway_container *container, void *data),
172 if (!sway_assert(con->view, "Expected a view")) { 601 void *data) {
173 return NULL; 602 if (container->pending.children) {
174 } 603 for (int i = 0; i < container->pending.children->length; ++i) {
175 struct sway_view *view = con->view; 604 struct sway_container *child = container->pending.children->items[i];
176 double view_sx = lx - con->surface_x + view->geometry.x; 605 f(child, data);
177 double view_sy = ly - con->surface_y + view->geometry.y; 606 container_for_each_child(child, f, data);
178
179 double _sx, _sy;
180 struct wlr_surface *_surface = NULL;
181 switch (view->type) {
182#if HAVE_XWAYLAND
183 case SWAY_VIEW_XWAYLAND:
184 _surface = wlr_surface_surface_at(view->surface,
185 view_sx, view_sy, &_sx, &_sy);
186 break;
187#endif
188 case SWAY_VIEW_XDG_SHELL:
189 _surface = wlr_xdg_surface_surface_at(
190 view->wlr_xdg_surface,
191 view_sx, view_sy, &_sx, &_sy);
192 break;
193 }
194 if (_surface) {
195 *sx = _sx;
196 *sy = _sy;
197 *surface = _surface;
198 return con;
199 }
200 return NULL;
201}
202
203/**
204 * container_at for a container with layout L_TABBED.
205 */
206static struct sway_container *container_at_tabbed(struct sway_node *parent,
207 double lx, double ly,
208 struct wlr_surface **surface, double *sx, double *sy) {
209 struct wlr_box box;
210 node_get_box(parent, &box);
211 if (lx < box.x || lx > box.x + box.width ||
212 ly < box.y || ly > box.y + box.height) {
213 return NULL;
214 }
215 struct sway_seat *seat = input_manager_current_seat();
216 list_t *children = node_get_children(parent);
217 if (!children->length) {
218 return NULL;
219 }
220
221 // Tab titles
222 int title_height = container_titlebar_height();
223 if (ly < box.y + title_height) {
224 int tab_width = box.width / children->length;
225 int child_index = (lx - box.x) / tab_width;
226 if (child_index >= children->length) {
227 child_index = children->length - 1;
228 }
229 struct sway_container *child = children->items[child_index];
230 return child;
231 }
232
233 // Surfaces
234 struct sway_node *current = seat_get_active_tiling_child(seat, parent);
235 return current ? tiling_container_at(current, lx, ly, surface, sx, sy) : NULL;
236}
237
238/**
239 * container_at for a container with layout L_STACKED.
240 */
241static struct sway_container *container_at_stacked(struct sway_node *parent,
242 double lx, double ly,
243 struct wlr_surface **surface, double *sx, double *sy) {
244 struct wlr_box box;
245 node_get_box(parent, &box);
246 if (lx < box.x || lx > box.x + box.width ||
247 ly < box.y || ly > box.y + box.height) {
248 return NULL;
249 }
250 struct sway_seat *seat = input_manager_current_seat();
251 list_t *children = node_get_children(parent);
252
253 // Title bars
254 int title_height = container_titlebar_height();
255 if (title_height > 0) {
256 int child_index = (ly - box.y) / title_height;
257 if (child_index < children->length) {
258 struct sway_container *child = children->items[child_index];
259 return child;
260 } 607 }
261 } 608 }
262
263 // Surfaces
264 struct sway_node *current = seat_get_active_tiling_child(seat, parent);
265 return current ? tiling_container_at(current, lx, ly, surface, sx, sy) : NULL;
266} 609}
267 610
268/** 611struct sway_container *container_obstructing_fullscreen_container(struct sway_container *container)
269 * container_at for a container with layout L_HORIZ or L_VERT. 612{
270 */ 613 struct sway_workspace *workspace = container->pending.workspace;
271static struct sway_container *container_at_linear(struct sway_node *parent,
272 double lx, double ly,
273 struct wlr_surface **surface, double *sx, double *sy) {
274 list_t *children = node_get_children(parent);
275 for (int i = 0; i < children->length; ++i) {
276 struct sway_container *child = children->items[i];
277 struct sway_container *container =
278 tiling_container_at(&child->node, lx, ly, surface, sx, sy);
279 if (container) {
280 return container;
281 }
282 }
283 return NULL;
284}
285 614
286static struct sway_container *floating_container_at(double lx, double ly, 615 if (workspace && workspace->fullscreen && !container_is_fullscreen_or_child(container)) {
287 struct wlr_surface **surface, double *sx, double *sy) { 616 if (container_is_transient_for(container, workspace->fullscreen)) {
288 // For outputs with floating containers that overhang the output bounds, 617 return NULL;
289 // those at the end of the output list appear on top of floating
290 // containers from other outputs, so iterate the list in reverse.
291 for (int i = root->outputs->length - 1; i >= 0; --i) {
292 struct sway_output *output = root->outputs->items[i];
293 for (int j = 0; j < output->workspaces->length; ++j) {
294 struct sway_workspace *ws = output->workspaces->items[j];
295 if (!workspace_is_visible(ws)) {
296 continue;
297 }
298 // Items at the end of the list are on top, so iterate the list in
299 // reverse.
300 for (int k = ws->floating->length - 1; k >= 0; --k) {
301 struct sway_container *floater = ws->floating->items[k];
302 struct sway_container *container =
303 tiling_container_at(&floater->node, lx, ly, surface, sx, sy);
304 if (container) {
305 return container;
306 }
307 }
308 } 618 }
309 } 619 return workspace->fullscreen;
310 return NULL;
311}
312
313struct sway_container *view_container_at(struct sway_node *parent,
314 double lx, double ly,
315 struct wlr_surface **surface, double *sx, double *sy) {
316 if (!sway_assert(node_is_view(parent), "Expected a view")) {
317 return NULL;
318 }
319
320 struct sway_container *container = parent->sway_container;
321 struct wlr_box box = {
322 .x = container->x,
323 .y = container->y,
324 .width = container->width,
325 .height = container->height,
326 };
327
328 if (wlr_box_contains_point(&box, lx, ly)) {
329 surface_at_view(parent->sway_container, lx, ly, surface, sx, sy);
330 return container;
331 } 620 }
332 621
333 return NULL; 622 struct sway_container *fullscreen_global = root->fullscreen_global;
334} 623 if (fullscreen_global && container != fullscreen_global && !container_has_ancestor(container, fullscreen_global)) {
335 624 if (container_is_transient_for(container, fullscreen_global)) {
336struct sway_container *tiling_container_at(struct sway_node *parent, 625 return NULL;
337 double lx, double ly,
338 struct wlr_surface **surface, double *sx, double *sy) {
339 if (node_is_view(parent)) {
340 return view_container_at(parent, lx, ly, surface, sx, sy);
341 }
342 if (!node_get_children(parent)) {
343 return NULL;
344 }
345 switch (node_get_layout(parent)) {
346 case L_HORIZ:
347 case L_VERT:
348 return container_at_linear(parent, lx, ly, surface, sx, sy);
349 case L_TABBED:
350 return container_at_tabbed(parent, lx, ly, surface, sx, sy);
351 case L_STACKED:
352 return container_at_stacked(parent, lx, ly, surface, sx, sy);
353 case L_NONE:
354 return NULL;
355 }
356 return NULL;
357}
358
359static bool surface_is_popup(struct wlr_surface *surface) {
360 if (wlr_surface_is_xdg_surface(surface)) {
361 struct wlr_xdg_surface *xdg_surface =
362 wlr_xdg_surface_from_wlr_surface(surface);
363 while (xdg_surface && xdg_surface->role != WLR_XDG_SURFACE_ROLE_NONE) {
364 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
365 return true;
366 }
367 xdg_surface = xdg_surface->toplevel->parent;
368 } 626 }
369 return false; 627 return fullscreen_global;
370 } 628 }
371 629
372 return false;
373}
374
375struct sway_container *container_at(struct sway_workspace *workspace,
376 double lx, double ly,
377 struct wlr_surface **surface, double *sx, double *sy) {
378 struct sway_container *c;
379
380 struct sway_seat *seat = input_manager_current_seat();
381 struct sway_container *focus = seat_get_focused_container(seat);
382 bool is_floating = focus && container_is_floating_or_child(focus);
383 // Focused view's popups
384 if (focus && focus->view) {
385 c = surface_at_view(focus, lx, ly, surface, sx, sy);
386 if (c && surface_is_popup(*surface)) {
387 return c;
388 }
389 *surface = NULL;
390 }
391 // Floating
392 if ((c = floating_container_at(lx, ly, surface ,sx ,sy))) {
393 return c;
394 }
395 // Tiling (focused)
396 if (focus && focus->view && !is_floating) {
397 if ((c = surface_at_view(focus, lx, ly, surface, sx, sy))) {
398 return c;
399 }
400 }
401 // Tiling (non-focused)
402 if ((c = tiling_container_at(&workspace->node, lx, ly, surface, sx, sy))) {
403 return c;
404 }
405 return NULL; 630 return NULL;
406} 631}
407 632
408void container_for_each_child(struct sway_container *container,
409 void (*f)(struct sway_container *container, void *data),
410 void *data) {
411 if (container->children) {
412 for (int i = 0; i < container->children->length; ++i) {
413 struct sway_container *child = container->children->items[i];
414 f(child, data);
415 container_for_each_child(child, f, data);
416 }
417 }
418}
419
420bool container_has_ancestor(struct sway_container *descendant, 633bool container_has_ancestor(struct sway_container *descendant,
421 struct sway_container *ancestor) { 634 struct sway_container *ancestor) {
422 while (descendant) { 635 while (descendant) {
423 descendant = descendant->parent; 636 descendant = descendant->pending.parent;
424 if (descendant == ancestor) { 637 if (descendant == ancestor) {
425 return true; 638 return true;
426 } 639 }
@@ -428,119 +641,6 @@ bool container_has_ancestor(struct sway_container *descendant,
428 return false; 641 return false;
429} 642}
430 643
431void container_damage_whole(struct sway_container *container) {
432 for (int i = 0; i < root->outputs->length; ++i) {
433 struct sway_output *output = root->outputs->items[i];
434 output_damage_whole_container(output, container);
435 }
436}
437
438/**
439 * Return the output which will be used for scale purposes.
440 * This is the most recently entered output.
441 */
442struct sway_output *container_get_effective_output(struct sway_container *con) {
443 if (con->outputs->length == 0) {
444 return NULL;
445 }
446 return con->outputs->items[con->outputs->length - 1];
447}
448
449static void update_title_texture(struct sway_container *con,
450 struct wlr_texture **texture, struct border_colors *class) {
451 struct sway_output *output = container_get_effective_output(con);
452 if (!output) {
453 return;
454 }
455 if (*texture) {
456 wlr_texture_destroy(*texture);
457 *texture = NULL;
458 }
459 if (!con->formatted_title) {
460 return;
461 }
462
463 double scale = output->wlr_output->scale;
464 int width = 0;
465 int height = con->title_height * scale;
466
467 // We must use a non-nil cairo_t for cairo_set_font_options to work.
468 // Therefore, we cannot use cairo_create(NULL).
469 cairo_surface_t *dummy_surface = cairo_image_surface_create(
470 CAIRO_FORMAT_ARGB32, 0, 0);
471 cairo_t *c = cairo_create(dummy_surface);
472 cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
473 cairo_font_options_t *fo = cairo_font_options_create();
474 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
475 if (output->wlr_output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) {
476 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
477 } else {
478 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
479 cairo_font_options_set_subpixel_order(fo,
480 to_cairo_subpixel_order(output->wlr_output->subpixel));
481 }
482 cairo_set_font_options(c, fo);
483 get_text_size(c, config->font, &width, NULL, NULL, scale,
484 config->pango_markup, "%s", con->formatted_title);
485 cairo_surface_destroy(dummy_surface);
486 cairo_destroy(c);
487
488 cairo_surface_t *surface = cairo_image_surface_create(
489 CAIRO_FORMAT_ARGB32, width, height);
490 cairo_t *cairo = cairo_create(surface);
491 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
492 cairo_set_font_options(cairo, fo);
493 cairo_font_options_destroy(fo);
494 cairo_set_source_rgba(cairo, class->background[0], class->background[1],
495 class->background[2], class->background[3]);
496 cairo_paint(cairo);
497 PangoContext *pango = pango_cairo_create_context(cairo);
498 cairo_set_source_rgba(cairo, class->text[0], class->text[1],
499 class->text[2], class->text[3]);
500 cairo_move_to(cairo, 0, 0);
501
502 pango_printf(cairo, config->font, scale, config->pango_markup,
503 "%s", con->formatted_title);
504
505 cairo_surface_flush(surface);
506 unsigned char *data = cairo_image_surface_get_data(surface);
507 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
508 struct wlr_renderer *renderer = wlr_backend_get_renderer(
509 output->wlr_output->backend);
510 *texture = wlr_texture_from_pixels(
511 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
512 cairo_surface_destroy(surface);
513 g_object_unref(pango);
514 cairo_destroy(cairo);
515}
516
517void container_update_title_textures(struct sway_container *container) {
518 update_title_texture(container, &container->title_focused,
519 &config->border_colors.focused);
520 update_title_texture(container, &container->title_focused_inactive,
521 &config->border_colors.focused_inactive);
522 update_title_texture(container, &container->title_unfocused,
523 &config->border_colors.unfocused);
524 update_title_texture(container, &container->title_urgent,
525 &config->border_colors.urgent);
526 container_damage_whole(container);
527}
528
529void container_calculate_title_height(struct sway_container *container) {
530 if (!container->formatted_title) {
531 container->title_height = 0;
532 return;
533 }
534 cairo_t *cairo = cairo_create(NULL);
535 int height;
536 int baseline;
537 get_text_size(cairo, config->font, NULL, &height, &baseline, 1,
538 config->pango_markup, "%s", container->formatted_title);
539 cairo_destroy(cairo);
540 container->title_height = height;
541 container->title_baseline = baseline;
542}
543
544/** 644/**
545 * Calculate and return the length of the tree representation. 645 * Calculate and return the length of the tree representation.
546 * An example tree representation is: V[Terminal, Firefox] 646 * An example tree representation is: V[Terminal, Firefox]
@@ -596,23 +696,28 @@ size_t container_build_representation(enum sway_container_layout layout,
596 696
597void container_update_representation(struct sway_container *con) { 697void container_update_representation(struct sway_container *con) {
598 if (!con->view) { 698 if (!con->view) {
599 size_t len = container_build_representation(con->layout, 699 size_t len = container_build_representation(con->pending.layout,
600 con->children, NULL); 700 con->pending.children, NULL);
601 free(con->formatted_title); 701 free(con->formatted_title);
602 con->formatted_title = calloc(len + 1, sizeof(char)); 702 con->formatted_title = calloc(len + 1, sizeof(char));
603 if (!sway_assert(con->formatted_title, 703 if (!sway_assert(con->formatted_title,
604 "Unable to allocate title string")) { 704 "Unable to allocate title string")) {
605 return; 705 return;
606 } 706 }
607 container_build_representation(con->layout, con->children, 707 container_build_representation(con->pending.layout, con->pending.children,
608 con->formatted_title); 708 con->formatted_title);
609 container_calculate_title_height(con); 709
610 container_update_title_textures(con); 710 if (con->title_bar.title_text) {
711 sway_text_node_set_text(con->title_bar.title_text, con->formatted_title);
712 container_arrange_title_bar(con);
713 } else {
714 container_update_title_bar(con);
715 }
611 } 716 }
612 if (con->parent) { 717 if (con->pending.parent) {
613 container_update_representation(con->parent); 718 container_update_representation(con->pending.parent);
614 } else if (con->workspace) { 719 } else if (con->pending.workspace) {
615 workspace_update_representation(con->workspace); 720 workspace_update_representation(con->pending.workspace);
616 } 721 }
617} 722}
618 723
@@ -638,12 +743,13 @@ void floating_calculate_constraints(int *min_width, int *max_width,
638 *min_height = config->floating_minimum_height; 743 *min_height = config->floating_minimum_height;
639 } 744 }
640 745
641 struct wlr_box *box = wlr_output_layout_get_box(root->output_layout, NULL); 746 struct wlr_box box;
747 wlr_output_layout_get_box(root->output_layout, NULL, &box);
642 748
643 if (config->floating_maximum_width == -1) { // no maximum 749 if (config->floating_maximum_width == -1) { // no maximum
644 *max_width = INT_MAX; 750 *max_width = INT_MAX;
645 } else if (config->floating_maximum_width == 0) { // automatic 751 } else if (config->floating_maximum_width == 0) { // automatic
646 *max_width = box->width; 752 *max_width = box.width;
647 } else { 753 } else {
648 *max_width = config->floating_maximum_width; 754 *max_width = config->floating_maximum_width;
649 } 755 }
@@ -651,99 +757,112 @@ void floating_calculate_constraints(int *min_width, int *max_width,
651 if (config->floating_maximum_height == -1) { // no maximum 757 if (config->floating_maximum_height == -1) { // no maximum
652 *max_height = INT_MAX; 758 *max_height = INT_MAX;
653 } else if (config->floating_maximum_height == 0) { // automatic 759 } else if (config->floating_maximum_height == 0) { // automatic
654 *max_height = box->height; 760 *max_height = box.height;
655 } else { 761 } else {
656 *max_height = config->floating_maximum_height; 762 *max_height = config->floating_maximum_height;
657 } 763 }
658 764
659} 765}
660 766
767void floating_fix_coordinates(struct sway_container *con, struct wlr_box *old, struct wlr_box *new) {
768 if (!old->width || !old->height) {
769 // Fall back to centering on the workspace.
770 container_floating_move_to_center(con);
771 } else {
772 int rel_x = con->pending.x - old->x + (con->pending.width / 2);
773 int rel_y = con->pending.y - old->y + (con->pending.height / 2);
774
775 con->pending.x = new->x + (double)(rel_x * new->width) / old->width - (con->pending.width / 2);
776 con->pending.y = new->y + (double)(rel_y * new->height) / old->height - (con->pending.height / 2);
777
778 sway_log(SWAY_DEBUG, "Transformed container %p to coords (%f, %f)", con, con->pending.x, con->pending.y);
779 }
780}
781
661static void floating_natural_resize(struct sway_container *con) { 782static void floating_natural_resize(struct sway_container *con) {
662 int min_width, max_width, min_height, max_height; 783 int min_width, max_width, min_height, max_height;
663 floating_calculate_constraints(&min_width, &max_width, 784 floating_calculate_constraints(&min_width, &max_width,
664 &min_height, &max_height); 785 &min_height, &max_height);
665 if (!con->view) { 786 if (!con->view) {
666 con->width = fmax(min_width, fmin(con->width, max_width)); 787 con->pending.width = fmax(min_width, fmin(con->pending.width, max_width));
667 con->height = fmax(min_height, fmin(con->height, max_height)); 788 con->pending.height = fmax(min_height, fmin(con->pending.height, max_height));
668 } else { 789 } else {
669 struct sway_view *view = con->view; 790 struct sway_view *view = con->view;
670 con->content_width = 791 con->pending.content_width =
671 fmax(min_width, fmin(view->natural_width, max_width)); 792 fmax(min_width, fmin(view->natural_width, max_width));
672 con->content_height = 793 con->pending.content_height =
673 fmax(min_height, fmin(view->natural_height, max_height)); 794 fmax(min_height, fmin(view->natural_height, max_height));
674 container_set_geometry_from_content(con); 795 container_set_geometry_from_content(con);
675 } 796 }
676} 797}
677 798
678void container_floating_resize_and_center(struct sway_container *con) { 799void container_floating_resize_and_center(struct sway_container *con) {
679 struct sway_workspace *ws = con->workspace; 800 struct sway_workspace *ws = con->pending.workspace;
680 if (!ws) { 801 if (!ws) {
681 // On scratchpad, just resize 802 // On scratchpad, just resize
682 floating_natural_resize(con); 803 floating_natural_resize(con);
683 return; 804 return;
684 } 805 }
685 806
686 struct wlr_box *ob = wlr_output_layout_get_box(root->output_layout, 807 struct wlr_box ob;
687 ws->output->wlr_output); 808 wlr_output_layout_get_box(root->output_layout, ws->output->wlr_output, &ob);
688 if (!ob) { 809 if (wlr_box_empty(&ob)) {
689 // On NOOP output. Will be called again when moved to an output 810 // On NOOP output. Will be called again when moved to an output
690 con->x = 0; 811 con->pending.x = 0;
691 con->y = 0; 812 con->pending.y = 0;
692 con->width = 0; 813 con->pending.width = 0;
693 con->height = 0; 814 con->pending.height = 0;
694 return; 815 return;
695 } 816 }
696 817
697 floating_natural_resize(con); 818 floating_natural_resize(con);
698 if (!con->view) { 819 if (!con->view) {
699 if (con->width > ws->width || con->height > ws->height) { 820 if (con->pending.width > ws->width || con->pending.height > ws->height) {
700 con->x = ob->x + (ob->width - con->width) / 2; 821 con->pending.x = ob.x + (ob.width - con->pending.width) / 2;
701 con->y = ob->y + (ob->height - con->height) / 2; 822 con->pending.y = ob.y + (ob.height - con->pending.height) / 2;
702 } else { 823 } else {
703 con->x = ws->x + (ws->width - con->width) / 2; 824 con->pending.x = ws->x + (ws->width - con->pending.width) / 2;
704 con->y = ws->y + (ws->height - con->height) / 2; 825 con->pending.y = ws->y + (ws->height - con->pending.height) / 2;
705 } 826 }
706 } else { 827 } else {
707 if (con->content_width > ws->width 828 if (con->pending.content_width > ws->width
708 || con->content_height > ws->height) { 829 || con->pending.content_height > ws->height) {
709 con->content_x = ob->x + (ob->width - con->content_width) / 2; 830 con->pending.content_x = ob.x + (ob.width - con->pending.content_width) / 2;
710 con->content_y = ob->y + (ob->height - con->content_height) / 2; 831 con->pending.content_y = ob.y + (ob.height - con->pending.content_height) / 2;
711 } else { 832 } else {
712 con->content_x = ws->x + (ws->width - con->content_width) / 2; 833 con->pending.content_x = ws->x + (ws->width - con->pending.content_width) / 2;
713 con->content_y = ws->y + (ws->height - con->content_height) / 2; 834 con->pending.content_y = ws->y + (ws->height - con->pending.content_height) / 2;
714 } 835 }
715 836
716 // If the view's border is B_NONE then these properties are ignored. 837 // If the view's border is B_NONE then these properties are ignored.
717 con->border_top = con->border_bottom = true; 838 con->pending.border_top = con->pending.border_bottom = true;
718 con->border_left = con->border_right = true; 839 con->pending.border_left = con->pending.border_right = true;
719 840
720 container_set_geometry_from_content(con); 841 container_set_geometry_from_content(con);
721 } 842 }
722} 843}
723 844
724void container_floating_set_default_size(struct sway_container *con) { 845void container_floating_set_default_size(struct sway_container *con) {
725 if (!sway_assert(con->workspace, "Expected a container on a workspace")) { 846 if (!sway_assert(con->pending.workspace, "Expected a container on a workspace")) {
726 return; 847 return;
727 } 848 }
728 849
729 int min_width, max_width, min_height, max_height; 850 int min_width, max_width, min_height, max_height;
730 floating_calculate_constraints(&min_width, &max_width, 851 floating_calculate_constraints(&min_width, &max_width,
731 &min_height, &max_height); 852 &min_height, &max_height);
732 struct wlr_box *box = calloc(1, sizeof(struct wlr_box)); 853 struct wlr_box box;
733 workspace_get_box(con->workspace, box); 854 workspace_get_box(con->pending.workspace, &box);
734 855
735 double width = fmax(min_width, fmin(box->width * 0.5, max_width)); 856 double width = fmax(min_width, fmin(box.width * 0.5, max_width));
736 double height = fmax(min_height, fmin(box->height * 0.75, max_height)); 857 double height = fmax(min_height, fmin(box.height * 0.75, max_height));
737 if (!con->view) { 858 if (!con->view) {
738 con->width = width; 859 con->pending.width = width;
739 con->height = height; 860 con->pending.height = height;
740 } else { 861 } else {
741 con->content_width = width; 862 con->pending.content_width = width;
742 con->content_height = height; 863 con->pending.content_height = height;
743 container_set_geometry_from_content(con); 864 container_set_geometry_from_content(con);
744 } 865 }
745
746 free(box);
747} 866}
748 867
749 868
@@ -761,8 +880,8 @@ void container_set_resizing(struct sway_container *con, bool resizing) {
761 con->view->impl->set_resizing(con->view, resizing); 880 con->view->impl->set_resizing(con->view, resizing);
762 } 881 }
763 } else { 882 } else {
764 for (int i = 0; i < con->children->length; ++i ) { 883 for (int i = 0; i < con->pending.children->length; ++i ) {
765 struct sway_container *child = con->children->items[i]; 884 struct sway_container *child = con->pending.children->items[i];
766 container_set_resizing(child, resizing); 885 container_set_resizing(child, resizing);
767 } 886 }
768 } 887 }
@@ -774,21 +893,33 @@ void container_set_floating(struct sway_container *container, bool enable) {
774 } 893 }
775 894
776 struct sway_seat *seat = input_manager_current_seat(); 895 struct sway_seat *seat = input_manager_current_seat();
777 struct sway_workspace *workspace = container->workspace; 896 struct sway_workspace *workspace = container->pending.workspace;
897 struct sway_container *focus = seat_get_focused_container(seat);
898 bool set_focus = focus == container;
778 899
779 if (enable) { 900 if (enable) {
780 struct sway_container *old_parent = container->parent; 901 struct sway_container *old_parent = container->pending.parent;
781 container_detach(container); 902 container_detach(container);
782 workspace_add_floating(workspace, container); 903 workspace_add_floating(workspace, container);
783 if (container->view) { 904 if (container->view) {
784 view_set_tiled(container->view, false); 905 view_set_tiled(container->view, false);
785 if (container->view->using_csd) { 906 if (container->view->using_csd) {
786 container->border = B_CSD; 907 container->saved_border = container->pending.border;
908 container->pending.border = B_CSD;
909 if (container->view->xdg_decoration) {
910 struct sway_xdg_decoration *deco = container->view->xdg_decoration;
911 wlr_xdg_toplevel_decoration_v1_set_mode(deco->wlr_xdg_decoration,
912 WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
913 }
787 } 914 }
788 } 915 }
789 container_floating_set_default_size(container); 916 container_floating_set_default_size(container);
790 container_floating_resize_and_center(container); 917 container_floating_resize_and_center(container);
791 if (old_parent) { 918 if (old_parent) {
919 if (set_focus) {
920 seat_set_raw_focus(seat, &old_parent->node);
921 seat_set_raw_focus(seat, &container->node);
922 }
792 container_reap_empty(old_parent); 923 container_reap_empty(old_parent);
793 } 924 }
794 } else { 925 } else {
@@ -800,19 +931,28 @@ void container_set_floating(struct sway_container *container, bool enable) {
800 struct sway_container *reference = 931 struct sway_container *reference =
801 seat_get_focus_inactive_tiling(seat, workspace); 932 seat_get_focus_inactive_tiling(seat, workspace);
802 if (reference) { 933 if (reference) {
803 container_add_sibling(reference, container, 1); 934 if (reference->view) {
804 container->width = reference->width; 935 container_add_sibling(reference, container, 1);
805 container->height = reference->height; 936 } else {
937 container_add_child(reference, container);
938 }
939 container->pending.width = reference->pending.width;
940 container->pending.height = reference->pending.height;
806 } else { 941 } else {
807 struct sway_container *other = 942 struct sway_container *other =
808 workspace_add_tiling(workspace, container); 943 workspace_add_tiling(workspace, container);
809 other->width = workspace->width; 944 other->pending.width = workspace->width;
810 other->height = workspace->height; 945 other->pending.height = workspace->height;
811 } 946 }
812 if (container->view) { 947 if (container->view) {
813 view_set_tiled(container->view, true); 948 view_set_tiled(container->view, true);
814 if (container->view->using_csd) { 949 if (container->view->using_csd) {
815 container->border = container->saved_border; 950 container->pending.border = container->saved_border;
951 if (container->view->xdg_decoration) {
952 struct sway_xdg_decoration *deco = container->view->xdg_decoration;
953 wlr_xdg_toplevel_decoration_v1_set_mode(deco->wlr_xdg_decoration,
954 WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
955 }
816 } 956 }
817 } 957 }
818 container->width_fraction = 0; 958 container->width_fraction = 0;
@@ -834,22 +974,22 @@ void container_set_geometry_from_content(struct sway_container *con) {
834 size_t border_width = 0; 974 size_t border_width = 0;
835 size_t top = 0; 975 size_t top = 0;
836 976
837 if (con->border != B_CSD) { 977 if (con->pending.border != B_CSD && !con->pending.fullscreen_mode) {
838 border_width = con->border_thickness * (con->border != B_NONE); 978 border_width = con->pending.border_thickness * (con->pending.border != B_NONE);
839 top = con->border == B_NORMAL ? 979 top = con->pending.border == B_NORMAL ?
840 container_titlebar_height() : border_width; 980 container_titlebar_height() : border_width;
841 } 981 }
842 982
843 con->x = con->content_x - border_width; 983 con->pending.x = con->pending.content_x - border_width;
844 con->y = con->content_y - top; 984 con->pending.y = con->pending.content_y - top;
845 con->width = con->content_width + border_width * 2; 985 con->pending.width = con->pending.content_width + border_width * 2;
846 con->height = top + con->content_height + border_width; 986 con->pending.height = top + con->pending.content_height + border_width;
847 node_set_dirty(&con->node); 987 node_set_dirty(&con->node);
848} 988}
849 989
850bool container_is_floating(struct sway_container *container) { 990bool container_is_floating(struct sway_container *container) {
851 if (!container->parent && container->workspace && 991 if (!container->pending.parent && container->pending.workspace &&
852 list_find(container->workspace->floating, container) != -1) { 992 list_find(container->pending.workspace->floating, container) != -1) {
853 return true; 993 return true;
854 } 994 }
855 if (container->scratchpad) { 995 if (container->scratchpad) {
@@ -859,10 +999,10 @@ bool container_is_floating(struct sway_container *container) {
859} 999}
860 1000
861void container_get_box(struct sway_container *container, struct wlr_box *box) { 1001void container_get_box(struct sway_container *container, struct wlr_box *box) {
862 box->x = container->x; 1002 box->x = container->pending.x;
863 box->y = container->y; 1003 box->y = container->pending.y;
864 box->width = container->width; 1004 box->width = container->pending.width;
865 box->height = container->height; 1005 box->height = container->pending.height;
866} 1006}
867 1007
868/** 1008/**
@@ -870,14 +1010,14 @@ void container_get_box(struct sway_container *container, struct wlr_box *box) {
870 */ 1010 */
871void container_floating_translate(struct sway_container *con, 1011void container_floating_translate(struct sway_container *con,
872 double x_amount, double y_amount) { 1012 double x_amount, double y_amount) {
873 con->x += x_amount; 1013 con->pending.x += x_amount;
874 con->y += y_amount; 1014 con->pending.y += y_amount;
875 con->content_x += x_amount; 1015 con->pending.content_x += x_amount;
876 con->content_y += y_amount; 1016 con->pending.content_y += y_amount;
877 1017
878 if (con->children) { 1018 if (con->pending.children) {
879 for (int i = 0; i < con->children->length; ++i) { 1019 for (int i = 0; i < con->pending.children->length; ++i) {
880 struct sway_container *child = con->children->items[i]; 1020 struct sway_container *child = con->pending.children->items[i];
881 container_floating_translate(child, x_amount, y_amount); 1021 container_floating_translate(child, x_amount, y_amount);
882 } 1022 }
883 } 1023 }
@@ -893,8 +1033,8 @@ void container_floating_translate(struct sway_container *con,
893 * center. 1033 * center.
894 */ 1034 */
895struct sway_output *container_floating_find_output(struct sway_container *con) { 1035struct sway_output *container_floating_find_output(struct sway_container *con) {
896 double center_x = con->x + con->width / 2; 1036 double center_x = con->pending.x + con->pending.width / 2;
897 double center_y = con->y + con->height / 2; 1037 double center_y = con->pending.y + con->pending.height / 2;
898 struct sway_output *closest_output = NULL; 1038 struct sway_output *closest_output = NULL;
899 double closest_distance = DBL_MAX; 1039 double closest_distance = DBL_MAX;
900 for (int i = 0; i < root->outputs->length; ++i) { 1040 for (int i = 0; i < root->outputs->length; ++i) {
@@ -925,11 +1065,11 @@ void container_floating_move_to(struct sway_container *con,
925 "Expected a floating container")) { 1065 "Expected a floating container")) {
926 return; 1066 return;
927 } 1067 }
928 container_floating_translate(con, lx - con->x, ly - con->y); 1068 container_floating_translate(con, lx - con->pending.x, ly - con->pending.y);
929 if (container_is_scratchpad_hidden(con)) { 1069 if (container_is_scratchpad_hidden(con)) {
930 return; 1070 return;
931 } 1071 }
932 struct sway_workspace *old_workspace = con->workspace; 1072 struct sway_workspace *old_workspace = con->pending.workspace;
933 struct sway_output *new_output = container_floating_find_output(con); 1073 struct sway_output *new_output = container_floating_find_output(con);
934 if (!sway_assert(new_output, "Unable to find any output")) { 1074 if (!sway_assert(new_output, "Unable to find any output")) {
935 return; 1075 return;
@@ -941,6 +1081,13 @@ void container_floating_move_to(struct sway_container *con,
941 workspace_add_floating(new_workspace, con); 1081 workspace_add_floating(new_workspace, con);
942 arrange_workspace(old_workspace); 1082 arrange_workspace(old_workspace);
943 arrange_workspace(new_workspace); 1083 arrange_workspace(new_workspace);
1084 // If the moved container was a visible scratchpad container, then
1085 // update its transform.
1086 if (con->scratchpad) {
1087 struct wlr_box output_box;
1088 output_get_box(new_output, &output_box);
1089 con->transform = output_box;
1090 }
944 workspace_detect_urgent(old_workspace); 1091 workspace_detect_urgent(old_workspace);
945 workspace_detect_urgent(new_workspace); 1092 workspace_detect_urgent(new_workspace);
946 } 1093 }
@@ -951,10 +1098,10 @@ void container_floating_move_to_center(struct sway_container *con) {
951 "Expected a floating container")) { 1098 "Expected a floating container")) {
952 return; 1099 return;
953 } 1100 }
954 struct sway_workspace *ws = con->workspace; 1101 struct sway_workspace *ws = con->pending.workspace;
955 double new_lx = ws->x + (ws->width - con->width) / 2; 1102 double new_lx = ws->x + (ws->width - con->pending.width) / 2;
956 double new_ly = ws->y + (ws->height - con->height) / 2; 1103 double new_ly = ws->y + (ws->height - con->pending.height) / 2;
957 container_floating_translate(con, new_lx - con->x, new_ly - con->y); 1104 container_floating_translate(con, new_lx - con->pending.x, new_ly - con->pending.y);
958} 1105}
959 1106
960static bool find_urgent_iterator(struct sway_container *con, void *data) { 1107static bool find_urgent_iterator(struct sway_container *con, void *data) {
@@ -972,42 +1119,39 @@ void container_end_mouse_operation(struct sway_container *container) {
972 } 1119 }
973} 1120}
974 1121
975static void set_fullscreen_iterator(struct sway_container *con, void *data) { 1122static void set_fullscreen(struct sway_container *con, bool enable) {
976 if (!con->view) { 1123 if (!con->view) {
977 return; 1124 return;
978 } 1125 }
979 if (con->view->impl->set_fullscreen) { 1126 if (con->view->impl->set_fullscreen) {
980 bool *enable = data; 1127 con->view->impl->set_fullscreen(con->view, enable);
981 con->view->impl->set_fullscreen(con->view, *enable);
982 if (con->view->foreign_toplevel) { 1128 if (con->view->foreign_toplevel) {
983 wlr_foreign_toplevel_handle_v1_set_fullscreen( 1129 wlr_foreign_toplevel_handle_v1_set_fullscreen(
984 con->view->foreign_toplevel, *enable); 1130 con->view->foreign_toplevel, enable);
985 } 1131 }
986 } 1132 }
987} 1133}
988 1134
989static void container_fullscreen_workspace(struct sway_container *con) { 1135static void container_fullscreen_workspace(struct sway_container *con) {
990 if (!sway_assert(con->fullscreen_mode == FULLSCREEN_NONE, 1136 if (!sway_assert(con->pending.fullscreen_mode == FULLSCREEN_NONE,
991 "Expected a non-fullscreen container")) { 1137 "Expected a non-fullscreen container")) {
992 return; 1138 return;
993 } 1139 }
994 bool enable = true; 1140 set_fullscreen(con, true);
995 set_fullscreen_iterator(con, &enable); 1141 con->pending.fullscreen_mode = FULLSCREEN_WORKSPACE;
996 container_for_each_child(con, set_fullscreen_iterator, &enable);
997 con->fullscreen_mode = FULLSCREEN_WORKSPACE;
998 1142
999 con->saved_x = con->x; 1143 con->saved_x = con->pending.x;
1000 con->saved_y = con->y; 1144 con->saved_y = con->pending.y;
1001 con->saved_width = con->width; 1145 con->saved_width = con->pending.width;
1002 con->saved_height = con->height; 1146 con->saved_height = con->pending.height;
1003 1147
1004 if (con->workspace) { 1148 if (con->pending.workspace) {
1005 con->workspace->fullscreen = con; 1149 con->pending.workspace->fullscreen = con;
1006 struct sway_seat *seat; 1150 struct sway_seat *seat;
1007 struct sway_workspace *focus_ws; 1151 struct sway_workspace *focus_ws;
1008 wl_list_for_each(seat, &server.input->seats, link) { 1152 wl_list_for_each(seat, &server.input->seats, link) {
1009 focus_ws = seat_get_focused_workspace(seat); 1153 focus_ws = seat_get_focused_workspace(seat);
1010 if (focus_ws == con->workspace) { 1154 if (focus_ws == con->pending.workspace) {
1011 seat_set_focus_container(seat, con); 1155 seat_set_focus_container(seat, con);
1012 } else { 1156 } else {
1013 struct sway_node *focus = 1157 struct sway_node *focus =
@@ -1023,19 +1167,17 @@ static void container_fullscreen_workspace(struct sway_container *con) {
1023} 1167}
1024 1168
1025static void container_fullscreen_global(struct sway_container *con) { 1169static void container_fullscreen_global(struct sway_container *con) {
1026 if (!sway_assert(con->fullscreen_mode == FULLSCREEN_NONE, 1170 if (!sway_assert(con->pending.fullscreen_mode == FULLSCREEN_NONE,
1027 "Expected a non-fullscreen container")) { 1171 "Expected a non-fullscreen container")) {
1028 return; 1172 return;
1029 } 1173 }
1030 bool enable = true; 1174 set_fullscreen(con, true);
1031 set_fullscreen_iterator(con, &enable);
1032 container_for_each_child(con, set_fullscreen_iterator, &enable);
1033 1175
1034 root->fullscreen_global = con; 1176 root->fullscreen_global = con;
1035 con->saved_x = con->x; 1177 con->saved_x = con->pending.x;
1036 con->saved_y = con->y; 1178 con->saved_y = con->pending.y;
1037 con->saved_width = con->width; 1179 con->saved_width = con->pending.width;
1038 con->saved_height = con->height; 1180 con->saved_height = con->pending.height;
1039 1181
1040 struct sway_seat *seat; 1182 struct sway_seat *seat;
1041 wl_list_for_each(seat, &server.input->seats, link) { 1183 wl_list_for_each(seat, &server.input->seats, link) {
@@ -1045,34 +1187,32 @@ static void container_fullscreen_global(struct sway_container *con) {
1045 } 1187 }
1046 } 1188 }
1047 1189
1048 con->fullscreen_mode = FULLSCREEN_GLOBAL; 1190 con->pending.fullscreen_mode = FULLSCREEN_GLOBAL;
1049 container_end_mouse_operation(con); 1191 container_end_mouse_operation(con);
1050 ipc_event_window(con, "fullscreen_mode"); 1192 ipc_event_window(con, "fullscreen_mode");
1051} 1193}
1052 1194
1053void container_fullscreen_disable(struct sway_container *con) { 1195void container_fullscreen_disable(struct sway_container *con) {
1054 if (!sway_assert(con->fullscreen_mode != FULLSCREEN_NONE, 1196 if (!sway_assert(con->pending.fullscreen_mode != FULLSCREEN_NONE,
1055 "Expected a fullscreen container")) { 1197 "Expected a fullscreen container")) {
1056 return; 1198 return;
1057 } 1199 }
1058 bool enable = false; 1200 set_fullscreen(con, false);
1059 set_fullscreen_iterator(con, &enable);
1060 container_for_each_child(con, set_fullscreen_iterator, &enable);
1061 1201
1062 if (container_is_floating(con)) { 1202 if (container_is_floating(con)) {
1063 con->x = con->saved_x; 1203 con->pending.x = con->saved_x;
1064 con->y = con->saved_y; 1204 con->pending.y = con->saved_y;
1065 con->width = con->saved_width; 1205 con->pending.width = con->saved_width;
1066 con->height = con->saved_height; 1206 con->pending.height = con->saved_height;
1067 } 1207 }
1068 1208
1069 if (con->fullscreen_mode == FULLSCREEN_WORKSPACE) { 1209 if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
1070 if (con->workspace) { 1210 if (con->pending.workspace) {
1071 con->workspace->fullscreen = NULL; 1211 con->pending.workspace->fullscreen = NULL;
1072 if (container_is_floating(con)) { 1212 if (container_is_floating(con)) {
1073 struct sway_output *output = 1213 struct sway_output *output =
1074 container_floating_find_output(con); 1214 container_floating_find_output(con);
1075 if (con->workspace->output != output) { 1215 if (con->pending.workspace->output != output) {
1076 container_floating_move_to_center(con); 1216 container_floating_move_to_center(con);
1077 } 1217 }
1078 } 1218 }
@@ -1084,11 +1224,11 @@ void container_fullscreen_disable(struct sway_container *con) {
1084 // If the container was mapped as fullscreen and set as floating by 1224 // If the container was mapped as fullscreen and set as floating by
1085 // criteria, it needs to be reinitialized as floating to get the proper 1225 // criteria, it needs to be reinitialized as floating to get the proper
1086 // size and location 1226 // size and location
1087 if (container_is_floating(con) && (con->width == 0 || con->height == 0)) { 1227 if (container_is_floating(con) && (con->pending.width == 0 || con->pending.height == 0)) {
1088 container_floating_resize_and_center(con); 1228 container_floating_resize_and_center(con);
1089 } 1229 }
1090 1230
1091 con->fullscreen_mode = FULLSCREEN_NONE; 1231 con->pending.fullscreen_mode = FULLSCREEN_NONE;
1092 container_end_mouse_operation(con); 1232 container_end_mouse_operation(con);
1093 ipc_event_window(con, "fullscreen_mode"); 1233 ipc_event_window(con, "fullscreen_mode");
1094 1234
@@ -1106,7 +1246,7 @@ void container_fullscreen_disable(struct sway_container *con) {
1106 1246
1107void container_set_fullscreen(struct sway_container *con, 1247void container_set_fullscreen(struct sway_container *con,
1108 enum sway_fullscreen_mode mode) { 1248 enum sway_fullscreen_mode mode) {
1109 if (con->fullscreen_mode == mode) { 1249 if (con->pending.fullscreen_mode == mode) {
1110 return; 1250 return;
1111 } 1251 }
1112 1252
@@ -1118,8 +1258,8 @@ void container_set_fullscreen(struct sway_container *con,
1118 if (root->fullscreen_global) { 1258 if (root->fullscreen_global) {
1119 container_fullscreen_disable(root->fullscreen_global); 1259 container_fullscreen_disable(root->fullscreen_global);
1120 } 1260 }
1121 if (con->workspace && con->workspace->fullscreen) { 1261 if (con->pending.workspace && con->pending.workspace->fullscreen) {
1122 container_fullscreen_disable(con->workspace->fullscreen); 1262 container_fullscreen_disable(con->pending.workspace->fullscreen);
1123 } 1263 }
1124 container_fullscreen_workspace(con); 1264 container_fullscreen_workspace(con);
1125 break; 1265 break;
@@ -1127,7 +1267,7 @@ void container_set_fullscreen(struct sway_container *con,
1127 if (root->fullscreen_global) { 1267 if (root->fullscreen_global) {
1128 container_fullscreen_disable(root->fullscreen_global); 1268 container_fullscreen_disable(root->fullscreen_global);
1129 } 1269 }
1130 if (con->fullscreen_mode == FULLSCREEN_WORKSPACE) { 1270 if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
1131 container_fullscreen_disable(con); 1271 container_fullscreen_disable(con);
1132 } 1272 }
1133 container_fullscreen_global(con); 1273 container_fullscreen_global(con);
@@ -1137,8 +1277,8 @@ void container_set_fullscreen(struct sway_container *con,
1137 1277
1138struct sway_container *container_toplevel_ancestor( 1278struct sway_container *container_toplevel_ancestor(
1139 struct sway_container *container) { 1279 struct sway_container *container) {
1140 while (container->parent) { 1280 while (container->pending.parent) {
1141 container = container->parent; 1281 container = container->pending.parent;
1142 } 1282 }
1143 1283
1144 return container; 1284 return container;
@@ -1150,148 +1290,67 @@ bool container_is_floating_or_child(struct sway_container *container) {
1150 1290
1151bool container_is_fullscreen_or_child(struct sway_container *container) { 1291bool container_is_fullscreen_or_child(struct sway_container *container) {
1152 do { 1292 do {
1153 if (container->fullscreen_mode) { 1293 if (container->pending.fullscreen_mode) {
1154 return true; 1294 return true;
1155 } 1295 }
1156 container = container->parent; 1296 container = container->pending.parent;
1157 } while (container); 1297 } while (container);
1158 1298
1159 return false; 1299 return false;
1160} 1300}
1161 1301
1162static void surface_send_enter_iterator(struct wlr_surface *surface,
1163 int x, int y, void *data) {
1164 struct wlr_output *wlr_output = data;
1165 wlr_surface_send_enter(surface, wlr_output);
1166}
1167
1168static void surface_send_leave_iterator(struct wlr_surface *surface,
1169 int x, int y, void *data) {
1170 struct wlr_output *wlr_output = data;
1171 wlr_surface_send_leave(surface, wlr_output);
1172}
1173
1174void container_discover_outputs(struct sway_container *con) {
1175 struct wlr_box con_box = {
1176 .x = con->current.x,
1177 .y = con->current.y,
1178 .width = con->current.width,
1179 .height = con->current.height,
1180 };
1181 struct sway_output *old_output = container_get_effective_output(con);
1182
1183 for (int i = 0; i < root->outputs->length; ++i) {
1184 struct sway_output *output = root->outputs->items[i];
1185 struct wlr_box output_box;
1186 output_get_box(output, &output_box);
1187 struct wlr_box intersection;
1188 bool intersects =
1189 wlr_box_intersection(&intersection, &con_box, &output_box);
1190 int index = list_find(con->outputs, output);
1191
1192 if (intersects && index == -1) {
1193 // Send enter
1194 sway_log(SWAY_DEBUG, "Container %p entered output %p", con, output);
1195 if (con->view) {
1196 view_for_each_surface(con->view,
1197 surface_send_enter_iterator, output->wlr_output);
1198 if (con->view->foreign_toplevel) {
1199 wlr_foreign_toplevel_handle_v1_output_enter(
1200 con->view->foreign_toplevel, output->wlr_output);
1201 }
1202 }
1203 list_add(con->outputs, output);
1204 } else if (!intersects && index != -1) {
1205 // Send leave
1206 sway_log(SWAY_DEBUG, "Container %p left output %p", con, output);
1207 if (con->view) {
1208 view_for_each_surface(con->view,
1209 surface_send_leave_iterator, output->wlr_output);
1210 if (con->view->foreign_toplevel) {
1211 wlr_foreign_toplevel_handle_v1_output_leave(
1212 con->view->foreign_toplevel, output->wlr_output);
1213 }
1214 }
1215 list_del(con->outputs, index);
1216 }
1217 }
1218 struct sway_output *new_output = container_get_effective_output(con);
1219 double old_scale = old_output && old_output->enabled ?
1220 old_output->wlr_output->scale : -1;
1221 double new_scale = new_output ? new_output->wlr_output->scale : -1;
1222 if (old_scale != new_scale) {
1223 container_update_title_textures(con);
1224 container_update_marks_textures(con);
1225 }
1226}
1227
1228enum sway_container_layout container_parent_layout(struct sway_container *con) { 1302enum sway_container_layout container_parent_layout(struct sway_container *con) {
1229 if (con->parent) { 1303 if (con->pending.parent) {
1230 return con->parent->layout; 1304 return con->pending.parent->pending.layout;
1231 } 1305 }
1232 if (con->workspace) { 1306 if (con->pending.workspace) {
1233 return con->workspace->layout; 1307 return con->pending.workspace->layout;
1234 } 1308 }
1235 return L_NONE; 1309 return L_NONE;
1236} 1310}
1237 1311
1238enum sway_container_layout container_current_parent_layout(
1239 struct sway_container *con) {
1240 if (con->current.parent) {
1241 return con->current.parent->current.layout;
1242 }
1243 return con->current.workspace->current.layout;
1244}
1245
1246list_t *container_get_siblings(struct sway_container *container) { 1312list_t *container_get_siblings(struct sway_container *container) {
1247 if (container->parent) { 1313 if (container->pending.parent) {
1248 return container->parent->children; 1314 return container->pending.parent->pending.children;
1249 } 1315 }
1250 if (container_is_scratchpad_hidden(container)) { 1316 if (!container->pending.workspace) {
1251 return NULL; 1317 return NULL;
1252 } 1318 }
1253 if (list_find(container->workspace->tiling, container) != -1) { 1319 if (list_find(container->pending.workspace->tiling, container) != -1) {
1254 return container->workspace->tiling; 1320 return container->pending.workspace->tiling;
1255 } 1321 }
1256 return container->workspace->floating; 1322 return container->pending.workspace->floating;
1257} 1323}
1258 1324
1259int container_sibling_index(struct sway_container *child) { 1325int container_sibling_index(struct sway_container *child) {
1260 return list_find(container_get_siblings(child), child); 1326 return list_find(container_get_siblings(child), child);
1261} 1327}
1262 1328
1263list_t *container_get_current_siblings(struct sway_container *container) {
1264 if (container->current.parent) {
1265 return container->current.parent->current.children;
1266 }
1267 return container->current.workspace->current.tiling;
1268}
1269
1270void container_handle_fullscreen_reparent(struct sway_container *con) { 1329void container_handle_fullscreen_reparent(struct sway_container *con) {
1271 if (con->fullscreen_mode != FULLSCREEN_WORKSPACE || !con->workspace || 1330 if (con->pending.fullscreen_mode != FULLSCREEN_WORKSPACE || !con->pending.workspace ||
1272 con->workspace->fullscreen == con) { 1331 con->pending.workspace->fullscreen == con) {
1273 return; 1332 return;
1274 } 1333 }
1275 if (con->workspace->fullscreen) { 1334 if (con->pending.workspace->fullscreen) {
1276 container_fullscreen_disable(con->workspace->fullscreen); 1335 container_fullscreen_disable(con->pending.workspace->fullscreen);
1277 } 1336 }
1278 con->workspace->fullscreen = con; 1337 con->pending.workspace->fullscreen = con;
1279 1338
1280 arrange_workspace(con->workspace); 1339 arrange_workspace(con->pending.workspace);
1281} 1340}
1282 1341
1283static void set_workspace(struct sway_container *container, void *data) { 1342static void set_workspace(struct sway_container *container, void *data) {
1284 container->workspace = container->parent->workspace; 1343 container->pending.workspace = container->pending.parent->pending.workspace;
1285} 1344}
1286 1345
1287void container_insert_child(struct sway_container *parent, 1346void container_insert_child(struct sway_container *parent,
1288 struct sway_container *child, int i) { 1347 struct sway_container *child, int i) {
1289 if (child->workspace) { 1348 if (child->pending.workspace) {
1290 container_detach(child); 1349 container_detach(child);
1291 } 1350 }
1292 list_insert(parent->children, i, child); 1351 list_insert(parent->pending.children, i, child);
1293 child->parent = parent; 1352 child->pending.parent = parent;
1294 child->workspace = parent->workspace; 1353 child->pending.workspace = parent->pending.workspace;
1295 container_for_each_child(child, set_workspace, NULL); 1354 container_for_each_child(child, set_workspace, NULL);
1296 container_handle_fullscreen_reparent(child); 1355 container_handle_fullscreen_reparent(child);
1297 container_update_representation(parent); 1356 container_update_representation(parent);
@@ -1299,14 +1358,14 @@ void container_insert_child(struct sway_container *parent,
1299 1358
1300void container_add_sibling(struct sway_container *fixed, 1359void container_add_sibling(struct sway_container *fixed,
1301 struct sway_container *active, bool after) { 1360 struct sway_container *active, bool after) {
1302 if (active->workspace) { 1361 if (active->pending.workspace) {
1303 container_detach(active); 1362 container_detach(active);
1304 } 1363 }
1305 list_t *siblings = container_get_siblings(fixed); 1364 list_t *siblings = container_get_siblings(fixed);
1306 int index = list_find(siblings, fixed); 1365 int index = list_find(siblings, fixed);
1307 list_insert(siblings, index + after, active); 1366 list_insert(siblings, index + after, active);
1308 active->parent = fixed->parent; 1367 active->pending.parent = fixed->pending.parent;
1309 active->workspace = fixed->workspace; 1368 active->pending.workspace = fixed->pending.workspace;
1310 container_for_each_child(active, set_workspace, NULL); 1369 container_for_each_child(active, set_workspace, NULL);
1311 container_handle_fullscreen_reparent(active); 1370 container_handle_fullscreen_reparent(active);
1312 container_update_representation(active); 1371 container_update_representation(active);
@@ -1314,17 +1373,13 @@ void container_add_sibling(struct sway_container *fixed,
1314 1373
1315void container_add_child(struct sway_container *parent, 1374void container_add_child(struct sway_container *parent,
1316 struct sway_container *child) { 1375 struct sway_container *child) {
1317 if (child->workspace) { 1376 if (child->pending.workspace) {
1318 container_detach(child); 1377 container_detach(child);
1319 } 1378 }
1320 list_add(parent->children, child); 1379 list_add(parent->pending.children, child);
1321 child->parent = parent; 1380 child->pending.parent = parent;
1322 child->workspace = parent->workspace; 1381 child->pending.workspace = parent->pending.workspace;
1323 container_for_each_child(child, set_workspace, NULL); 1382 container_for_each_child(child, set_workspace, NULL);
1324 bool fullscreen = child->fullscreen_mode != FULLSCREEN_NONE ||
1325 parent->fullscreen_mode != FULLSCREEN_NONE;
1326 set_fullscreen_iterator(child, &fullscreen);
1327 container_for_each_child(child, set_fullscreen_iterator, &fullscreen);
1328 container_handle_fullscreen_reparent(child); 1383 container_handle_fullscreen_reparent(child);
1329 container_update_representation(parent); 1384 container_update_representation(parent);
1330 node_set_dirty(&child->node); 1385 node_set_dirty(&child->node);
@@ -1332,15 +1387,15 @@ void container_add_child(struct sway_container *parent,
1332} 1387}
1333 1388
1334void container_detach(struct sway_container *child) { 1389void container_detach(struct sway_container *child) {
1335 if (child->fullscreen_mode == FULLSCREEN_WORKSPACE) { 1390 if (child->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
1336 child->workspace->fullscreen = NULL; 1391 child->pending.workspace->fullscreen = NULL;
1337 } 1392 }
1338 if (child->fullscreen_mode == FULLSCREEN_GLOBAL) { 1393 if (child->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
1339 root->fullscreen_global = NULL; 1394 root->fullscreen_global = NULL;
1340 } 1395 }
1341 1396
1342 struct sway_container *old_parent = child->parent; 1397 struct sway_container *old_parent = child->pending.parent;
1343 struct sway_workspace *old_workspace = child->workspace; 1398 struct sway_workspace *old_workspace = child->pending.workspace;
1344 list_t *siblings = container_get_siblings(child); 1399 list_t *siblings = container_get_siblings(child);
1345 if (siblings) { 1400 if (siblings) {
1346 int index = list_find(siblings, child); 1401 int index = list_find(siblings, child);
@@ -1348,8 +1403,8 @@ void container_detach(struct sway_container *child) {
1348 list_del(siblings, index); 1403 list_del(siblings, index);
1349 } 1404 }
1350 } 1405 }
1351 child->parent = NULL; 1406 child->pending.parent = NULL;
1352 child->workspace = NULL; 1407 child->pending.workspace = NULL;
1353 container_for_each_child(child, set_workspace, NULL); 1408 container_for_each_child(child, set_workspace, NULL);
1354 1409
1355 if (old_parent) { 1410 if (old_parent) {
@@ -1364,18 +1419,18 @@ void container_detach(struct sway_container *child) {
1364 1419
1365void container_replace(struct sway_container *container, 1420void container_replace(struct sway_container *container,
1366 struct sway_container *replacement) { 1421 struct sway_container *replacement) {
1367 enum sway_fullscreen_mode fullscreen = container->fullscreen_mode; 1422 enum sway_fullscreen_mode fullscreen = container->pending.fullscreen_mode;
1368 bool scratchpad = container->scratchpad; 1423 bool scratchpad = container->scratchpad;
1369 struct sway_workspace *ws = NULL; 1424 struct sway_workspace *ws = NULL;
1370 if (fullscreen != FULLSCREEN_NONE) { 1425 if (fullscreen != FULLSCREEN_NONE) {
1371 container_fullscreen_disable(container); 1426 container_fullscreen_disable(container);
1372 } 1427 }
1373 if (scratchpad) { 1428 if (scratchpad) {
1374 ws = container->workspace; 1429 ws = container->pending.workspace;
1375 root_scratchpad_show(container); 1430 root_scratchpad_show(container);
1376 root_scratchpad_remove_container(container); 1431 root_scratchpad_remove_container(container);
1377 } 1432 }
1378 if (container->parent || container->workspace) { 1433 if (container->pending.parent || container->pending.workspace) {
1379 float width_fraction = container->width_fraction; 1434 float width_fraction = container->width_fraction;
1380 float height_fraction = container->height_fraction; 1435 float height_fraction = container->height_fraction;
1381 container_add_sibling(container, replacement, 1); 1436 container_add_sibling(container, replacement, 1);
@@ -1403,7 +1458,7 @@ struct sway_container *container_split(struct sway_container *child,
1403 enum sway_container_layout layout) { 1458 enum sway_container_layout layout) {
1404 // i3 doesn't split singleton H/V containers 1459 // i3 doesn't split singleton H/V containers
1405 // https://github.com/i3/i3/blob/3cd1c45eba6de073bc4300eebb4e1cc1a0c4479a/src/tree.c#L354 1460 // https://github.com/i3/i3/blob/3cd1c45eba6de073bc4300eebb4e1cc1a0c4479a/src/tree.c#L354
1406 if (child->parent || child->workspace) { 1461 if (child->pending.parent || child->pending.workspace) {
1407 list_t *siblings = container_get_siblings(child); 1462 list_t *siblings = container_get_siblings(child);
1408 if (siblings->length == 1) { 1463 if (siblings->length == 1) {
1409 enum sway_container_layout current = container_parent_layout(child); 1464 enum sway_container_layout current = container_parent_layout(child);
@@ -1411,12 +1466,12 @@ struct sway_container *container_split(struct sway_container *child,
1411 current = L_NONE; 1466 current = L_NONE;
1412 } 1467 }
1413 if (current == L_HORIZ || current == L_VERT) { 1468 if (current == L_HORIZ || current == L_VERT) {
1414 if (child->parent) { 1469 if (child->pending.parent) {
1415 child->parent->layout = layout; 1470 child->pending.parent->pending.layout = layout;
1416 container_update_representation(child->parent); 1471 container_update_representation(child->pending.parent);
1417 } else { 1472 } else {
1418 child->workspace->layout = layout; 1473 child->pending.workspace->layout = layout;
1419 workspace_update_representation(child->workspace); 1474 workspace_update_representation(child->pending.workspace);
1420 } 1475 }
1421 return child; 1476 return child;
1422 } 1477 }
@@ -1429,25 +1484,25 @@ struct sway_container *container_split(struct sway_container *child,
1429 if (container_is_floating(child) && child->view) { 1484 if (container_is_floating(child) && child->view) {
1430 view_set_tiled(child->view, true); 1485 view_set_tiled(child->view, true);
1431 if (child->view->using_csd) { 1486 if (child->view->using_csd) {
1432 child->border = child->saved_border; 1487 child->pending.border = child->saved_border;
1433 } 1488 }
1434 } 1489 }
1435 1490
1436 struct sway_container *cont = container_create(NULL); 1491 struct sway_container *cont = container_create(NULL);
1437 cont->width = child->width; 1492 cont->pending.width = child->pending.width;
1438 cont->height = child->height; 1493 cont->pending.height = child->pending.height;
1439 cont->width_fraction = child->width_fraction; 1494 cont->width_fraction = child->width_fraction;
1440 cont->height_fraction = child->height_fraction; 1495 cont->height_fraction = child->height_fraction;
1441 cont->x = child->x; 1496 cont->pending.x = child->pending.x;
1442 cont->y = child->y; 1497 cont->pending.y = child->pending.y;
1443 cont->layout = layout; 1498 cont->pending.layout = layout;
1444 1499
1445 container_replace(child, cont); 1500 container_replace(child, cont);
1446 container_add_child(cont, child); 1501 container_add_child(cont, child);
1447 1502
1448 if (set_focus) { 1503 if (set_focus) {
1449 seat_set_raw_focus(seat, &cont->node); 1504 seat_set_raw_focus(seat, &cont->node);
1450 if (cont->fullscreen_mode == FULLSCREEN_GLOBAL) { 1505 if (cont->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
1451 seat_set_focus(seat, &child->node); 1506 seat_set_focus(seat, &child->node);
1452 } else { 1507 } else {
1453 seat_set_raw_focus(seat, &child->node); 1508 seat_set_raw_focus(seat, &child->node);
@@ -1485,7 +1540,7 @@ bool container_find_and_unmark(char *mark) {
1485 if (strcmp(con_mark, mark) == 0) { 1540 if (strcmp(con_mark, mark) == 0) {
1486 free(con_mark); 1541 free(con_mark);
1487 list_del(con->marks, i); 1542 list_del(con->marks, i);
1488 container_update_marks_textures(con); 1543 container_update_marks(con);
1489 ipc_event_window(con, "mark"); 1544 ipc_event_window(con, "mark");
1490 return true; 1545 return true;
1491 } 1546 }
@@ -1516,111 +1571,27 @@ void container_add_mark(struct sway_container *con, char *mark) {
1516 ipc_event_window(con, "mark"); 1571 ipc_event_window(con, "mark");
1517} 1572}
1518 1573
1519static void update_marks_texture(struct sway_container *con,
1520 struct wlr_texture **texture, struct border_colors *class) {
1521 struct sway_output *output = container_get_effective_output(con);
1522 if (!output) {
1523 return;
1524 }
1525 if (*texture) {
1526 wlr_texture_destroy(*texture);
1527 *texture = NULL;
1528 }
1529 if (!con->marks->length) {
1530 return;
1531 }
1532
1533 size_t len = 0;
1534 for (int i = 0; i < con->marks->length; ++i) {
1535 char *mark = con->marks->items[i];
1536 if (mark[0] != '_') {
1537 len += strlen(mark) + 2;
1538 }
1539 }
1540 char *buffer = calloc(len + 1, 1);
1541 char *part = malloc(len + 1);
1542
1543 if (!sway_assert(buffer && part, "Unable to allocate memory")) {
1544 free(buffer);
1545 return;
1546 }
1547
1548 for (int i = 0; i < con->marks->length; ++i) {
1549 char *mark = con->marks->items[i];
1550 if (mark[0] != '_') {
1551 sprintf(part, "[%s]", mark);
1552 strcat(buffer, part);
1553 }
1554 }
1555 free(part);
1556
1557 double scale = output->wlr_output->scale;
1558 int width = 0;
1559 int height = con->title_height * scale;
1560
1561 cairo_t *c = cairo_create(NULL);
1562 get_text_size(c, config->font, &width, NULL, NULL, scale, false,
1563 "%s", buffer);
1564 cairo_destroy(c);
1565
1566 cairo_surface_t *surface = cairo_image_surface_create(
1567 CAIRO_FORMAT_ARGB32, width, height);
1568 cairo_t *cairo = cairo_create(surface);
1569 cairo_set_source_rgba(cairo, class->background[0], class->background[1],
1570 class->background[2], class->background[3]);
1571 cairo_paint(cairo);
1572 PangoContext *pango = pango_cairo_create_context(cairo);
1573 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
1574 cairo_set_source_rgba(cairo, class->text[0], class->text[1],
1575 class->text[2], class->text[3]);
1576 cairo_move_to(cairo, 0, 0);
1577
1578 pango_printf(cairo, config->font, scale, false, "%s", buffer);
1579
1580 cairo_surface_flush(surface);
1581 unsigned char *data = cairo_image_surface_get_data(surface);
1582 int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
1583 struct wlr_renderer *renderer = wlr_backend_get_renderer(
1584 output->wlr_output->backend);
1585 *texture = wlr_texture_from_pixels(
1586 renderer, WL_SHM_FORMAT_ARGB8888, stride, width, height, data);
1587 cairo_surface_destroy(surface);
1588 g_object_unref(pango);
1589 cairo_destroy(cairo);
1590 free(buffer);
1591}
1592
1593void container_update_marks_textures(struct sway_container *con) {
1594 if (!config->show_marks) {
1595 return;
1596 }
1597 update_marks_texture(con, &con->marks_focused,
1598 &config->border_colors.focused);
1599 update_marks_texture(con, &con->marks_focused_inactive,
1600 &config->border_colors.focused_inactive);
1601 update_marks_texture(con, &con->marks_unfocused,
1602 &config->border_colors.unfocused);
1603 update_marks_texture(con, &con->marks_urgent,
1604 &config->border_colors.urgent);
1605 container_damage_whole(con);
1606}
1607
1608void container_raise_floating(struct sway_container *con) { 1574void container_raise_floating(struct sway_container *con) {
1609 // Bring container to front by putting it at the end of the floating list. 1575 // Bring container to front by putting it at the end of the floating list.
1610 struct sway_container *floater = container_toplevel_ancestor(con); 1576 struct sway_container *floater = container_toplevel_ancestor(con);
1611 if (container_is_floating(floater) && floater->workspace) { 1577 if (container_is_floating(floater) && floater->pending.workspace) {
1612 list_move_to_end(floater->workspace->floating, floater); 1578 // it's okay to just raise the scene directly instead of waiting
1613 node_set_dirty(&floater->workspace->node); 1579 // for the transaction to go through. We won't be reconfiguring
1580 // surfaces
1581 wlr_scene_node_raise_to_top(&floater->scene_tree->node);
1582
1583 list_move_to_end(floater->pending.workspace->floating, floater);
1584 node_set_dirty(&floater->pending.workspace->node);
1614 } 1585 }
1615} 1586}
1616 1587
1617bool container_is_scratchpad_hidden(struct sway_container *con) { 1588bool container_is_scratchpad_hidden(struct sway_container *con) {
1618 return con->scratchpad && !con->workspace; 1589 return con->scratchpad && !con->pending.workspace;
1619} 1590}
1620 1591
1621bool container_is_scratchpad_hidden_or_child(struct sway_container *con) { 1592bool container_is_scratchpad_hidden_or_child(struct sway_container *con) {
1622 con = container_toplevel_ancestor(con); 1593 con = container_toplevel_ancestor(con);
1623 return con->scratchpad && !con->workspace; 1594 return con->scratchpad && !con->pending.workspace;
1624} 1595}
1625 1596
1626bool container_is_sticky(struct sway_container *con) { 1597bool container_is_sticky(struct sway_container *con) {
@@ -1648,39 +1619,39 @@ static bool is_parallel(enum sway_container_layout first,
1648static bool container_is_squashable(struct sway_container *con, 1619static bool container_is_squashable(struct sway_container *con,
1649 struct sway_container *child) { 1620 struct sway_container *child) {
1650 enum sway_container_layout gp_layout = container_parent_layout(con); 1621 enum sway_container_layout gp_layout = container_parent_layout(con);
1651 return (con->layout == L_HORIZ || con->layout == L_VERT) && 1622 return (con->pending.layout == L_HORIZ || con->pending.layout == L_VERT) &&
1652 (child->layout == L_HORIZ || child->layout == L_VERT) && 1623 (child->pending.layout == L_HORIZ || child->pending.layout == L_VERT) &&
1653 !is_parallel(con->layout, child->layout) && 1624 !is_parallel(con->pending.layout, child->pending.layout) &&
1654 is_parallel(gp_layout, child->layout); 1625 is_parallel(gp_layout, child->pending.layout);
1655} 1626}
1656 1627
1657static void container_squash_children(struct sway_container *con) { 1628static void container_squash_children(struct sway_container *con) {
1658 for (int i = 0; i < con->children->length; i++) { 1629 for (int i = 0; i < con->pending.children->length; i++) {
1659 struct sway_container *child = con->children->items[i]; 1630 struct sway_container *child = con->pending.children->items[i];
1660 i += container_squash(child); 1631 i += container_squash(child);
1661 } 1632 }
1662} 1633}
1663 1634
1664int container_squash(struct sway_container *con) { 1635int container_squash(struct sway_container *con) {
1665 if (!con->children) { 1636 if (!con->pending.children) {
1666 return 0; 1637 return 0;
1667 } 1638 }
1668 if (con->children->length != 1) { 1639 if (con->pending.children->length != 1) {
1669 container_squash_children(con); 1640 container_squash_children(con);
1670 return 0; 1641 return 0;
1671 } 1642 }
1672 struct sway_container *child = con->children->items[0]; 1643 struct sway_container *child = con->pending.children->items[0];
1673 int idx = container_sibling_index(con); 1644 int idx = container_sibling_index(con);
1674 int change = 0; 1645 int change = 0;
1675 if (container_is_squashable(con, child)) { 1646 if (container_is_squashable(con, child)) {
1676 // con and child are a redundant H/V pair. Destroy them. 1647 // con and child are a redundant H/V pair. Destroy them.
1677 while (child->children->length) { 1648 while (child->pending.children->length) {
1678 struct sway_container *current = child->children->items[0]; 1649 struct sway_container *current = child->pending.children->items[0];
1679 container_detach(current); 1650 container_detach(current);
1680 if (con->parent) { 1651 if (con->pending.parent) {
1681 container_insert_child(con->parent, current, idx); 1652 container_insert_child(con->pending.parent, current, idx);
1682 } else { 1653 } else {
1683 workspace_insert_tiling_direct(con->workspace, current, idx); 1654 workspace_insert_tiling_direct(con->pending.workspace, current, idx);
1684 } 1655 }
1685 change++; 1656 change++;
1686 } 1657 }
@@ -1692,3 +1663,177 @@ int container_squash(struct sway_container *con) {
1692 } 1663 }
1693 return change; 1664 return change;
1694} 1665}
1666
1667static void swap_places(struct sway_container *con1,
1668 struct sway_container *con2) {
1669 struct sway_container *temp = malloc(sizeof(struct sway_container));
1670 temp->pending.x = con1->pending.x;
1671 temp->pending.y = con1->pending.y;
1672 temp->pending.width = con1->pending.width;
1673 temp->pending.height = con1->pending.height;
1674 temp->width_fraction = con1->width_fraction;
1675 temp->height_fraction = con1->height_fraction;
1676 temp->pending.parent = con1->pending.parent;
1677 temp->pending.workspace = con1->pending.workspace;
1678 bool temp_floating = container_is_floating(con1);
1679
1680 con1->pending.x = con2->pending.x;
1681 con1->pending.y = con2->pending.y;
1682 con1->pending.width = con2->pending.width;
1683 con1->pending.height = con2->pending.height;
1684 con1->width_fraction = con2->width_fraction;
1685 con1->height_fraction = con2->height_fraction;
1686
1687 con2->pending.x = temp->pending.x;
1688 con2->pending.y = temp->pending.y;
1689 con2->pending.width = temp->pending.width;
1690 con2->pending.height = temp->pending.height;
1691 con2->width_fraction = temp->width_fraction;
1692 con2->height_fraction = temp->height_fraction;
1693
1694 int temp_index = container_sibling_index(con1);
1695 if (con2->pending.parent) {
1696 container_insert_child(con2->pending.parent, con1,
1697 container_sibling_index(con2));
1698 } else if (container_is_floating(con2)) {
1699 workspace_add_floating(con2->pending.workspace, con1);
1700 } else {
1701 workspace_insert_tiling(con2->pending.workspace, con1,
1702 container_sibling_index(con2));
1703 }
1704 if (temp->pending.parent) {
1705 container_insert_child(temp->pending.parent, con2, temp_index);
1706 } else if (temp_floating) {
1707 workspace_add_floating(temp->pending.workspace, con2);
1708 } else {
1709 workspace_insert_tiling(temp->pending.workspace, con2, temp_index);
1710 }
1711
1712 free(temp);
1713}
1714
1715static void swap_focus(struct sway_container *con1,
1716 struct sway_container *con2, struct sway_seat *seat,
1717 struct sway_container *focus) {
1718 if (focus == con1 || focus == con2) {
1719 struct sway_workspace *ws1 = con1->pending.workspace;
1720 struct sway_workspace *ws2 = con2->pending.workspace;
1721 enum sway_container_layout layout1 = container_parent_layout(con1);
1722 enum sway_container_layout layout2 = container_parent_layout(con2);
1723 if (focus == con1 && (layout2 == L_TABBED || layout2 == L_STACKED)) {
1724 if (workspace_is_visible(ws2)) {
1725 seat_set_focus(seat, &con2->node);
1726 }
1727 seat_set_focus_container(seat, ws1 != ws2 ? con2 : con1);
1728 } else if (focus == con2 && (layout1 == L_TABBED
1729 || layout1 == L_STACKED)) {
1730 if (workspace_is_visible(ws1)) {
1731 seat_set_focus(seat, &con1->node);
1732 }
1733 seat_set_focus_container(seat, ws1 != ws2 ? con1 : con2);
1734 } else if (ws1 != ws2) {
1735 seat_set_focus_container(seat, focus == con1 ? con2 : con1);
1736 } else {
1737 seat_set_focus_container(seat, focus);
1738 }
1739 } else {
1740 seat_set_focus_container(seat, focus);
1741 }
1742
1743 if (root->fullscreen_global) {
1744 seat_set_focus(seat,
1745 seat_get_focus_inactive(seat, &root->fullscreen_global->node));
1746 }
1747}
1748
1749void container_swap(struct sway_container *con1, struct sway_container *con2) {
1750 if (!sway_assert(con1 && con2, "Cannot swap with nothing")) {
1751 return;
1752 }
1753 if (!sway_assert(!container_has_ancestor(con1, con2)
1754 && !container_has_ancestor(con2, con1),
1755 "Cannot swap ancestor and descendant")) {
1756 return;
1757 }
1758
1759 sway_log(SWAY_DEBUG, "Swapping containers %zu and %zu",
1760 con1->node.id, con2->node.id);
1761
1762 bool scratch1 = con1->scratchpad;
1763 bool hidden1 = container_is_scratchpad_hidden(con1);
1764 bool scratch2 = con2->scratchpad;
1765 bool hidden2 = container_is_scratchpad_hidden(con2);
1766 if (scratch1) {
1767 if (hidden1) {
1768 root_scratchpad_show(con1);
1769 }
1770 root_scratchpad_remove_container(con1);
1771 }
1772 if (scratch2) {
1773 if (hidden2) {
1774 root_scratchpad_show(con2);
1775 }
1776 root_scratchpad_remove_container(con2);
1777 }
1778
1779 enum sway_fullscreen_mode fs1 = con1->pending.fullscreen_mode;
1780 if (fs1) {
1781 container_fullscreen_disable(con1);
1782 }
1783 enum sway_fullscreen_mode fs2 = con2->pending.fullscreen_mode;
1784 if (fs2) {
1785 container_fullscreen_disable(con2);
1786 }
1787
1788 struct sway_seat *seat = input_manager_current_seat();
1789 struct sway_container *focus = seat_get_focused_container(seat);
1790 struct sway_workspace *vis1 =
1791 output_get_active_workspace(con1->pending.workspace->output);
1792 struct sway_workspace *vis2 =
1793 output_get_active_workspace(con2->pending.workspace->output);
1794 if (!sway_assert(vis1 && vis2, "con1 or con2 are on an output without a"
1795 "workspace. This should not happen")) {
1796 return;
1797 }
1798
1799 char *stored_prev_name = NULL;
1800 if (seat->prev_workspace_name) {
1801 stored_prev_name = strdup(seat->prev_workspace_name);
1802 }
1803
1804 swap_places(con1, con2);
1805
1806 if (!workspace_is_visible(vis1)) {
1807 seat_set_focus(seat, seat_get_focus_inactive(seat, &vis1->node));
1808 }
1809 if (!workspace_is_visible(vis2)) {
1810 seat_set_focus(seat, seat_get_focus_inactive(seat, &vis2->node));
1811 }
1812
1813 swap_focus(con1, con2, seat, focus);
1814
1815 if (stored_prev_name) {
1816 free(seat->prev_workspace_name);
1817 seat->prev_workspace_name = stored_prev_name;
1818 }
1819
1820 if (scratch1) {
1821 root_scratchpad_add_container(con2, NULL);
1822 if (!hidden1) {
1823 root_scratchpad_show(con2);
1824 }
1825 }
1826 if (scratch2) {
1827 root_scratchpad_add_container(con1, NULL);
1828 if (!hidden2) {
1829 root_scratchpad_show(con1);
1830 }
1831 }
1832
1833 if (fs1) {
1834 container_set_fullscreen(con2, fs1);
1835 }
1836 if (fs2) {
1837 container_set_fullscreen(con1, fs2);
1838 }
1839}
diff --git a/sway/tree/node.c b/sway/tree/node.c
index ffa7f2cc..213cf0a6 100644
--- a/sway/tree/node.c
+++ b/sway/tree/node.c
@@ -18,13 +18,13 @@ void node_init(struct sway_node *node, enum sway_node_type type, void *thing) {
18const char *node_type_to_str(enum sway_node_type type) { 18const char *node_type_to_str(enum sway_node_type type) {
19 switch (type) { 19 switch (type) {
20 case N_ROOT: 20 case N_ROOT:
21 return "N_ROOT"; 21 return "root";
22 case N_OUTPUT: 22 case N_OUTPUT:
23 return "N_OUTPUT"; 23 return "output";
24 case N_WORKSPACE: 24 case N_WORKSPACE:
25 return "N_WORKSPACE"; 25 return "workspace";
26 case N_CONTAINER: 26 case N_CONTAINER:
27 return "N_CONTAINER"; 27 return "container";
28 } 28 }
29 return ""; 29 return "";
30} 30}
@@ -75,7 +75,7 @@ void node_get_box(struct sway_node *node, struct wlr_box *box) {
75struct sway_output *node_get_output(struct sway_node *node) { 75struct sway_output *node_get_output(struct sway_node *node) {
76 switch (node->type) { 76 switch (node->type) {
77 case N_CONTAINER: { 77 case N_CONTAINER: {
78 struct sway_workspace *ws = node->sway_container->workspace; 78 struct sway_workspace *ws = node->sway_container->pending.workspace;
79 return ws ? ws->output : NULL; 79 return ws ? ws->output : NULL;
80 } 80 }
81 case N_WORKSPACE: 81 case N_WORKSPACE:
@@ -91,7 +91,7 @@ struct sway_output *node_get_output(struct sway_node *node) {
91enum sway_container_layout node_get_layout(struct sway_node *node) { 91enum sway_container_layout node_get_layout(struct sway_node *node) {
92 switch (node->type) { 92 switch (node->type) {
93 case N_CONTAINER: 93 case N_CONTAINER:
94 return node->sway_container->layout; 94 return node->sway_container->pending.layout;
95 case N_WORKSPACE: 95 case N_WORKSPACE:
96 return node->sway_workspace->layout; 96 return node->sway_workspace->layout;
97 case N_OUTPUT: 97 case N_OUTPUT:
@@ -105,11 +105,11 @@ struct sway_node *node_get_parent(struct sway_node *node) {
105 switch (node->type) { 105 switch (node->type) {
106 case N_CONTAINER: { 106 case N_CONTAINER: {
107 struct sway_container *con = node->sway_container; 107 struct sway_container *con = node->sway_container;
108 if (con->parent) { 108 if (con->pending.parent) {
109 return &con->parent->node; 109 return &con->pending.parent->node;
110 } 110 }
111 if (con->workspace) { 111 if (con->pending.workspace) {
112 return &con->workspace->node; 112 return &con->pending.workspace->node;
113 } 113 }
114 } 114 }
115 return NULL; 115 return NULL;
@@ -131,7 +131,7 @@ struct sway_node *node_get_parent(struct sway_node *node) {
131list_t *node_get_children(struct sway_node *node) { 131list_t *node_get_children(struct sway_node *node) {
132 switch (node->type) { 132 switch (node->type) {
133 case N_CONTAINER: 133 case N_CONTAINER:
134 return node->sway_container->children; 134 return node->sway_container->pending.children;
135 case N_WORKSPACE: 135 case N_WORKSPACE:
136 return node->sway_workspace->tiling; 136 return node->sway_workspace->tiling;
137 case N_OUTPUT: 137 case N_OUTPUT:
@@ -143,7 +143,7 @@ list_t *node_get_children(struct sway_node *node) {
143 143
144bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) { 144bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) {
145 if (ancestor->type == N_ROOT && node->type == N_CONTAINER && 145 if (ancestor->type == N_ROOT && node->type == N_CONTAINER &&
146 node->sway_container->fullscreen_mode == FULLSCREEN_GLOBAL) { 146 node->sway_container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
147 return true; 147 return true;
148 } 148 }
149 struct sway_node *parent = node_get_parent(node); 149 struct sway_node *parent = node_get_parent(node);
@@ -152,10 +152,39 @@ bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) {
152 return true; 152 return true;
153 } 153 }
154 if (ancestor->type == N_ROOT && parent->type == N_CONTAINER && 154 if (ancestor->type == N_ROOT && parent->type == N_CONTAINER &&
155 parent->sway_container->fullscreen_mode == FULLSCREEN_GLOBAL) { 155 parent->sway_container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
156 return true; 156 return true;
157 } 157 }
158 parent = node_get_parent(parent); 158 parent = node_get_parent(parent);
159 } 159 }
160 return false; 160 return false;
161} 161}
162
163void scene_node_disown_children(struct wlr_scene_tree *tree) {
164 // this function can be called as part of destruction code that will be invoked
165 // upon an allocation failure. Let's not crash on NULL due to an allocation error.
166 if (!tree) {
167 return;
168 }
169
170 struct wlr_scene_node *child, *tmp_child;
171 wl_list_for_each_safe(child, tmp_child, &tree->children, link) {
172 wlr_scene_node_reparent(child, root->staging);
173 }
174}
175
176struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent,
177 bool *failed) {
178 // fallthrough
179 if (*failed) {
180 return NULL;
181 }
182
183 struct wlr_scene_tree *tree = wlr_scene_tree_create(parent);
184 if (!tree) {
185 sway_log(SWAY_ERROR, "Failed to allocate a scene node");
186 *failed = true;
187 }
188
189 return tree;
190}
diff --git a/sway/tree/output.c b/sway/tree/output.c
index a8ae30f7..cd7bf0c2 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -3,12 +3,12 @@
3#include <ctype.h> 3#include <ctype.h>
4#include <string.h> 4#include <string.h>
5#include <strings.h> 5#include <strings.h>
6#include <wlr/types/wlr_output_damage.h>
7#include "sway/ipc-server.h" 6#include "sway/ipc-server.h"
8#include "sway/layers.h" 7#include "sway/layers.h"
9#include "sway/output.h" 8#include "sway/output.h"
10#include "sway/tree/arrange.h" 9#include "sway/tree/arrange.h"
11#include "sway/tree/workspace.h" 10#include "sway/tree/workspace.h"
11#include "sway/server.h"
12#include "log.h" 12#include "log.h"
13#include "util.h" 13#include "util.h"
14 14
@@ -56,8 +56,8 @@ static void restore_workspaces(struct sway_output *output) {
56 } 56 }
57 57
58 // Saved workspaces 58 // Saved workspaces
59 while (root->noop_output->workspaces->length) { 59 while (root->fallback_output->workspaces->length) {
60 struct sway_workspace *ws = root->noop_output->workspaces->items[0]; 60 struct sway_workspace *ws = root->fallback_output->workspaces->items[0];
61 workspace_detach(ws); 61 workspace_detach(ws);
62 output_add_workspace(output, ws); 62 output_add_workspace(output, ws);
63 63
@@ -70,13 +70,13 @@ static void restore_workspaces(struct sway_output *output) {
70 // floater re-centered 70 // floater re-centered
71 for (int i = 0; i < ws->floating->length; i++) { 71 for (int i = 0; i < ws->floating->length; i++) {
72 struct sway_container *floater = ws->floating->items[i]; 72 struct sway_container *floater = ws->floating->items[i];
73 if (floater->width == 0 || floater->height == 0 || 73 if (floater->pending.width == 0 || floater->pending.height == 0 ||
74 floater->width > output->width || 74 floater->pending.width > output->width ||
75 floater->height > output->height || 75 floater->pending.height > output->height ||
76 floater->x > output->lx + output->width || 76 floater->pending.x > output->lx + output->width ||
77 floater->y > output->ly + output->height || 77 floater->pending.y > output->ly + output->height ||
78 floater->x + floater->width < output->lx || 78 floater->pending.x + floater->pending.width < output->lx ||
79 floater->y + floater->height < output->ly) { 79 floater->pending.y + floater->pending.height < output->ly) {
80 container_floating_resize_and_center(floater); 80 container_floating_resize_and_center(floater);
81 } 81 }
82 } 82 }
@@ -87,26 +87,63 @@ static void restore_workspaces(struct sway_output *output) {
87 output_sort_workspaces(output); 87 output_sort_workspaces(output);
88} 88}
89 89
90static void destroy_scene_layers(struct sway_output *output) {
91 wlr_scene_node_destroy(&output->fullscreen_background->node);
92
93 scene_node_disown_children(output->layers.tiling);
94 scene_node_disown_children(output->layers.fullscreen);
95
96 wlr_scene_node_destroy(&output->layers.shell_background->node);
97 wlr_scene_node_destroy(&output->layers.shell_bottom->node);
98 wlr_scene_node_destroy(&output->layers.tiling->node);
99 wlr_scene_node_destroy(&output->layers.fullscreen->node);
100 wlr_scene_node_destroy(&output->layers.shell_top->node);
101 wlr_scene_node_destroy(&output->layers.shell_overlay->node);
102 wlr_scene_node_destroy(&output->layers.session_lock->node);
103}
104
90struct sway_output *output_create(struct wlr_output *wlr_output) { 105struct sway_output *output_create(struct wlr_output *wlr_output) {
91 struct sway_output *output = calloc(1, sizeof(struct sway_output)); 106 struct sway_output *output = calloc(1, sizeof(struct sway_output));
92 node_init(&output->node, N_OUTPUT, output); 107 node_init(&output->node, N_OUTPUT, output);
108
109 bool failed = false;
110 output->layers.shell_background = alloc_scene_tree(root->staging, &failed);
111 output->layers.shell_bottom = alloc_scene_tree(root->staging, &failed);
112 output->layers.tiling = alloc_scene_tree(root->staging, &failed);
113 output->layers.fullscreen = alloc_scene_tree(root->staging, &failed);
114 output->layers.shell_top = alloc_scene_tree(root->staging, &failed);
115 output->layers.shell_overlay = alloc_scene_tree(root->staging, &failed);
116 output->layers.session_lock = alloc_scene_tree(root->staging, &failed);
117
118 if (!failed) {
119 output->fullscreen_background = wlr_scene_rect_create(
120 output->layers.fullscreen, 0, 0, (float[4]){0.f, 0.f, 0.f, 1.f});
121
122 if (!output->fullscreen_background) {
123 sway_log(SWAY_ERROR, "Unable to allocate a background rect");
124 failed = true;
125 }
126 }
127
128 if (failed) {
129 destroy_scene_layers(output);
130 wlr_scene_output_destroy(output->scene_output);
131 free(output);
132 return NULL;
133 }
134
93 output->wlr_output = wlr_output; 135 output->wlr_output = wlr_output;
94 wlr_output->data = output; 136 wlr_output->data = output;
95 output->detected_subpixel = wlr_output->subpixel; 137 output->detected_subpixel = wlr_output->subpixel;
96 output->scale_filter = SCALE_FILTER_NEAREST; 138 output->scale_filter = SCALE_FILTER_NEAREST;
97 139
98 wl_signal_init(&output->events.destroy); 140 wl_signal_init(&output->events.disable);
99 141
100 wl_list_insert(&root->all_outputs, &output->link); 142 wl_list_insert(&root->all_outputs, &output->link);
101 143
102 output->workspaces = create_list(); 144 output->workspaces = create_list();
103 output->current.workspaces = create_list(); 145 output->current.workspaces = create_list();
104 146
105 size_t len = sizeof(output->layers) / sizeof(output->layers[0]);
106 for (size_t i = 0; i < len; ++i) {
107 wl_list_init(&output->layers[i]);
108 }
109
110 return output; 147 return output;
111} 148}
112 149
@@ -146,7 +183,7 @@ void output_enable(struct sway_output *output) {
146 183
147 input_manager_configure_xcursor(); 184 input_manager_configure_xcursor();
148 185
149 wl_signal_emit(&root->events.new_node, &output->node); 186 wl_signal_emit_mutable(&root->events.new_node, &output->node);
150 187
151 arrange_layers(output); 188 arrange_layers(output);
152 arrange_root(); 189 arrange_root();
@@ -192,7 +229,7 @@ static void output_evacuate(struct sway_output *output) {
192 new_output = fallback_output; 229 new_output = fallback_output;
193 } 230 }
194 if (!new_output) { 231 if (!new_output) {
195 new_output = root->noop_output; 232 new_output = root->fallback_output;
196 } 233 }
197 234
198 struct sway_workspace *new_output_ws = 235 struct sway_workspace *new_output_ws =
@@ -238,20 +275,14 @@ void output_destroy(struct sway_output *output) {
238 "which is still referenced by transactions")) { 275 "which is still referenced by transactions")) {
239 return; 276 return;
240 } 277 }
278
279 destroy_scene_layers(output);
241 list_free(output->workspaces); 280 list_free(output->workspaces);
242 list_free(output->current.workspaces); 281 list_free(output->current.workspaces);
243 wl_event_source_remove(output->repaint_timer); 282 wl_event_source_remove(output->repaint_timer);
244 free(output); 283 free(output);
245} 284}
246 285
247static void untrack_output(struct sway_container *con, void *data) {
248 struct sway_output *output = data;
249 int index = list_find(con->outputs, output);
250 if (index != -1) {
251 list_del(con->outputs, index);
252 }
253}
254
255void output_disable(struct sway_output *output) { 286void output_disable(struct sway_output *output) {
256 if (!sway_assert(output->enabled, "Expected an enabled output")) { 287 if (!sway_assert(output->enabled, "Expected an enabled output")) {
257 return; 288 return;
@@ -262,23 +293,20 @@ void output_disable(struct sway_output *output) {
262 } 293 }
263 294
264 sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name); 295 sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name);
265 wl_signal_emit(&output->events.destroy, output); 296 wl_signal_emit_mutable(&output->events.disable, output);
266 297
267 output_evacuate(output); 298 output_evacuate(output);
268 299
269 root_for_each_container(untrack_output, output);
270
271 list_del(root->outputs, index); 300 list_del(root->outputs, index);
272 301
273 output->enabled = false; 302 output->enabled = false;
274 output->current_mode = NULL;
275 303
276 arrange_root(); 304 arrange_root();
277 305
278 // Reconfigure all devices, since devices with map_to_output directives for 306 // Reconfigure all devices, since devices with map_to_output directives for
279 // an output that goes offline should stop sending events as long as the 307 // an output that goes offline should stop sending events as long as the
280 // output remains offline. 308 // output remains offline.
281 input_manager_configure_all_inputs(); 309 input_manager_configure_all_input_mappings();
282} 310}
283 311
284void output_begin_destroy(struct sway_output *output) { 312void output_begin_destroy(struct sway_output *output) {
@@ -286,13 +314,10 @@ void output_begin_destroy(struct sway_output *output) {
286 return; 314 return;
287 } 315 }
288 sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name); 316 sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name);
317 wl_signal_emit_mutable(&output->node.events.destroy, &output->node);
289 318
290 output->node.destroying = true; 319 output->node.destroying = true;
291 node_set_dirty(&output->node); 320 node_set_dirty(&output->node);
292
293 wl_list_remove(&output->link);
294 output->wlr_output->data = NULL;
295 output->wlr_output = NULL;
296} 321}
297 322
298struct sway_output *output_from_wlr_output(struct wlr_output *output) { 323struct sway_output *output_from_wlr_output(struct wlr_output *output) {
@@ -304,10 +329,10 @@ struct sway_output *output_get_in_direction(struct sway_output *reference,
304 if (!sway_assert(direction, "got invalid direction: %d", direction)) { 329 if (!sway_assert(direction, "got invalid direction: %d", direction)) {
305 return NULL; 330 return NULL;
306 } 331 }
307 struct wlr_box *output_box = 332 struct wlr_box output_box;
308 wlr_output_layout_get_box(root->output_layout, reference->wlr_output); 333 wlr_output_layout_get_box(root->output_layout, reference->wlr_output, &output_box);
309 int lx = output_box->x + output_box->width / 2; 334 int lx = output_box.x + output_box.width / 2;
310 int ly = output_box->y + output_box->height / 2; 335 int ly = output_box.y + output_box.height / 2;
311 struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output( 336 struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output(
312 root->output_layout, direction, reference->wlr_output, lx, ly); 337 root->output_layout, direction, reference->wlr_output, lx, ly);
313 if (!wlr_adjacent) { 338 if (!wlr_adjacent) {
@@ -393,6 +418,33 @@ void output_get_box(struct sway_output *output, struct wlr_box *box) {
393 box->height = output->height; 418 box->height = output->height;
394} 419}
395 420
421static void handle_destroy_non_desktop(struct wl_listener *listener, void *data) {
422 struct sway_output_non_desktop *output =
423 wl_container_of(listener, output, destroy);
424
425 sway_log(SWAY_DEBUG, "Destroying non-desktop output '%s'", output->wlr_output->name);
426
427 int index = list_find(root->non_desktop_outputs, output);
428 list_del(root->non_desktop_outputs, index);
429
430 wl_list_remove(&output->destroy.link);
431
432 free(output);
433}
434
435struct sway_output_non_desktop *output_non_desktop_create(
436 struct wlr_output *wlr_output) {
437 struct sway_output_non_desktop *output =
438 calloc(1, sizeof(struct sway_output_non_desktop));
439
440 output->wlr_output = wlr_output;
441
442 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
443 output->destroy.notify = handle_destroy_non_desktop;
444
445 return output;
446}
447
396enum sway_container_layout output_get_default_layout( 448enum sway_container_layout output_get_default_layout(
397 struct sway_output *output) { 449 struct sway_output *output) {
398 if (config->default_orientation != L_NONE) { 450 if (config->default_orientation != L_NONE) {
diff --git a/sway/tree/root.c b/sway/tree/root.c
index ebd185ec..e9cea5e2 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -3,10 +3,13 @@
3#include <stdlib.h> 3#include <stdlib.h>
4#include <string.h> 4#include <string.h>
5#include <wlr/types/wlr_output_layout.h> 5#include <wlr/types/wlr_output_layout.h>
6#include <wlr/types/wlr_scene.h>
7#include <wlr/util/transform.h>
6#include "sway/desktop/transaction.h" 8#include "sway/desktop/transaction.h"
7#include "sway/input/seat.h" 9#include "sway/input/seat.h"
8#include "sway/ipc-server.h" 10#include "sway/ipc-server.h"
9#include "sway/output.h" 11#include "sway/output.h"
12#include "sway/scene_descriptor.h"
10#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
11#include "sway/tree/container.h" 14#include "sway/tree/container.h"
12#include "sway/tree/root.h" 15#include "sway/tree/root.h"
@@ -23,21 +26,60 @@ static void output_layout_handle_change(struct wl_listener *listener,
23 transaction_commit_dirty(); 26 transaction_commit_dirty();
24} 27}
25 28
26struct sway_root *root_create(void) { 29struct sway_root *root_create(struct wl_display *wl_display) {
27 struct sway_root *root = calloc(1, sizeof(struct sway_root)); 30 struct sway_root *root = calloc(1, sizeof(struct sway_root));
28 if (!root) { 31 if (!root) {
29 sway_log(SWAY_ERROR, "Unable to allocate sway_root"); 32 sway_log(SWAY_ERROR, "Unable to allocate sway_root");
30 return NULL; 33 return NULL;
31 } 34 }
35
36 struct wlr_scene *root_scene = wlr_scene_create();
37 if (!root_scene) {
38 sway_log(SWAY_ERROR, "Unable to allocate root scene node");
39 free(root);
40 return NULL;
41 }
42
32 node_init(&root->node, N_ROOT, root); 43 node_init(&root->node, N_ROOT, root);
33 root->output_layout = wlr_output_layout_create(); 44 root->root_scene = root_scene;
34 wl_list_init(&root->all_outputs); 45
46 bool failed = false;
47 root->staging = alloc_scene_tree(&root_scene->tree, &failed);
48 root->layer_tree = alloc_scene_tree(&root_scene->tree, &failed);
49
50 root->layers.shell_background = alloc_scene_tree(root->layer_tree, &failed);
51 root->layers.shell_bottom = alloc_scene_tree(root->layer_tree, &failed);
52 root->layers.tiling = alloc_scene_tree(root->layer_tree, &failed);
53 root->layers.floating = alloc_scene_tree(root->layer_tree, &failed);
54 root->layers.shell_top = alloc_scene_tree(root->layer_tree, &failed);
55 root->layers.fullscreen = alloc_scene_tree(root->layer_tree, &failed);
56 root->layers.fullscreen_global = alloc_scene_tree(root->layer_tree, &failed);
35#if HAVE_XWAYLAND 57#if HAVE_XWAYLAND
36 wl_list_init(&root->xwayland_unmanaged); 58 root->layers.unmanaged = alloc_scene_tree(root->layer_tree, &failed);
37#endif 59#endif
38 wl_list_init(&root->drag_icons); 60 root->layers.shell_overlay = alloc_scene_tree(root->layer_tree, &failed);
61 root->layers.popup = alloc_scene_tree(root->layer_tree, &failed);
62 root->layers.seat = alloc_scene_tree(root->layer_tree, &failed);
63 root->layers.session_lock = alloc_scene_tree(root->layer_tree, &failed);
64
65 if (!failed && !scene_descriptor_assign(&root->layers.seat->node,
66 SWAY_SCENE_DESC_NON_INTERACTIVE, (void *)1)) {
67 failed = true;
68 }
69
70 if (failed) {
71 wlr_scene_node_destroy(&root_scene->tree.node);
72 free(root);
73 return NULL;
74 }
75
76 wlr_scene_node_set_enabled(&root->staging->node, false);
77
78 root->output_layout = wlr_output_layout_create(wl_display);
79 wl_list_init(&root->all_outputs);
39 wl_signal_init(&root->events.new_node); 80 wl_signal_init(&root->events.new_node);
40 root->outputs = create_list(); 81 root->outputs = create_list();
82 root->non_desktop_outputs = create_list();
41 root->scratchpad = create_list(); 83 root->scratchpad = create_list();
42 84
43 root->output_layout_change.notify = output_layout_handle_change; 85 root->output_layout_change.notify = output_layout_handle_change;
@@ -49,21 +91,34 @@ struct sway_root *root_create(void) {
49void root_destroy(struct sway_root *root) { 91void root_destroy(struct sway_root *root) {
50 wl_list_remove(&root->output_layout_change.link); 92 wl_list_remove(&root->output_layout_change.link);
51 list_free(root->scratchpad); 93 list_free(root->scratchpad);
94 list_free(root->non_desktop_outputs);
52 list_free(root->outputs); 95 list_free(root->outputs);
53 wlr_output_layout_destroy(root->output_layout); 96 wlr_scene_node_destroy(&root->root_scene->tree.node);
54 free(root); 97 free(root);
55} 98}
56 99
100static void set_container_transform(struct sway_workspace *ws,
101 struct sway_container *con) {
102 struct sway_output *output = ws->output;
103 struct wlr_box box = {0};
104 if (output) {
105 output_get_box(output, &box);
106 }
107 con->transform = box;
108}
109
57void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws) { 110void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws) {
58 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) { 111 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
59 return; 112 return;
60 } 113 }
61 114
62 struct sway_container *parent = con->parent; 115 struct sway_container *parent = con->pending.parent;
63 struct sway_workspace *workspace = con->workspace; 116 struct sway_workspace *workspace = con->pending.workspace;
117
118 set_container_transform(workspace, con);
64 119
65 // Clear the fullscreen mode when sending to the scratchpad 120 // Clear the fullscreen mode when sending to the scratchpad
66 if (con->fullscreen_mode != FULLSCREEN_NONE) { 121 if (con->pending.fullscreen_mode != FULLSCREEN_NONE) {
67 container_fullscreen_disable(con); 122 container_fullscreen_disable(con);
68 } 123 }
69 124
@@ -117,7 +172,7 @@ void root_scratchpad_show(struct sway_container *con) {
117 sway_log(SWAY_DEBUG, "No focused workspace to show scratchpad on"); 172 sway_log(SWAY_DEBUG, "No focused workspace to show scratchpad on");
118 return; 173 return;
119 } 174 }
120 struct sway_workspace *old_ws = con->workspace; 175 struct sway_workspace *old_ws = con->pending.workspace;
121 176
122 // If the current con or any of its parents are in fullscreen mode, we 177 // If the current con or any of its parents are in fullscreen mode, we
123 // first need to disable it before showing the scratchpad con. 178 // first need to disable it before showing the scratchpad con.
@@ -131,31 +186,34 @@ void root_scratchpad_show(struct sway_container *con) {
131 // Show the container 186 // Show the container
132 if (old_ws) { 187 if (old_ws) {
133 container_detach(con); 188 container_detach(con);
134 workspace_consider_destroy(old_ws); 189 // Make sure the last inactive container on the old workspace is above
190 // the workspace itself in the focus stack.
191 struct sway_node *node = seat_get_focus_inactive(seat, &old_ws->node);
192 seat_set_raw_focus(seat, node);
135 } else { 193 } else {
136 // Act on the ancestor of scratchpad hidden split containers 194 // Act on the ancestor of scratchpad hidden split containers
137 while (con->parent) { 195 while (con->pending.parent) {
138 con = con->parent; 196 con = con->pending.parent;
139 } 197 }
140 } 198 }
141 workspace_add_floating(new_ws, con); 199 workspace_add_floating(new_ws, con);
142 200
143 // Make sure the container's center point overlaps this workspace 201 if (new_ws->output) {
144 double center_lx = con->x + con->width / 2; 202 struct wlr_box output_box;
145 double center_ly = con->y + con->height / 2; 203 output_get_box(new_ws->output, &output_box);
146 204 floating_fix_coordinates(con, &con->transform, &output_box);
147 struct wlr_box workspace_box;
148 workspace_get_box(new_ws, &workspace_box);
149 if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) {
150 container_floating_resize_and_center(con);
151 } 205 }
206 set_container_transform(new_ws, con);
152 207
153 arrange_workspace(new_ws); 208 arrange_workspace(new_ws);
154 seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node)); 209 seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node));
210 if (old_ws) {
211 workspace_consider_destroy(old_ws);
212 }
155} 213}
156 214
157static void disable_fullscreen(struct sway_container *con, void *data) { 215static void disable_fullscreen(struct sway_container *con, void *data) {
158 if (con->fullscreen_mode != FULLSCREEN_NONE) { 216 if (con->pending.fullscreen_mode != FULLSCREEN_NONE) {
159 container_fullscreen_disable(con); 217 container_fullscreen_disable(con);
160 } 218 }
161} 219}
@@ -163,14 +221,16 @@ static void disable_fullscreen(struct sway_container *con, void *data) {
163void root_scratchpad_hide(struct sway_container *con) { 221void root_scratchpad_hide(struct sway_container *con) {
164 struct sway_seat *seat = input_manager_current_seat(); 222 struct sway_seat *seat = input_manager_current_seat();
165 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); 223 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node);
166 struct sway_workspace *ws = con->workspace; 224 struct sway_workspace *ws = con->pending.workspace;
167 225
168 if (con->fullscreen_mode == FULLSCREEN_GLOBAL && !con->workspace) { 226 if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL && !con->pending.workspace) {
169 // If the container was made fullscreen global while in the scratchpad, 227 // If the container was made fullscreen global while in the scratchpad,
170 // it should be shown until fullscreen has been disabled 228 // it should be shown until fullscreen has been disabled
171 return; 229 return;
172 } 230 }
173 231
232 set_container_transform(con->pending.workspace, con);
233
174 disable_fullscreen(con, NULL); 234 disable_fullscreen(con, NULL);
175 container_for_each_child(con, disable_fullscreen, NULL); 235 container_for_each_child(con, disable_fullscreen, NULL);
176 container_detach(con); 236 container_detach(con);
@@ -183,163 +243,6 @@ void root_scratchpad_hide(struct sway_container *con) {
183 ipc_event_window(con, "move"); 243 ipc_event_window(con, "move");
184} 244}
185 245
186struct pid_workspace {
187 pid_t pid;
188 char *workspace;
189 struct timespec time_added;
190
191 struct sway_output *output;
192 struct wl_listener output_destroy;
193
194 struct wl_list link;
195};
196
197static struct wl_list pid_workspaces;
198
199/**
200 * Get the pid of a parent process given the pid of a child process.
201 *
202 * Returns the parent pid or NULL if the parent pid cannot be determined.
203 */
204static pid_t get_parent_pid(pid_t child) {
205 pid_t parent = -1;
206 char file_name[100];
207 char *buffer = NULL;
208 const char *sep = " ";
209 FILE *stat = NULL;
210 size_t buf_size = 0;
211
212 sprintf(file_name, "/proc/%d/stat", child);
213
214 if ((stat = fopen(file_name, "r"))) {
215 if (getline(&buffer, &buf_size, stat) != -1) {
216 strtok(buffer, sep); // pid
217 strtok(NULL, sep); // executable name
218 strtok(NULL, sep); // state
219 char *token = strtok(NULL, sep); // parent pid
220 parent = strtol(token, NULL, 10);
221 }
222 free(buffer);
223 fclose(stat);
224 }
225
226 if (parent) {
227 return (parent == child) ? -1 : parent;
228 }
229
230 return -1;
231}
232
233static void pid_workspace_destroy(struct pid_workspace *pw) {
234 wl_list_remove(&pw->output_destroy.link);
235 wl_list_remove(&pw->link);
236 free(pw->workspace);
237 free(pw);
238}
239
240struct sway_workspace *root_workspace_for_pid(pid_t pid) {
241 if (!pid_workspaces.prev && !pid_workspaces.next) {
242 wl_list_init(&pid_workspaces);
243 return NULL;
244 }
245
246 struct sway_workspace *ws = NULL;
247 struct pid_workspace *pw = NULL;
248
249 sway_log(SWAY_DEBUG, "Looking up workspace for pid %d", pid);
250
251 do {
252 struct pid_workspace *_pw = NULL;
253 wl_list_for_each(_pw, &pid_workspaces, link) {
254 if (pid == _pw->pid) {
255 pw = _pw;
256 sway_log(SWAY_DEBUG,
257 "found pid_workspace for pid %d, workspace %s",
258 pid, pw->workspace);
259 goto found;
260 }
261 }
262 pid = get_parent_pid(pid);
263 } while (pid > 1);
264found:
265
266 if (pw && pw->workspace) {
267 ws = workspace_by_name(pw->workspace);
268
269 if (!ws) {
270 sway_log(SWAY_DEBUG,
271 "Creating workspace %s for pid %d because it disappeared",
272 pw->workspace, pid);
273 ws = workspace_create(pw->output, pw->workspace);
274 }
275
276 pid_workspace_destroy(pw);
277 }
278
279 return ws;
280}
281
282static void pw_handle_output_destroy(struct wl_listener *listener, void *data) {
283 struct pid_workspace *pw = wl_container_of(listener, pw, output_destroy);
284 pw->output = NULL;
285 wl_list_remove(&pw->output_destroy.link);
286 wl_list_init(&pw->output_destroy.link);
287}
288
289void root_record_workspace_pid(pid_t pid) {
290 sway_log(SWAY_DEBUG, "Recording workspace for process %d", pid);
291 if (!pid_workspaces.prev && !pid_workspaces.next) {
292 wl_list_init(&pid_workspaces);
293 }
294
295 struct sway_seat *seat = input_manager_current_seat();
296 struct sway_workspace *ws = seat_get_focused_workspace(seat);
297 if (!ws) {
298 sway_log(SWAY_DEBUG, "Bailing out, no workspace");
299 return;
300 }
301 struct sway_output *output = ws->output;
302 if (!output) {
303 sway_log(SWAY_DEBUG, "Bailing out, no output");
304 return;
305 }
306
307 struct timespec now;
308 clock_gettime(CLOCK_MONOTONIC, &now);
309
310 // Remove expired entries
311 static const int timeout = 60;
312 struct pid_workspace *old, *_old;
313 wl_list_for_each_safe(old, _old, &pid_workspaces, link) {
314 if (now.tv_sec - old->time_added.tv_sec >= timeout) {
315 pid_workspace_destroy(old);
316 }
317 }
318
319 struct pid_workspace *pw = calloc(1, sizeof(struct pid_workspace));
320 pw->workspace = strdup(ws->name);
321 pw->output = output;
322 pw->pid = pid;
323 memcpy(&pw->time_added, &now, sizeof(struct timespec));
324 pw->output_destroy.notify = pw_handle_output_destroy;
325 wl_signal_add(&output->wlr_output->events.destroy, &pw->output_destroy);
326 wl_list_insert(&pid_workspaces, &pw->link);
327}
328
329void root_remove_workspace_pid(pid_t pid) {
330 if (!pid_workspaces.prev || !pid_workspaces.next) {
331 return;
332 }
333
334 struct pid_workspace *pw, *tmp;
335 wl_list_for_each_safe(pw, tmp, &pid_workspaces, link) {
336 if (pid == pw->pid) {
337 pid_workspace_destroy(pw);
338 return;
339 }
340 }
341}
342
343void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data), 246void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data),
344 void *data) { 247 void *data) {
345 for (int i = 0; i < root->outputs->length; ++i) { 248 for (int i = 0; i < root->outputs->length; ++i) {
@@ -365,8 +268,8 @@ void root_for_each_container(void (*f)(struct sway_container *con, void *data),
365 } 268 }
366 269
367 // Saved workspaces 270 // Saved workspaces
368 for (int i = 0; i < root->noop_output->workspaces->length; ++i) { 271 for (int i = 0; i < root->fallback_output->workspaces->length; ++i) {
369 struct sway_workspace *ws = root->noop_output->workspaces->items[i]; 272 struct sway_workspace *ws = root->fallback_output->workspaces->items[i];
370 workspace_for_each_container(ws, f, data); 273 workspace_for_each_container(ws, f, data);
371 } 274 }
372} 275}
@@ -418,8 +321,8 @@ struct sway_container *root_find_container(
418 } 321 }
419 322
420 // Saved workspaces 323 // Saved workspaces
421 for (int i = 0; i < root->noop_output->workspaces->length; ++i) { 324 for (int i = 0; i < root->fallback_output->workspaces->length; ++i) {
422 struct sway_workspace *ws = root->noop_output->workspaces->items[i]; 325 struct sway_workspace *ws = root->fallback_output->workspaces->items[i];
423 if ((result = workspace_find_container(ws, test, data))) { 326 if ((result = workspace_find_container(ws, test, data))) {
424 return result; 327 return result;
425 } 328 }
@@ -434,17 +337,3 @@ void root_get_box(struct sway_root *root, struct wlr_box *box) {
434 box->width = root->width; 337 box->width = root->width;
435 box->height = root->height; 338 box->height = root->height;
436} 339}
437
438void root_rename_pid_workspaces(const char *old_name, const char *new_name) {
439 if (!pid_workspaces.prev && !pid_workspaces.next) {
440 wl_list_init(&pid_workspaces);
441 }
442
443 struct pid_workspace *pw = NULL;
444 wl_list_for_each(pw, &pid_workspaces, link) {
445 if (strcmp(pw->workspace, old_name) == 0) {
446 free(pw->workspace);
447 pw->workspace = strdup(new_name);
448 }
449 }
450}
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 7afcdf31..d6984178 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -6,6 +6,7 @@
6#include <wlr/types/wlr_buffer.h> 6#include <wlr/types/wlr_buffer.h>
7#include <wlr/types/wlr_output_layout.h> 7#include <wlr/types/wlr_output_layout.h>
8#include <wlr/types/wlr_server_decoration.h> 8#include <wlr/types/wlr_server_decoration.h>
9#include <wlr/types/wlr_subcompositor.h>
9#include <wlr/types/wlr_xdg_decoration_v1.h> 10#include <wlr/types/wlr_xdg_decoration_v1.h>
10#include "config.h" 11#include "config.h"
11#if HAVE_XWAYLAND 12#if HAVE_XWAYLAND
@@ -15,14 +16,16 @@
15#include "log.h" 16#include "log.h"
16#include "sway/criteria.h" 17#include "sway/criteria.h"
17#include "sway/commands.h" 18#include "sway/commands.h"
18#include "sway/desktop.h"
19#include "sway/desktop/transaction.h" 19#include "sway/desktop/transaction.h"
20#include "sway/desktop/idle_inhibit_v1.h" 20#include "sway/desktop/idle_inhibit_v1.h"
21#include "sway/desktop/launcher.h"
21#include "sway/input/cursor.h" 22#include "sway/input/cursor.h"
22#include "sway/ipc-server.h" 23#include "sway/ipc-server.h"
23#include "sway/output.h" 24#include "sway/output.h"
24#include "sway/input/seat.h" 25#include "sway/input/seat.h"
26#include "sway/scene_descriptor.h"
25#include "sway/server.h" 27#include "sway/server.h"
28#include "sway/sway_text_node.h"
26#include "sway/tree/arrange.h" 29#include "sway/tree/arrange.h"
27#include "sway/tree/container.h" 30#include "sway/tree/container.h"
28#include "sway/tree/view.h" 31#include "sway/tree/view.h"
@@ -32,15 +35,29 @@
32#include "pango.h" 35#include "pango.h"
33#include "stringop.h" 36#include "stringop.h"
34 37
35void view_init(struct sway_view *view, enum sway_view_type type, 38bool view_init(struct sway_view *view, enum sway_view_type type,
36 const struct sway_view_impl *impl) { 39 const struct sway_view_impl *impl) {
40 bool failed = false;
41 view->scene_tree = alloc_scene_tree(root->staging, &failed);
42 view->content_tree = alloc_scene_tree(view->scene_tree, &failed);
43
44 if (!failed && !scene_descriptor_assign(&view->scene_tree->node,
45 SWAY_SCENE_DESC_VIEW, view)) {
46 failed = true;
47 }
48
49 if (failed) {
50 wlr_scene_node_destroy(&view->scene_tree->node);
51 return false;
52 }
53
37 view->type = type; 54 view->type = type;
38 view->impl = impl; 55 view->impl = impl;
39 view->executed_criteria = create_list(); 56 view->executed_criteria = create_list();
40 wl_list_init(&view->saved_buffers);
41 view->allow_request_urgent = true; 57 view->allow_request_urgent = true;
42 view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT; 58 view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT;
43 wl_signal_init(&view->events.unmap); 59 wl_signal_init(&view->events.unmap);
60 return true;
44} 61}
45 62
46void view_destroy(struct sway_view *view) { 63void view_destroy(struct sway_view *view) {
@@ -56,11 +73,11 @@ void view_destroy(struct sway_view *view) {
56 "(might have a pending transaction?)")) { 73 "(might have a pending transaction?)")) {
57 return; 74 return;
58 } 75 }
59 if (!wl_list_empty(&view->saved_buffers)) { 76 wl_list_remove(&view->events.unmap.listener_list);
60 view_remove_saved_buffer(view);
61 }
62 list_free(view->executed_criteria); 77 list_free(view->executed_criteria);
63 78
79 view_assign_ctx(view, NULL);
80 wlr_scene_node_destroy(&view->scene_tree->node);
64 free(view->title_format); 81 free(view->title_format);
65 82
66 if (view->impl->destroy) { 83 if (view->impl->destroy) {
@@ -206,7 +223,7 @@ bool view_ancestor_is_only_visible(struct sway_view *view) {
206 } else { 223 } else {
207 only_visible = true; 224 only_visible = true;
208 } 225 }
209 con = con->parent; 226 con = con->pending.parent;
210 } 227 }
211 return only_visible; 228 return only_visible;
212} 229}
@@ -222,72 +239,73 @@ static bool view_is_only_visible(struct sway_view *view) {
222 } 239 }
223 } 240 }
224 241
225 con = con->parent; 242 con = con->pending.parent;
226 } 243 }
227 244
228 return true; 245 return true;
229} 246}
230 247
231static bool gaps_to_edge(struct sway_view *view) { 248static bool gaps_to_edge(struct sway_view *view) {
232 struct side_gaps gaps = view->container->workspace->current_gaps; 249 struct side_gaps gaps = view->container->pending.workspace->current_gaps;
233 return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0; 250 return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0;
234} 251}
235 252
236void view_autoconfigure(struct sway_view *view) { 253void view_autoconfigure(struct sway_view *view) {
237 struct sway_container *con = view->container; 254 struct sway_container *con = view->container;
238 struct sway_workspace *ws = con->workspace; 255 struct sway_workspace *ws = con->pending.workspace;
239 256
240 if (container_is_scratchpad_hidden(con) && 257 if (container_is_scratchpad_hidden(con) &&
241 con->fullscreen_mode != FULLSCREEN_GLOBAL) { 258 con->pending.fullscreen_mode != FULLSCREEN_GLOBAL) {
242 return; 259 return;
243 } 260 }
244 struct sway_output *output = ws ? ws->output : NULL; 261 struct sway_output *output = ws ? ws->output : NULL;
245 262
246 if (con->fullscreen_mode == FULLSCREEN_WORKSPACE) { 263 if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
247 con->content_x = output->lx; 264 con->pending.content_x = output->lx;
248 con->content_y = output->ly; 265 con->pending.content_y = output->ly;
249 con->content_width = output->width; 266 con->pending.content_width = output->width;
250 con->content_height = output->height; 267 con->pending.content_height = output->height;
251 return; 268 return;
252 } else if (con->fullscreen_mode == FULLSCREEN_GLOBAL) { 269 } else if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
253 con->content_x = root->x; 270 con->pending.content_x = root->x;
254 con->content_y = root->y; 271 con->pending.content_y = root->y;
255 con->content_width = root->width; 272 con->pending.content_width = root->width;
256 con->content_height = root->height; 273 con->pending.content_height = root->height;
257 return; 274 return;
258 } 275 }
259 276
260 con->border_top = con->border_bottom = true; 277 con->pending.border_top = con->pending.border_bottom = true;
261 con->border_left = con->border_right = true; 278 con->pending.border_left = con->pending.border_right = true;
262 double y_offset = 0; 279 double y_offset = 0;
263 280
264 if (!container_is_floating(con) && ws) { 281 if (!container_is_floating_or_child(con) && ws) {
265 if (config->hide_edge_borders == E_BOTH 282 if (config->hide_edge_borders == E_BOTH
266 || config->hide_edge_borders == E_VERTICAL) { 283 || config->hide_edge_borders == E_VERTICAL) {
267 con->border_left = con->x != ws->x; 284 con->pending.border_left = con->pending.x != ws->x;
268 int right_x = con->x + con->width; 285 int right_x = con->pending.x + con->pending.width;
269 con->border_right = right_x != ws->x + ws->width; 286 con->pending.border_right = right_x != ws->x + ws->width;
270 } 287 }
271 288
272 if (config->hide_edge_borders == E_BOTH 289 if (config->hide_edge_borders == E_BOTH
273 || config->hide_edge_borders == E_HORIZONTAL) { 290 || config->hide_edge_borders == E_HORIZONTAL) {
274 con->border_top = con->y != ws->y; 291 con->pending.border_top = con->pending.y != ws->y;
275 int bottom_y = con->y + con->height; 292 int bottom_y = con->pending.y + con->pending.height;
276 con->border_bottom = bottom_y != ws->y + ws->height; 293 con->pending.border_bottom = bottom_y != ws->y + ws->height;
277 } 294 }
278 295
279 bool smart = config->hide_edge_borders_smart == ESMART_ON || 296 bool smart = config->hide_edge_borders_smart == ESMART_ON ||
280 (config->hide_edge_borders_smart == ESMART_NO_GAPS && 297 (config->hide_edge_borders_smart == ESMART_NO_GAPS &&
281 !gaps_to_edge(view)); 298 !gaps_to_edge(view));
282 if (smart) { 299 if (smart) {
283 bool show_border = container_is_floating_or_child(con) || 300 bool show_border = !view_is_only_visible(view);
284 !view_is_only_visible(view); 301 con->pending.border_left &= show_border;
285 con->border_left &= show_border; 302 con->pending.border_right &= show_border;
286 con->border_right &= show_border; 303 con->pending.border_top &= show_border;
287 con->border_top &= show_border; 304 con->pending.border_bottom &= show_border;
288 con->border_bottom &= show_border;
289 } 305 }
306 }
290 307
308 if (!container_is_floating(con)) {
291 // In a tabbed or stacked container, the container's y is the top of the 309 // In a tabbed or stacked container, the container's y is the top of the
292 // title area. We have to offset the surface y by the height of the title, 310 // title area. We have to offset the surface y by the height of the title,
293 // bar, and disable any top border because we'll always have the title bar. 311 // bar, and disable any top border because we'll always have the title bar.
@@ -298,56 +316,56 @@ void view_autoconfigure(struct sway_view *view) {
298 enum sway_container_layout layout = container_parent_layout(con); 316 enum sway_container_layout layout = container_parent_layout(con);
299 if (layout == L_TABBED) { 317 if (layout == L_TABBED) {
300 y_offset = container_titlebar_height(); 318 y_offset = container_titlebar_height();
301 con->border_top = false; 319 con->pending.border_top = false;
302 } else if (layout == L_STACKED) { 320 } else if (layout == L_STACKED) {
303 y_offset = container_titlebar_height() * siblings->length; 321 y_offset = container_titlebar_height() * siblings->length;
304 con->border_top = false; 322 con->pending.border_top = false;
305 } 323 }
306 } 324 }
307 } 325 }
308 326
309 double x, y, width, height; 327 double x, y, width, height;
310 switch (con->border) { 328 switch (con->pending.border) {
311 default: 329 default:
312 case B_CSD: 330 case B_CSD:
313 case B_NONE: 331 case B_NONE:
314 x = con->x; 332 x = con->pending.x;
315 y = con->y + y_offset; 333 y = con->pending.y + y_offset;
316 width = con->width; 334 width = con->pending.width;
317 height = con->height - y_offset; 335 height = con->pending.height - y_offset;
318 break; 336 break;
319 case B_PIXEL: 337 case B_PIXEL:
320 x = con->x + con->border_thickness * con->border_left; 338 x = con->pending.x + con->pending.border_thickness * con->pending.border_left;
321 y = con->y + con->border_thickness * con->border_top + y_offset; 339 y = con->pending.y + con->pending.border_thickness * con->pending.border_top + y_offset;
322 width = con->width 340 width = con->pending.width
323 - con->border_thickness * con->border_left 341 - con->pending.border_thickness * con->pending.border_left
324 - con->border_thickness * con->border_right; 342 - con->pending.border_thickness * con->pending.border_right;
325 height = con->height - y_offset 343 height = con->pending.height - y_offset
326 - con->border_thickness * con->border_top 344 - con->pending.border_thickness * con->pending.border_top
327 - con->border_thickness * con->border_bottom; 345 - con->pending.border_thickness * con->pending.border_bottom;
328 break; 346 break;
329 case B_NORMAL: 347 case B_NORMAL:
330 // Height is: 1px border + 3px pad + title height + 3px pad + 1px border 348 // Height is: 1px border + 3px pad + title height + 3px pad + 1px border
331 x = con->x + con->border_thickness * con->border_left; 349 x = con->pending.x + con->pending.border_thickness * con->pending.border_left;
332 width = con->width 350 width = con->pending.width
333 - con->border_thickness * con->border_left 351 - con->pending.border_thickness * con->pending.border_left
334 - con->border_thickness * con->border_right; 352 - con->pending.border_thickness * con->pending.border_right;
335 if (y_offset) { 353 if (y_offset) {
336 y = con->y + y_offset; 354 y = con->pending.y + y_offset;
337 height = con->height - y_offset 355 height = con->pending.height - y_offset
338 - con->border_thickness * con->border_bottom; 356 - con->pending.border_thickness * con->pending.border_bottom;
339 } else { 357 } else {
340 y = con->y + container_titlebar_height(); 358 y = con->pending.y + container_titlebar_height();
341 height = con->height - container_titlebar_height() 359 height = con->pending.height - container_titlebar_height()
342 - con->border_thickness * con->border_bottom; 360 - con->pending.border_thickness * con->pending.border_bottom;
343 } 361 }
344 break; 362 break;
345 } 363 }
346 364
347 con->content_x = x; 365 con->pending.content_x = x;
348 con->content_y = y; 366 con->pending.content_y = y;
349 con->content_width = width; 367 con->pending.content_width = width;
350 con->content_height = height; 368 con->pending.content_height = height;
351} 369}
352 370
353void view_set_activated(struct sway_view *view, bool activated) { 371void view_set_activated(struct sway_view *view, bool activated) {
@@ -360,17 +378,17 @@ void view_set_activated(struct sway_view *view, bool activated) {
360 } 378 }
361} 379}
362 380
363void view_request_activate(struct sway_view *view) { 381void view_request_activate(struct sway_view *view, struct sway_seat *seat) {
364 struct sway_workspace *ws = view->container->workspace; 382 struct sway_workspace *ws = view->container->pending.workspace;
365 if (!ws) { // hidden scratchpad container 383 if (!seat) {
366 return; 384 seat = input_manager_current_seat();
367 } 385 }
368 struct sway_seat *seat = input_manager_current_seat();
369 386
370 switch (config->focus_on_window_activation) { 387 switch (config->focus_on_window_activation) {
371 case FOWA_SMART: 388 case FOWA_SMART:
372 if (workspace_is_visible(ws)) { 389 if (ws && workspace_is_visible(ws)) {
373 seat_set_focus_container(seat, view->container); 390 seat_set_focus_container(seat, view->container);
391 container_raise_floating(view->container);
374 } else { 392 } else {
375 view_set_urgent(view, true); 393 view_set_urgent(view, true);
376 } 394 }
@@ -379,11 +397,17 @@ void view_request_activate(struct sway_view *view) {
379 view_set_urgent(view, true); 397 view_set_urgent(view, true);
380 break; 398 break;
381 case FOWA_FOCUS: 399 case FOWA_FOCUS:
382 seat_set_focus_container(seat, view->container); 400 if (container_is_scratchpad_hidden_or_child(view->container)) {
401 root_scratchpad_show(view->container);
402 } else {
403 seat_set_focus_container(seat, view->container);
404 container_raise_floating(view->container);
405 }
383 break; 406 break;
384 case FOWA_NONE: 407 case FOWA_NONE:
385 break; 408 break;
386 } 409 }
410 transaction_commit_dirty();
387} 411}
388 412
389void view_set_csd_from_server(struct sway_view *view, bool enabled) { 413void view_set_csd_from_server(struct sway_view *view, bool enabled) {
@@ -401,13 +425,13 @@ void view_set_csd_from_server(struct sway_view *view, bool enabled) {
401void view_update_csd_from_client(struct sway_view *view, bool enabled) { 425void view_update_csd_from_client(struct sway_view *view, bool enabled) {
402 sway_log(SWAY_DEBUG, "View %p updated CSD to %i", view, enabled); 426 sway_log(SWAY_DEBUG, "View %p updated CSD to %i", view, enabled);
403 struct sway_container *con = view->container; 427 struct sway_container *con = view->container;
404 if (enabled && con && con->border != B_CSD) { 428 if (enabled && con && con->pending.border != B_CSD) {
405 con->saved_border = con->border; 429 con->saved_border = con->pending.border;
406 if (container_is_floating(con)) { 430 if (container_is_floating(con)) {
407 con->border = B_CSD; 431 con->pending.border = B_CSD;
408 } 432 }
409 } else if (!enabled && con && con->border == B_CSD) { 433 } else if (!enabled && con && con->pending.border == B_CSD) {
410 con->border = con->saved_border; 434 con->pending.border = con->saved_border;
411 } 435 }
412 view->using_csd = enabled; 436 view->using_csd = enabled;
413} 437}
@@ -430,49 +454,6 @@ void view_close_popups(struct sway_view *view) {
430 } 454 }
431} 455}
432 456
433void view_damage_from(struct sway_view *view) {
434 for (int i = 0; i < root->outputs->length; ++i) {
435 struct sway_output *output = root->outputs->items[i];
436 output_damage_from_view(output, view);
437 }
438}
439
440void view_for_each_surface(struct sway_view *view,
441 wlr_surface_iterator_func_t iterator, void *user_data) {
442 if (!view->surface) {
443 return;
444 }
445 if (view->impl->for_each_surface) {
446 view->impl->for_each_surface(view, iterator, user_data);
447 } else {
448 wlr_surface_for_each_surface(view->surface, iterator, user_data);
449 }
450}
451
452void view_for_each_popup_surface(struct sway_view *view,
453 wlr_surface_iterator_func_t iterator, void *user_data) {
454 if (!view->surface) {
455 return;
456 }
457 if (view->impl->for_each_popup_surface) {
458 view->impl->for_each_popup_surface(view, iterator, user_data);
459 }
460}
461
462static void view_subsurface_create(struct sway_view *view,
463 struct wlr_subsurface *subsurface);
464
465static void view_init_subsurfaces(struct sway_view *view,
466 struct wlr_surface *surface);
467
468static void view_handle_surface_new_subsurface(struct wl_listener *listener,
469 void *data) {
470 struct sway_view *view =
471 wl_container_of(listener, view, surface_new_subsurface);
472 struct wlr_subsurface *subsurface = data;
473 view_subsurface_create(view, subsurface);
474}
475
476static bool view_has_executed_criteria(struct sway_view *view, 457static bool view_has_executed_criteria(struct sway_view *view,
477 struct criteria *criteria) { 458 struct criteria *criteria) {
478 for (int i = 0; i < view->executed_criteria->length; ++i) { 459 for (int i = 0; i < view->executed_criteria->length; ++i) {
@@ -514,7 +495,7 @@ static void view_populate_pid(struct sway_view *view) {
514#if HAVE_XWAYLAND 495#if HAVE_XWAYLAND
515 case SWAY_VIEW_XWAYLAND:; 496 case SWAY_VIEW_XWAYLAND:;
516 struct wlr_xwayland_surface *surf = 497 struct wlr_xwayland_surface *surf =
517 wlr_xwayland_surface_from_wlr_surface(view->surface); 498 wlr_xwayland_surface_try_from_wlr_surface(view->surface);
518 pid = surf->pid; 499 pid = surf->pid;
519 break; 500 break;
520#endif 501#endif
@@ -527,6 +508,20 @@ static void view_populate_pid(struct sway_view *view) {
527 view->pid = pid; 508 view->pid = pid;
528} 509}
529 510
511void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx) {
512 if (view->ctx) {
513 // This ctx has been replaced
514 launcher_ctx_destroy(view->ctx);
515 view->ctx = NULL;
516 }
517 if (ctx == NULL) {
518 return;
519 }
520 launcher_ctx_consume(ctx);
521
522 view->ctx = ctx;
523}
524
530static struct sway_workspace *select_workspace(struct sway_view *view) { 525static struct sway_workspace *select_workspace(struct sway_view *view) {
531 struct sway_seat *seat = input_manager_current_seat(); 526 struct sway_seat *seat = input_manager_current_seat();
532 527
@@ -562,13 +557,14 @@ static struct sway_workspace *select_workspace(struct sway_view *view) {
562 } 557 }
563 list_free(criterias); 558 list_free(criterias);
564 if (ws) { 559 if (ws) {
565 root_remove_workspace_pid(view->pid); 560 view_assign_ctx(view, NULL);
566 return ws; 561 return ws;
567 } 562 }
568 563
569 // Check if there's a PID mapping 564 // Check if there's a PID mapping
570 ws = root_workspace_for_pid(view->pid); 565 ws = view->ctx ? launcher_ctx_get_workspace(view->ctx) : NULL;
571 if (ws) { 566 if (ws) {
567 view_assign_ctx(view, NULL);
572 return ws; 568 return ws;
573 } 569 }
574 570
@@ -577,7 +573,7 @@ static struct sway_workspace *select_workspace(struct sway_view *view) {
577 if (node && node->type == N_WORKSPACE) { 573 if (node && node->type == N_WORKSPACE) {
578 return node->sway_workspace; 574 return node->sway_workspace;
579 } else if (node && node->type == N_CONTAINER) { 575 } else if (node && node->type == N_CONTAINER) {
580 return node->sway_container->workspace; 576 return node->sway_container->pending.workspace;
581 } 577 }
582 578
583 // When there's no outputs connected, the above should match a workspace on 579 // When there's no outputs connected, the above should match a workspace on
@@ -590,12 +586,17 @@ static bool should_focus(struct sway_view *view) {
590 struct sway_seat *seat = input_manager_current_seat(); 586 struct sway_seat *seat = input_manager_current_seat();
591 struct sway_container *prev_con = seat_get_focused_container(seat); 587 struct sway_container *prev_con = seat_get_focused_container(seat);
592 struct sway_workspace *prev_ws = seat_get_focused_workspace(seat); 588 struct sway_workspace *prev_ws = seat_get_focused_workspace(seat);
593 struct sway_workspace *map_ws = view->container->workspace; 589 struct sway_workspace *map_ws = view->container->pending.workspace;
594 590
595 if (view->container->fullscreen_mode == FULLSCREEN_GLOBAL) { 591 if (view->container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
596 return true; 592 return true;
597 } 593 }
598 594
595 // View opened "under" fullscreen view should not be given focus.
596 if (root->fullscreen_global || !map_ws || map_ws->fullscreen) {
597 return false;
598 }
599
599 // Views can only take focus if they are mapped into the active workspace 600 // Views can only take focus if they are mapped into the active workspace
600 if (prev_ws != map_ws) { 601 if (prev_ws != map_ws) {
601 return false; 602 return false;
@@ -603,9 +604,9 @@ static bool should_focus(struct sway_view *view) {
603 604
604 // If the view is the only one in the focused workspace, it'll get focus 605 // If the view is the only one in the focused workspace, it'll get focus
605 // regardless of any no_focus criteria. 606 // regardless of any no_focus criteria.
606 if (!view->container->parent && !prev_con) { 607 if (!view->container->pending.parent && !prev_con) {
607 size_t num_children = view->container->workspace->tiling->length + 608 size_t num_children = view->container->pending.workspace->tiling->length +
608 view->container->workspace->floating->length; 609 view->container->pending.workspace->floating->length;
609 if (num_children == 1) { 610 if (num_children == 1) {
610 return true; 611 return true;
611 } 612 }
@@ -635,6 +636,7 @@ static void handle_foreign_activate_request(
635 break; 636 break;
636 } 637 }
637 } 638 }
639 transaction_commit_dirty();
638} 640}
639 641
640static void handle_foreign_fullscreen_request( 642static void handle_foreign_fullscreen_request(
@@ -645,9 +647,21 @@ static void handle_foreign_fullscreen_request(
645 647
646 // Match fullscreen command behavior for scratchpad hidden views 648 // Match fullscreen command behavior for scratchpad hidden views
647 struct sway_container *container = view->container; 649 struct sway_container *container = view->container;
648 if (!container->workspace) { 650 if (!container->pending.workspace) {
649 while (container->parent) { 651 while (container->pending.parent) {
650 container = container->parent; 652 container = container->pending.parent;
653 }
654 }
655
656 if (event->fullscreen && event->output && event->output->data) {
657 struct sway_output *output = event->output->data;
658 struct sway_workspace *ws = output_get_active_workspace(output);
659 if (ws && !container_is_scratchpad_hidden(view->container)) {
660 if (container_is_floating(view->container)) {
661 workspace_add_floating(ws, view->container);
662 } else {
663 workspace_add_tiling(ws, view->container);
664 }
651 } 665 }
652 } 666 }
653 667
@@ -656,12 +670,13 @@ static void handle_foreign_fullscreen_request(
656 if (event->fullscreen) { 670 if (event->fullscreen) {
657 arrange_root(); 671 arrange_root();
658 } else { 672 } else {
659 if (container->parent) { 673 if (container->pending.parent) {
660 arrange_container(container->parent); 674 arrange_container(container->pending.parent);
661 } else if (container->workspace) { 675 } else if (container->pending.workspace) {
662 arrange_workspace(container->workspace); 676 arrange_workspace(container->pending.workspace);
663 } 677 }
664 } 678 }
679 transaction_commit_dirty();
665} 680}
666 681
667static void handle_foreign_close_request( 682static void handle_foreign_close_request(
@@ -692,6 +707,13 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
692 view_populate_pid(view); 707 view_populate_pid(view);
693 view->container = container_create(view); 708 view->container = container_create(view);
694 709
710 if (view->ctx == NULL) {
711 struct launcher_ctx *ctx = launcher_ctx_find_pid(view->pid);
712 if (ctx != NULL) {
713 view_assign_ctx(view, ctx);
714 }
715 }
716
695 // If there is a request to be opened fullscreen on a specific output, try 717 // If there is a request to be opened fullscreen on a specific output, try
696 // to honor that request. Otherwise, fallback to assigns, pid mappings, 718 // to honor that request. Otherwise, fallback to assigns, pid mappings,
697 // focused workspace, etc 719 // focused workspace, etc
@@ -705,10 +727,29 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
705 } 727 }
706 728
707 struct sway_seat *seat = input_manager_current_seat(); 729 struct sway_seat *seat = input_manager_current_seat();
708 struct sway_node *node = ws ? seat_get_focus_inactive(seat, &ws->node) 730 struct sway_node *node =
709 : seat_get_focus_inactive(seat, &root->node); 731 seat_get_focus_inactive(seat, ws ? &ws->node : &root->node);
710 struct sway_container *target_sibling = node->type == N_CONTAINER ? 732 struct sway_container *target_sibling = NULL;
711 node->sway_container : NULL; 733 if (node && node->type == N_CONTAINER) {
734 if (container_is_floating(node->sway_container)) {
735 // If we're about to launch the view into the floating container, then
736 // launch it as a tiled view instead.
737 if (ws) {
738 target_sibling = seat_get_focus_inactive_tiling(seat, ws);
739 if (target_sibling) {
740 struct sway_container *con =
741 seat_get_focus_inactive_view(seat, &target_sibling->node);
742 if (con) {
743 target_sibling = con;
744 }
745 }
746 } else {
747 ws = seat_get_last_known_workspace(seat);
748 }
749 } else {
750 target_sibling = node->sway_container;
751 }
752 }
712 753
713 view->foreign_toplevel = 754 view->foreign_toplevel =
714 wlr_foreign_toplevel_handle_v1_create(server.foreign_toplevel_manager); 755 wlr_foreign_toplevel_handle_v1_create(server.foreign_toplevel_manager);
@@ -725,13 +766,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
725 wl_signal_add(&view->foreign_toplevel->events.destroy, 766 wl_signal_add(&view->foreign_toplevel->events.destroy,
726 &view->foreign_destroy); 767 &view->foreign_destroy);
727 768
728 // If we're about to launch the view into the floating container, then
729 // launch it as a tiled view in the root of the workspace instead.
730 if (target_sibling && container_is_floating(target_sibling)) {
731 target_sibling = NULL;
732 ws = seat_get_last_known_workspace(seat);
733 }
734
735 struct sway_container *container = view->container; 769 struct sway_container *container = view->container;
736 if (target_sibling) { 770 if (target_sibling) {
737 container_add_sibling(target_sibling, container, 1); 771 container_add_sibling(target_sibling, container, 1);
@@ -740,30 +774,25 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
740 } 774 }
741 ipc_event_window(view->container, "new"); 775 ipc_event_window(view->container, "new");
742 776
743 view_init_subsurfaces(view, wlr_surface);
744 wl_signal_add(&wlr_surface->events.new_subsurface,
745 &view->surface_new_subsurface);
746 view->surface_new_subsurface.notify = view_handle_surface_new_subsurface;
747
748 if (decoration) { 777 if (decoration) {
749 view_update_csd_from_client(view, decoration); 778 view_update_csd_from_client(view, decoration);
750 } 779 }
751 780
752 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 781 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
753 view->container->border = config->floating_border; 782 view->container->pending.border = config->floating_border;
754 view->container->border_thickness = config->floating_border_thickness; 783 view->container->pending.border_thickness = config->floating_border_thickness;
755 container_set_floating(view->container, true); 784 container_set_floating(view->container, true);
756 } else { 785 } else {
757 view->container->border = config->border; 786 view->container->pending.border = config->border;
758 view->container->border_thickness = config->border_thickness; 787 view->container->pending.border_thickness = config->border_thickness;
759 view_set_tiled(view, true); 788 view_set_tiled(view, true);
760 } 789 }
761 790
762 if (config->popup_during_fullscreen == POPUP_LEAVE && 791 if (config->popup_during_fullscreen == POPUP_LEAVE &&
763 container->workspace && 792 container->pending.workspace &&
764 container->workspace->fullscreen && 793 container->pending.workspace->fullscreen &&
765 container->workspace->fullscreen->view) { 794 container->pending.workspace->fullscreen->view) {
766 struct sway_container *fs = container->workspace->fullscreen; 795 struct sway_container *fs = container->pending.workspace->fullscreen;
767 if (view_is_transient_for(view, fs->view)) { 796 if (view_is_transient_for(view, fs->view)) {
768 container_set_fullscreen(fs, false); 797 container_set_fullscreen(fs, false);
769 } 798 }
@@ -774,12 +803,12 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
774 803
775 if (fullscreen) { 804 if (fullscreen) {
776 container_set_fullscreen(view->container, true); 805 container_set_fullscreen(view->container, true);
777 arrange_workspace(view->container->workspace); 806 arrange_workspace(view->container->pending.workspace);
778 } else { 807 } else {
779 if (container->parent) { 808 if (container->pending.parent) {
780 arrange_container(container->parent); 809 arrange_container(container->pending.parent);
781 } else if (container->workspace) { 810 } else if (container->pending.workspace) {
782 arrange_workspace(container->workspace); 811 arrange_workspace(container->pending.workspace);
783 } 812 }
784 } 813 }
785 814
@@ -788,11 +817,10 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
788 bool set_focus = should_focus(view); 817 bool set_focus = should_focus(view);
789 818
790#if HAVE_XWAYLAND 819#if HAVE_XWAYLAND
791 if (wlr_surface_is_xwayland_surface(wlr_surface)) { 820 struct wlr_xwayland_surface *xsurface;
792 struct wlr_xwayland_surface *xsurface = 821 if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface))) {
793 wlr_xwayland_surface_from_wlr_surface(wlr_surface); 822 set_focus &= wlr_xwayland_icccm_input_model(xsurface) !=
794 set_focus = (wlr_xwayland_icccm_input_model(xsurface) != 823 WLR_ICCCM_INPUT_MODEL_NONE;
795 WLR_ICCCM_INPUT_MODEL_NONE) && set_focus;
796 } 824 }
797#endif 825#endif
798 826
@@ -803,18 +831,16 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
803 const char *app_id; 831 const char *app_id;
804 const char *class; 832 const char *class;
805 if ((app_id = view_get_app_id(view)) != NULL) { 833 if ((app_id = view_get_app_id(view)) != NULL) {
806 wlr_foreign_toplevel_handle_v1_set_app_id( 834 wlr_foreign_toplevel_handle_v1_set_app_id(view->foreign_toplevel, app_id);
807 view->foreign_toplevel, app_id);
808 } else if ((class = view_get_class(view)) != NULL) { 835 } else if ((class = view_get_class(view)) != NULL) {
809 wlr_foreign_toplevel_handle_v1_set_app_id( 836 wlr_foreign_toplevel_handle_v1_set_app_id(view->foreign_toplevel, class);
810 view->foreign_toplevel, class);
811 } 837 }
812} 838}
813 839
814void view_unmap(struct sway_view *view) { 840void view_unmap(struct sway_view *view) {
815 wl_signal_emit(&view->events.unmap, view); 841 wl_signal_emit_mutable(&view->events.unmap, view);
816 842
817 wl_list_remove(&view->surface_new_subsurface.link); 843 view->executed_criteria->length = 0;
818 844
819 if (view->urgent_timer) { 845 if (view->urgent_timer) {
820 wl_event_source_remove(view->urgent_timer); 846 wl_event_source_remove(view->urgent_timer);
@@ -826,8 +852,8 @@ void view_unmap(struct sway_view *view) {
826 view->foreign_toplevel = NULL; 852 view->foreign_toplevel = NULL;
827 } 853 }
828 854
829 struct sway_container *parent = view->container->parent; 855 struct sway_container *parent = view->container->pending.parent;
830 struct sway_workspace *ws = view->container->workspace; 856 struct sway_workspace *ws = view->container->pending.workspace;
831 container_begin_destroy(view->container); 857 container_begin_destroy(view->container);
832 if (parent) { 858 if (parent) {
833 container_reap_empty(parent); 859 container_reap_empty(parent);
@@ -860,262 +886,54 @@ void view_unmap(struct sway_view *view) {
860 view->surface = NULL; 886 view->surface = NULL;
861} 887}
862 888
863void view_update_size(struct sway_view *view, int width, int height) { 889void view_update_size(struct sway_view *view) {
864 struct sway_container *con = view->container; 890 struct sway_container *con = view->container;
865 891 con->pending.content_width = view->geometry.width;
866 if (container_is_floating(con)) { 892 con->pending.content_height = view->geometry.height;
867 con->content_width = width; 893 container_set_geometry_from_content(con);
868 con->content_height = height;
869 container_set_geometry_from_content(con);
870 } else {
871 con->surface_x = con->content_x + (con->content_width - width) / 2;
872 con->surface_y = con->content_y + (con->content_height - height) / 2;
873 con->surface_x = fmax(con->surface_x, con->content_x);
874 con->surface_y = fmax(con->surface_y, con->content_y);
875 }
876} 894}
877 895
878static const struct sway_view_child_impl subsurface_impl; 896void view_center_and_clip_surface(struct sway_view *view) {
897 struct sway_container *con = view->container;
879 898
880static void subsurface_get_root_coords(struct sway_view_child *child, 899 if (container_is_floating(con)) {
881 int *root_sx, int *root_sy) { 900 // We always center the current coordinates rather than the next, as the
882 struct wlr_surface *surface = child->surface; 901 // geometry immediately affects the currently active rendering.
883 *root_sx = -child->view->geometry.x; 902 int x = (int) fmax(0, (con->current.content_width - view->geometry.width) / 2);
884 *root_sy = -child->view->geometry.y; 903 int y = (int) fmax(0, (con->current.content_height - view->geometry.height) / 2);
885 904
886 if (child->parent && child->parent->impl && 905 wlr_scene_node_set_position(&view->content_tree->node, x, y);
887 child->parent->impl->get_root_coords) {
888 int sx, sy;
889 child->parent->impl->get_root_coords(child->parent, &sx, &sy);
890 *root_sx += sx;
891 *root_sy += sy;
892 } else { 906 } else {
893 while (surface && wlr_surface_is_subsurface(surface)) { 907 wlr_scene_node_set_position(&view->content_tree->node, 0, 0);
894 struct wlr_subsurface *subsurface =
895 wlr_subsurface_from_wlr_surface(surface);
896 if (subsurface == NULL) {
897 break;
898 }
899 *root_sx += subsurface->current.x;
900 *root_sy += subsurface->current.y;
901 surface = subsurface->parent;
902 }
903 }
904}
905
906static void subsurface_destroy(struct sway_view_child *child) {
907 if (!sway_assert(child->impl == &subsurface_impl,
908 "Expected a subsurface")) {
909 return;
910 }
911 struct sway_subsurface *subsurface = (struct sway_subsurface *)child;
912 wl_list_remove(&subsurface->destroy.link);
913 free(subsurface);
914}
915
916static const struct sway_view_child_impl subsurface_impl = {
917 .get_root_coords = subsurface_get_root_coords,
918 .destroy = subsurface_destroy,
919};
920
921static void subsurface_handle_destroy(struct wl_listener *listener,
922 void *data) {
923 struct sway_subsurface *subsurface =
924 wl_container_of(listener, subsurface, destroy);
925 struct sway_view_child *child = &subsurface->child;
926 view_child_destroy(child);
927}
928
929static void view_child_damage(struct sway_view_child *child, bool whole);
930
931static void view_subsurface_create(struct sway_view *view,
932 struct wlr_subsurface *wlr_subsurface) {
933 struct sway_subsurface *subsurface =
934 calloc(1, sizeof(struct sway_subsurface));
935 if (subsurface == NULL) {
936 sway_log(SWAY_ERROR, "Allocation failed");
937 return;
938 }
939 view_child_init(&subsurface->child, &subsurface_impl, view,
940 wlr_subsurface->surface);
941
942 wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
943 subsurface->destroy.notify = subsurface_handle_destroy;
944
945 subsurface->child.mapped = true;
946
947 view_child_damage(&subsurface->child, true);
948}
949
950static void view_child_subsurface_create(struct sway_view_child *child,
951 struct wlr_subsurface *wlr_subsurface) {
952 struct sway_subsurface *subsurface =
953 calloc(1, sizeof(struct sway_subsurface));
954 if (subsurface == NULL) {
955 sway_log(SWAY_ERROR, "Allocation failed");
956 return;
957 }
958 subsurface->child.parent = child;
959 wl_list_insert(&child->children, &subsurface->child.link);
960 view_child_init(&subsurface->child, &subsurface_impl, child->view,
961 wlr_subsurface->surface);
962
963 wl_signal_add(&wlr_subsurface->events.destroy, &subsurface->destroy);
964 subsurface->destroy.notify = subsurface_handle_destroy;
965
966 subsurface->child.mapped = true;
967
968 view_child_damage(&subsurface->child, true);
969}
970
971static void view_child_damage(struct sway_view_child *child, bool whole) {
972 if (!child || !child->mapped || !child->view || !child->view->container) {
973 return;
974 } 908 }
975 int sx, sy;
976 child->impl->get_root_coords(child, &sx, &sy);
977 desktop_damage_surface(child->surface,
978 child->view->container->content_x + sx,
979 child->view->container->content_y + sy, whole);
980}
981
982static void view_child_handle_surface_commit(struct wl_listener *listener,
983 void *data) {
984 struct sway_view_child *child =
985 wl_container_of(listener, child, surface_commit);
986 view_child_damage(child, false);
987}
988 909
989static void view_child_handle_surface_new_subsurface( 910 // only make sure to clip the content if there is content to clip
990 struct wl_listener *listener, void *data) { 911 if (!wl_list_empty(&con->view->content_tree->children)) {
991 struct sway_view_child *child = 912 wlr_scene_subsurface_tree_set_clip(&con->view->content_tree->node, &(struct wlr_box){
992 wl_container_of(listener, child, surface_new_subsurface); 913 .x = con->view->geometry.x,
993 struct wlr_subsurface *subsurface = data; 914 .y = con->view->geometry.y,
994 view_child_subsurface_create(child, subsurface); 915 .width = con->current.content_width,
995} 916 .height = con->current.content_height,
996 917 });
997static void view_child_handle_surface_destroy(struct wl_listener *listener,
998 void *data) {
999 struct sway_view_child *child =
1000 wl_container_of(listener, child, surface_destroy);
1001 view_child_destroy(child);
1002}
1003
1004static void view_init_subsurfaces(struct sway_view *view,
1005 struct wlr_surface *surface) {
1006 struct wlr_subsurface *subsurface;
1007 wl_list_for_each(subsurface, &surface->subsurfaces, parent_link) {
1008 view_subsurface_create(view, subsurface);
1009 }
1010}
1011
1012static void view_child_handle_surface_map(struct wl_listener *listener,
1013 void *data) {
1014 struct sway_view_child *child =
1015 wl_container_of(listener, child, surface_map);
1016 child->mapped = true;
1017 view_child_damage(child, true);
1018}
1019
1020static void view_child_handle_surface_unmap(struct wl_listener *listener,
1021 void *data) {
1022 struct sway_view_child *child =
1023 wl_container_of(listener, child, surface_unmap);
1024 view_child_damage(child, true);
1025 child->mapped = false;
1026}
1027
1028static void view_child_handle_view_unmap(struct wl_listener *listener,
1029 void *data) {
1030 struct sway_view_child *child =
1031 wl_container_of(listener, child, view_unmap);
1032 view_child_damage(child, true);
1033 child->mapped = false;
1034}
1035
1036void view_child_init(struct sway_view_child *child,
1037 const struct sway_view_child_impl *impl, struct sway_view *view,
1038 struct wlr_surface *surface) {
1039 child->impl = impl;
1040 child->view = view;
1041 child->surface = surface;
1042 wl_list_init(&child->children);
1043
1044 wl_signal_add(&surface->events.commit, &child->surface_commit);
1045 child->surface_commit.notify = view_child_handle_surface_commit;
1046 wl_signal_add(&surface->events.new_subsurface,
1047 &child->surface_new_subsurface);
1048 child->surface_new_subsurface.notify =
1049 view_child_handle_surface_new_subsurface;
1050 wl_signal_add(&surface->events.destroy, &child->surface_destroy);
1051 child->surface_destroy.notify = view_child_handle_surface_destroy;
1052
1053 // Not all child views have a map/unmap event
1054 child->surface_map.notify = view_child_handle_surface_map;
1055 wl_list_init(&child->surface_map.link);
1056 child->surface_unmap.notify = view_child_handle_surface_unmap;
1057 wl_list_init(&child->surface_unmap.link);
1058
1059 wl_signal_add(&view->events.unmap, &child->view_unmap);
1060 child->view_unmap.notify = view_child_handle_view_unmap;
1061
1062 struct sway_workspace *workspace = child->view->container->workspace;
1063 if (workspace) {
1064 wlr_surface_send_enter(child->surface, workspace->output->wlr_output);
1065 }
1066
1067 view_init_subsurfaces(child->view, surface);
1068}
1069
1070void view_child_destroy(struct sway_view_child *child) {
1071 if (child->mapped && child->view->container != NULL) {
1072 view_child_damage(child, true);
1073 }
1074
1075 if (child->parent != NULL) {
1076 wl_list_remove(&child->link);
1077 child->parent = NULL;
1078 }
1079
1080 struct sway_view_child *subchild, *tmpchild;
1081 wl_list_for_each_safe(subchild, tmpchild, &child->children, link) {
1082 wl_list_remove(&subchild->link);
1083 subchild->parent = NULL;
1084 }
1085
1086 wl_list_remove(&child->surface_commit.link);
1087 wl_list_remove(&child->surface_destroy.link);
1088 wl_list_remove(&child->surface_map.link);
1089 wl_list_remove(&child->surface_unmap.link);
1090 wl_list_remove(&child->view_unmap.link);
1091 wl_list_remove(&child->surface_new_subsurface.link);
1092
1093 if (child->impl && child->impl->destroy) {
1094 child->impl->destroy(child);
1095 } else {
1096 free(child);
1097 } 918 }
1098} 919}
1099 920
1100struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { 921struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) {
1101 if (wlr_surface_is_xdg_surface(wlr_surface)) { 922 struct wlr_xdg_surface *xdg_surface;
1102 struct wlr_xdg_surface *xdg_surface = 923 if ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(wlr_surface))) {
1103 wlr_xdg_surface_from_wlr_surface(wlr_surface);
1104 return view_from_wlr_xdg_surface(xdg_surface); 924 return view_from_wlr_xdg_surface(xdg_surface);
1105 } 925 }
1106#if HAVE_XWAYLAND 926#if HAVE_XWAYLAND
1107 if (wlr_surface_is_xwayland_surface(wlr_surface)) { 927 struct wlr_xwayland_surface *xsurface;
1108 struct wlr_xwayland_surface *xsurface = 928 if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface))) {
1109 wlr_xwayland_surface_from_wlr_surface(wlr_surface);
1110 return view_from_wlr_xwayland_surface(xsurface); 929 return view_from_wlr_xwayland_surface(xsurface);
1111 } 930 }
1112#endif 931#endif
1113 if (wlr_surface_is_subsurface(wlr_surface)) { 932 struct wlr_subsurface *subsurface;
1114 struct wlr_subsurface *subsurface = 933 if ((subsurface = wlr_subsurface_try_from_wlr_surface(wlr_surface))) {
1115 wlr_subsurface_from_wlr_surface(wlr_surface);
1116 return view_from_wlr_surface(subsurface->parent); 934 return view_from_wlr_surface(subsurface->parent);
1117 } 935 }
1118 if (wlr_surface_is_layer_surface(wlr_surface)) { 936 if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) {
1119 return NULL; 937 return NULL;
1120 } 938 }
1121 939
@@ -1211,25 +1029,31 @@ void view_update_title(struct sway_view *view, bool force) {
1211 1029
1212 free(view->container->title); 1030 free(view->container->title);
1213 free(view->container->formatted_title); 1031 free(view->container->formatted_title);
1214 if (title) { 1032
1215 size_t len = parse_title_format(view, NULL); 1033 size_t len = parse_title_format(view, NULL);
1034
1035 if (len) {
1216 char *buffer = calloc(len + 1, sizeof(char)); 1036 char *buffer = calloc(len + 1, sizeof(char));
1217 if (!sway_assert(buffer, "Unable to allocate title string")) { 1037 if (!sway_assert(buffer, "Unable to allocate title string")) {
1218 return; 1038 return;
1219 } 1039 }
1220 parse_title_format(view, buffer);
1221 1040
1222 view->container->title = strdup(title); 1041 parse_title_format(view, buffer);
1223 view->container->formatted_title = buffer; 1042 view->container->formatted_title = buffer;
1224 } else { 1043 } else {
1225 view->container->title = NULL;
1226 view->container->formatted_title = NULL; 1044 view->container->formatted_title = NULL;
1227 } 1045 }
1228 container_calculate_title_height(view->container); 1046
1229 config_update_font_height(false); 1047 view->container->title = title ? strdup(title) : NULL;
1230 1048
1231 // Update title after the global font height is updated 1049 // Update title after the global font height is updated
1232 container_update_title_textures(view->container); 1050 if (view->container->title_bar.title_text && len) {
1051 sway_text_node_set_text(view->container->title_bar.title_text,
1052 view->container->formatted_title);
1053 container_arrange_title_bar(view->container);
1054 } else {
1055 container_update_title_bar(view->container);
1056 }
1233 1057
1234 ipc_event_window(view->container, "title"); 1058 ipc_event_window(view->container, "title");
1235 1059
@@ -1242,15 +1066,15 @@ bool view_is_visible(struct sway_view *view) {
1242 if (view->container->node.destroying) { 1066 if (view->container->node.destroying) {
1243 return false; 1067 return false;
1244 } 1068 }
1245 struct sway_workspace *workspace = view->container->workspace; 1069 struct sway_workspace *workspace = view->container->pending.workspace;
1246 if (!workspace && view->container->fullscreen_mode != FULLSCREEN_GLOBAL) { 1070 if (!workspace && view->container->pending.fullscreen_mode != FULLSCREEN_GLOBAL) {
1247 bool fs_global_descendant = false; 1071 bool fs_global_descendant = false;
1248 struct sway_container *parent = view->container->parent; 1072 struct sway_container *parent = view->container->pending.parent;
1249 while (parent) { 1073 while (parent) {
1250 if (parent->fullscreen_mode == FULLSCREEN_GLOBAL) { 1074 if (parent->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
1251 fs_global_descendant = true; 1075 fs_global_descendant = true;
1252 } 1076 }
1253 parent = parent->parent; 1077 parent = parent->pending.parent;
1254 } 1078 }
1255 if (!fs_global_descendant) { 1079 if (!fs_global_descendant) {
1256 return false; 1080 return false;
@@ -1268,13 +1092,13 @@ bool view_is_visible(struct sway_view *view) {
1268 enum sway_container_layout layout = container_parent_layout(con); 1092 enum sway_container_layout layout = container_parent_layout(con);
1269 if ((layout == L_TABBED || layout == L_STACKED) 1093 if ((layout == L_TABBED || layout == L_STACKED)
1270 && !container_is_floating(con)) { 1094 && !container_is_floating(con)) {
1271 struct sway_node *parent = con->parent ? 1095 struct sway_node *parent = con->pending.parent ?
1272 &con->parent->node : &con->workspace->node; 1096 &con->pending.parent->node : &con->pending.workspace->node;
1273 if (seat_get_active_tiling_child(seat, parent) != &con->node) { 1097 if (seat_get_active_tiling_child(seat, parent) != &con->node) {
1274 return false; 1098 return false;
1275 } 1099 }
1276 } 1100 }
1277 con = con->parent; 1101 con = con->pending.parent;
1278 } 1102 }
1279 // Check view isn't hidden by another fullscreen view 1103 // Check view isn't hidden by another fullscreen view
1280 struct sway_container *fs = root->fullscreen_global ? 1104 struct sway_container *fs = root->fullscreen_global ?
@@ -1296,6 +1120,7 @@ void view_set_urgent(struct sway_view *view, bool enable) {
1296 return; 1120 return;
1297 } 1121 }
1298 clock_gettime(CLOCK_MONOTONIC, &view->urgent); 1122 clock_gettime(CLOCK_MONOTONIC, &view->urgent);
1123 container_update_itself_and_parents(view->container);
1299 } else { 1124 } else {
1300 view->urgent = (struct timespec){ 0 }; 1125 view->urgent = (struct timespec){ 0 };
1301 if (view->urgent_timer) { 1126 if (view->urgent_timer) {
@@ -1303,12 +1128,11 @@ void view_set_urgent(struct sway_view *view, bool enable) {
1303 view->urgent_timer = NULL; 1128 view->urgent_timer = NULL;
1304 } 1129 }
1305 } 1130 }
1306 container_damage_whole(view->container);
1307 1131
1308 ipc_event_window(view->container, "urgent"); 1132 ipc_event_window(view->container, "urgent");
1309 1133
1310 if (!container_is_scratchpad_hidden(view->container)) { 1134 if (!container_is_scratchpad_hidden(view->container)) {
1311 workspace_detect_urgent(view->container->workspace); 1135 workspace_detect_urgent(view->container->pending.workspace);
1312 } 1136 }
1313} 1137}
1314 1138
@@ -1317,40 +1141,54 @@ bool view_is_urgent(struct sway_view *view) {
1317} 1141}
1318 1142
1319void view_remove_saved_buffer(struct sway_view *view) { 1143void view_remove_saved_buffer(struct sway_view *view) {
1320 if (!sway_assert(!wl_list_empty(&view->saved_buffers), "Expected a saved buffer")) { 1144 if (!sway_assert(view->saved_surface_tree, "Expected a saved buffer")) {
1321 return; 1145 return;
1322 } 1146 }
1323 struct sway_saved_buffer *saved_buf, *tmp; 1147
1324 wl_list_for_each_safe(saved_buf, tmp, &view->saved_buffers, link) { 1148 wlr_scene_node_destroy(&view->saved_surface_tree->node);
1325 wlr_buffer_unlock(&saved_buf->buffer->base); 1149 view->saved_surface_tree = NULL;
1326 wl_list_remove(&saved_buf->link); 1150 wlr_scene_node_set_enabled(&view->content_tree->node, true);
1327 free(saved_buf);
1328 }
1329} 1151}
1330 1152
1331static void view_save_buffer_iterator(struct wlr_surface *surface, 1153static void view_save_buffer_iterator(struct wlr_scene_buffer *buffer,
1332 int sx, int sy, void *data) { 1154 int sx, int sy, void *data) {
1333 struct sway_view *view = data; 1155 struct wlr_scene_tree *tree = data;
1334 1156
1335 if (surface && wlr_surface_has_buffer(surface)) { 1157 struct wlr_scene_buffer *sbuf = wlr_scene_buffer_create(tree, NULL);
1336 wlr_buffer_lock(&surface->buffer->base); 1158 if (!sbuf) {
1337 struct sway_saved_buffer *saved_buffer = calloc(1, sizeof(struct sway_saved_buffer)); 1159 sway_log(SWAY_ERROR, "Could not allocate a scene buffer when saving a surface");
1338 saved_buffer->buffer = surface->buffer; 1160 return;
1339 saved_buffer->width = surface->current.width;
1340 saved_buffer->height = surface->current.height;
1341 saved_buffer->x = sx;
1342 saved_buffer->y = sy;
1343 saved_buffer->transform = surface->current.transform;
1344 wlr_surface_get_buffer_source_box(surface, &saved_buffer->source_box);
1345 wl_list_insert(&view->saved_buffers, &saved_buffer->link);
1346 } 1161 }
1162
1163 wlr_scene_buffer_set_dest_size(sbuf,
1164 buffer->dst_width, buffer->dst_height);
1165 wlr_scene_buffer_set_opaque_region(sbuf, &buffer->opaque_region);
1166 wlr_scene_buffer_set_source_box(sbuf, &buffer->src_box);
1167 wlr_scene_node_set_position(&sbuf->node, sx, sy);
1168 wlr_scene_buffer_set_transform(sbuf, buffer->transform);
1169 wlr_scene_buffer_set_buffer(sbuf, buffer->buffer);
1347} 1170}
1348 1171
1349void view_save_buffer(struct sway_view *view) { 1172void view_save_buffer(struct sway_view *view) {
1350 if (!sway_assert(wl_list_empty(&view->saved_buffers), "Didn't expect saved buffer")) { 1173 if (!sway_assert(!view->saved_surface_tree, "Didn't expect saved buffer")) {
1351 view_remove_saved_buffer(view); 1174 view_remove_saved_buffer(view);
1352 } 1175 }
1353 view_for_each_surface(view, view_save_buffer_iterator, view); 1176
1177 view->saved_surface_tree = wlr_scene_tree_create(view->scene_tree);
1178 if (!view->saved_surface_tree) {
1179 sway_log(SWAY_ERROR, "Could not allocate a scene tree node when saving a surface");
1180 return;
1181 }
1182
1183 // Enable and disable the saved surface tree like so to atomitaclly update
1184 // the tree. This will prevent over damaging or other weirdness.
1185 wlr_scene_node_set_enabled(&view->saved_surface_tree->node, false);
1186
1187 wlr_scene_node_for_each_buffer(&view->content_tree->node,
1188 view_save_buffer_iterator, view->saved_surface_tree);
1189
1190 wlr_scene_node_set_enabled(&view->content_tree->node, false);
1191 wlr_scene_node_set_enabled(&view->saved_surface_tree->node, true);
1354} 1192}
1355 1193
1356bool view_is_transient_for(struct sway_view *child, 1194bool view_is_transient_for(struct sway_view *child,
@@ -1358,3 +1196,19 @@ bool view_is_transient_for(struct sway_view *child,
1358 return child->impl->is_transient_for && 1196 return child->impl->is_transient_for &&
1359 child->impl->is_transient_for(child, ancestor); 1197 child->impl->is_transient_for(child, ancestor);
1360} 1198}
1199
1200static void send_frame_done_iterator(struct wlr_scene_buffer *scene_buffer,
1201 int x, int y, void *data) {
1202 struct timespec *when = data;
1203 wl_signal_emit_mutable(&scene_buffer->events.frame_done, when);
1204}
1205
1206void view_send_frame_done(struct sway_view *view) {
1207 struct timespec when;
1208 clock_gettime(CLOCK_MONOTONIC, &when);
1209
1210 struct wlr_scene_node *node;
1211 wl_list_for_each(node, &view->content_tree->children, link) {
1212 wlr_scene_node_for_each_buffer(node, send_frame_done_iterator, &when);
1213 }
1214}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 921b7d19..40d33435 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -48,14 +48,16 @@ struct sway_output *workspace_get_initial_output(const char *name) {
48 if (focus && focus->type == N_WORKSPACE) { 48 if (focus && focus->type == N_WORKSPACE) {
49 return focus->sway_workspace->output; 49 return focus->sway_workspace->output;
50 } else if (focus && focus->type == N_CONTAINER) { 50 } else if (focus && focus->type == N_CONTAINER) {
51 return focus->sway_container->workspace->output; 51 return focus->sway_container->pending.workspace->output;
52 } 52 }
53 // Fallback to the first output or noop output for headless 53 // Fallback to the first output or the headless output
54 return root->outputs->length ? root->outputs->items[0] : root->noop_output; 54 return root->outputs->length ? root->outputs->items[0] : root->fallback_output;
55} 55}
56 56
57struct sway_workspace *workspace_create(struct sway_output *output, 57struct sway_workspace *workspace_create(struct sway_output *output,
58 const char *name) { 58 const char *name) {
59 sway_assert(name, "NULL name given to workspace_create");
60
59 if (output == NULL) { 61 if (output == NULL) {
60 output = workspace_get_initial_output(name); 62 output = workspace_get_initial_output(name);
61 } 63 }
@@ -69,7 +71,19 @@ struct sway_workspace *workspace_create(struct sway_output *output,
69 return NULL; 71 return NULL;
70 } 72 }
71 node_init(&ws->node, N_WORKSPACE, ws); 73 node_init(&ws->node, N_WORKSPACE, ws);
72 ws->name = name ? strdup(name) : NULL; 74
75 bool failed = false;
76 ws->layers.tiling = alloc_scene_tree(root->staging, &failed);
77 ws->layers.fullscreen = alloc_scene_tree(root->staging, &failed);
78
79 if (failed) {
80 wlr_scene_node_destroy(&ws->layers.tiling->node);
81 wlr_scene_node_destroy(&ws->layers.fullscreen->node);
82 free(ws);
83 return NULL;
84 }
85
86 ws->name = strdup(name);
73 ws->prev_split_layout = L_NONE; 87 ws->prev_split_layout = L_NONE;
74 ws->layout = output_get_default_layout(output); 88 ws->layout = output_get_default_layout(output);
75 ws->floating = create_list(); 89 ws->floating = create_list();
@@ -114,7 +128,7 @@ struct sway_workspace *workspace_create(struct sway_output *output,
114 output_sort_workspaces(output); 128 output_sort_workspaces(output);
115 129
116 ipc_event_workspace(NULL, ws, "init"); 130 ipc_event_workspace(NULL, ws, "init");
117 wl_signal_emit(&root->events.new_node, &ws->node); 131 wl_signal_emit_mutable(&root->events.new_node, &ws->node);
118 132
119 return ws; 133 return ws;
120} 134}
@@ -129,6 +143,11 @@ void workspace_destroy(struct sway_workspace *workspace) {
129 return; 143 return;
130 } 144 }
131 145
146 scene_node_disown_children(workspace->layers.tiling);
147 scene_node_disown_children(workspace->layers.fullscreen);
148 wlr_scene_node_destroy(&workspace->layers.tiling->node);
149 wlr_scene_node_destroy(&workspace->layers.fullscreen->node);
150
132 free(workspace->name); 151 free(workspace->name);
133 free(workspace->representation); 152 free(workspace->representation);
134 list_free_items_and_destroy(workspace->output_priority); 153 list_free_items_and_destroy(workspace->output_priority);
@@ -142,7 +161,7 @@ void workspace_destroy(struct sway_workspace *workspace) {
142void workspace_begin_destroy(struct sway_workspace *workspace) { 161void workspace_begin_destroy(struct sway_workspace *workspace) {
143 sway_log(SWAY_DEBUG, "Destroying workspace '%s'", workspace->name); 162 sway_log(SWAY_DEBUG, "Destroying workspace '%s'", workspace->name);
144 ipc_event_workspace(NULL, workspace, "empty"); // intentional 163 ipc_event_workspace(NULL, workspace, "empty"); // intentional
145 wl_signal_emit(&workspace->node.events.destroy, &workspace->node); 164 wl_signal_emit_mutable(&workspace->node.events.destroy, &workspace->node);
146 165
147 if (workspace->output) { 166 if (workspace->output) {
148 workspace_detach(workspace); 167 workspace_detach(workspace);
@@ -174,22 +193,16 @@ void workspace_consider_destroy(struct sway_workspace *ws) {
174static bool workspace_valid_on_output(const char *output_name, 193static bool workspace_valid_on_output(const char *output_name,
175 const char *ws_name) { 194 const char *ws_name) {
176 struct workspace_config *wsc = workspace_find_config(ws_name); 195 struct workspace_config *wsc = workspace_find_config(ws_name);
177 char identifier[128];
178 struct sway_output *output = output_by_name_or_id(output_name); 196 struct sway_output *output = output_by_name_or_id(output_name);
179 if (!output) { 197 if (!output) {
180 return false; 198 return false;
181 } 199 }
182 output_name = output->wlr_output->name;
183 output_get_identifier(identifier, sizeof(identifier), output);
184
185 if (!wsc) { 200 if (!wsc) {
186 return true; 201 return true;
187 } 202 }
188 203
189 for (int i = 0; i < wsc->outputs->length; i++) { 204 for (int i = 0; i < wsc->outputs->length; i++) {
190 if (strcmp(wsc->outputs->items[i], "*") == 0 || 205 if (output_match_name_or_id(output, wsc->outputs->items[i])) {
191 strcmp(wsc->outputs->items[i], output_name) == 0 ||
192 strcmp(wsc->outputs->items[i], identifier) == 0) {
193 return true; 206 return true;
194 } 207 }
195 } 208 }
@@ -222,10 +235,8 @@ static void workspace_name_from_binding(const struct sway_binding * binding,
222 // not a command about workspaces 235 // not a command about workspaces
223 if (strcmp(_target, "next") == 0 || 236 if (strcmp(_target, "next") == 0 ||
224 strcmp(_target, "prev") == 0 || 237 strcmp(_target, "prev") == 0 ||
225 strncmp(_target, "next_on_output", 238 strcmp(_target, "next_on_output") == 0 ||
226 strlen("next_on_output")) == 0 || 239 strcmp(_target, "prev_on_output") == 0 ||
227 strncmp(_target, "prev_on_output",
228 strlen("next_on_output")) == 0 ||
229 strcmp(_target, "number") == 0 || 240 strcmp(_target, "number") == 0 ||
230 strcmp(_target, "back_and_forth") == 0 || 241 strcmp(_target, "back_and_forth") == 0 ||
231 strcmp(_target, "current") == 0) { 242 strcmp(_target, "current") == 0) {
@@ -286,13 +297,10 @@ char *workspace_next_name(const char *output_name) {
286 // assignments primarily, falling back to bindings and numbers. 297 // assignments primarily, falling back to bindings and numbers.
287 struct sway_mode *mode = config->current_mode; 298 struct sway_mode *mode = config->current_mode;
288 299
289 char identifier[128];
290 struct sway_output *output = output_by_name_or_id(output_name); 300 struct sway_output *output = output_by_name_or_id(output_name);
291 if (!output) { 301 if (!output) {
292 return NULL; 302 return NULL;
293 } 303 }
294 output_name = output->wlr_output->name;
295 output_get_identifier(identifier, sizeof(identifier), output);
296 304
297 int order = INT_MAX; 305 int order = INT_MAX;
298 char *target = NULL; 306 char *target = NULL;
@@ -312,9 +320,7 @@ char *workspace_next_name(const char *output_name) {
312 } 320 }
313 bool found = false; 321 bool found = false;
314 for (int j = 0; j < wsc->outputs->length; ++j) { 322 for (int j = 0; j < wsc->outputs->length; ++j) {
315 if (strcmp(wsc->outputs->items[j], "*") == 0 || 323 if (output_match_name_or_id(output, wsc->outputs->items[j])) {
316 strcmp(wsc->outputs->items[j], output_name) == 0 ||
317 strcmp(wsc->outputs->items[j], identifier) == 0) {
318 found = true; 324 found = true;
319 free(target); 325 free(target);
320 target = strdup(wsc->workspace); 326 target = strdup(wsc->workspace);
@@ -363,11 +369,11 @@ struct sway_workspace *workspace_by_name(const char *name) {
363 if (current && strcmp(name, "prev") == 0) { 369 if (current && strcmp(name, "prev") == 0) {
364 return workspace_prev(current); 370 return workspace_prev(current);
365 } else if (current && strcmp(name, "prev_on_output") == 0) { 371 } else if (current && strcmp(name, "prev_on_output") == 0) {
366 return workspace_output_prev(current, false); 372 return workspace_output_prev(current);
367 } else if (current && strcmp(name, "next") == 0) { 373 } else if (current && strcmp(name, "next") == 0) {
368 return workspace_next(current); 374 return workspace_next(current);
369 } else if (current && strcmp(name, "next_on_output") == 0) { 375 } else if (current && strcmp(name, "next_on_output") == 0) {
370 return workspace_output_next(current, false); 376 return workspace_output_next(current);
371 } else if (strcmp(name, "current") == 0) { 377 } else if (strcmp(name, "current") == 0) {
372 return current; 378 return current;
373 } else if (strcasecmp(name, "back_and_forth") == 0) { 379 } else if (strcasecmp(name, "back_and_forth") == 0) {
@@ -530,7 +536,7 @@ struct sway_workspace *workspace_next(struct sway_workspace *workspace) {
530 * otherwise the next one is returned. 536 * otherwise the next one is returned.
531 */ 537 */
532static struct sway_workspace *workspace_output_prev_next_impl( 538static struct sway_workspace *workspace_output_prev_next_impl(
533 struct sway_output *output, int dir, bool create) { 539 struct sway_output *output, int dir) {
534 struct sway_seat *seat = input_manager_current_seat(); 540 struct sway_seat *seat = input_manager_current_seat();
535 struct sway_workspace *workspace = seat_get_focused_workspace(seat); 541 struct sway_workspace *workspace = seat_get_focused_workspace(seat);
536 if (!workspace) { 542 if (!workspace) {
@@ -540,46 +546,43 @@ static struct sway_workspace *workspace_output_prev_next_impl(
540 } 546 }
541 547
542 int index = list_find(output->workspaces, workspace); 548 int index = list_find(output->workspaces, workspace);
543 if (!workspace_is_empty(workspace) && create &&
544 (index + dir < 0 || index + dir == output->workspaces->length)) {
545 struct sway_output *output = workspace->output;
546 char *next = workspace_next_name(output->wlr_output->name);
547 workspace_create(output, next);
548 free(next);
549 }
550 size_t new_index = wrap(index + dir, output->workspaces->length); 549 size_t new_index = wrap(index + dir, output->workspaces->length);
551 return output->workspaces->items[new_index]; 550 return output->workspaces->items[new_index];
552} 551}
553 552
554struct sway_workspace *workspace_output_next( 553
555 struct sway_workspace *current, bool create) { 554struct sway_workspace *workspace_output_next(struct sway_workspace *current) {
556 return workspace_output_prev_next_impl(current->output, 1, create); 555 return workspace_output_prev_next_impl(current->output, 1);
557} 556}
558 557
559struct sway_workspace *workspace_output_prev( 558struct sway_workspace *workspace_output_prev(struct sway_workspace *current) {
560 struct sway_workspace *current, bool create) { 559 return workspace_output_prev_next_impl(current->output, -1);
561 return workspace_output_prev_next_impl(current->output, -1, create);
562} 560}
563 561
564bool workspace_switch(struct sway_workspace *workspace, 562struct sway_workspace *workspace_auto_back_and_forth(
565 bool no_auto_back_and_forth) { 563 struct sway_workspace *workspace) {
566 struct sway_seat *seat = input_manager_current_seat(); 564 struct sway_seat *seat = input_manager_current_seat();
567 struct sway_workspace *active_ws = NULL; 565 struct sway_workspace *active_ws = NULL;
568 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); 566 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node);
569 if (focus && focus->type == N_WORKSPACE) { 567 if (focus && focus->type == N_WORKSPACE) {
570 active_ws = focus->sway_workspace; 568 active_ws = focus->sway_workspace;
571 } else if (focus && focus->type == N_CONTAINER) { 569 } else if (focus && focus->type == N_CONTAINER) {
572 active_ws = focus->sway_container->workspace; 570 active_ws = focus->sway_container->pending.workspace;
573 } 571 }
574 572
575 if (!no_auto_back_and_forth && config->auto_back_and_forth && active_ws 573 if (config->auto_back_and_forth && active_ws && active_ws == workspace &&
576 && active_ws == workspace && seat->prev_workspace_name) { 574 seat->prev_workspace_name) {
577 struct sway_workspace *new_ws = 575 struct sway_workspace *new_ws =
578 workspace_by_name(seat->prev_workspace_name); 576 workspace_by_name(seat->prev_workspace_name);
579 workspace = new_ws ? 577 workspace = new_ws ?
580 new_ws : 578 new_ws :
581 workspace_create(NULL, seat->prev_workspace_name); 579 workspace_create(NULL, seat->prev_workspace_name);
582 } 580 }
581 return workspace;
582}
583
584bool workspace_switch(struct sway_workspace *workspace) {
585 struct sway_seat *seat = input_manager_current_seat();
583 586
584 sway_log(SWAY_DEBUG, "Switching to workspace %p:%s", 587 sway_log(SWAY_DEBUG, "Switching to workspace %p:%s",
585 workspace, workspace->name); 588 workspace, workspace->name);
@@ -657,15 +660,9 @@ void workspace_output_add_priority(struct sway_workspace *workspace,
657 660
658struct sway_output *workspace_output_get_highest_available( 661struct sway_output *workspace_output_get_highest_available(
659 struct sway_workspace *ws, struct sway_output *exclude) { 662 struct sway_workspace *ws, struct sway_output *exclude) {
660 char exclude_id[128] = {'\0'};
661 if (exclude) {
662 output_get_identifier(exclude_id, sizeof(exclude_id), exclude);
663 }
664
665 for (int i = 0; i < ws->output_priority->length; i++) { 663 for (int i = 0; i < ws->output_priority->length; i++) {
666 char *name = ws->output_priority->items[i]; 664 const char *name = ws->output_priority->items[i];
667 if (exclude && (strcmp(name, exclude->wlr_output->name) == 0 665 if (exclude && output_match_name_or_id(exclude, name)) {
668 || strcmp(name, exclude_id) == 0)) {
669 continue; 666 continue;
670 } 667 }
671 668
@@ -689,7 +686,6 @@ void workspace_detect_urgent(struct sway_workspace *workspace) {
689 if (workspace->urgent != new_urgent) { 686 if (workspace->urgent != new_urgent) {
690 workspace->urgent = new_urgent; 687 workspace->urgent = new_urgent;
691 ipc_event_workspace(NULL, workspace, "urgent"); 688 ipc_event_workspace(NULL, workspace, "urgent");
692 output_damage_whole(workspace->output);
693 } 689 }
694} 690}
695 691
@@ -736,13 +732,13 @@ struct sway_container *workspace_find_container(struct sway_workspace *ws,
736} 732}
737 733
738static void set_workspace(struct sway_container *container, void *data) { 734static void set_workspace(struct sway_container *container, void *data) {
739 container->workspace = container->parent->workspace; 735 container->pending.workspace = container->pending.parent->pending.workspace;
740} 736}
741 737
742static void workspace_attach_tiling(struct sway_workspace *ws, 738static void workspace_attach_tiling(struct sway_workspace *ws,
743 struct sway_container *con) { 739 struct sway_container *con) {
744 list_add(ws->tiling, con); 740 list_add(ws->tiling, con);
745 con->workspace = ws; 741 con->pending.workspace = ws;
746 container_for_each_child(con, set_workspace, NULL); 742 container_for_each_child(con, set_workspace, NULL);
747 container_handle_fullscreen_reparent(con); 743 container_handle_fullscreen_reparent(con);
748 workspace_update_representation(ws); 744 workspace_update_representation(ws);
@@ -753,7 +749,7 @@ static void workspace_attach_tiling(struct sway_workspace *ws,
753struct sway_container *workspace_wrap_children(struct sway_workspace *ws) { 749struct sway_container *workspace_wrap_children(struct sway_workspace *ws) {
754 struct sway_container *fs = ws->fullscreen; 750 struct sway_container *fs = ws->fullscreen;
755 struct sway_container *middle = container_create(NULL); 751 struct sway_container *middle = container_create(NULL);
756 middle->layout = ws->layout; 752 middle->pending.layout = ws->layout;
757 while (ws->tiling->length) { 753 while (ws->tiling->length) {
758 struct sway_container *child = ws->tiling->items[0]; 754 struct sway_container *child = ws->tiling->items[0];
759 container_detach(child); 755 container_detach(child);
@@ -771,9 +767,9 @@ void workspace_unwrap_children(struct sway_workspace *ws,
771 return; 767 return;
772 } 768 }
773 769
774 ws->layout = wrap->layout; 770 ws->layout = wrap->pending.layout;
775 while (wrap->children->length) { 771 while (wrap->pending.children->length) {
776 struct sway_container *child = wrap->children->items[0]; 772 struct sway_container *child = wrap->pending.children->items[0];
777 container_detach(child); 773 container_detach(child);
778 workspace_add_tiling(ws, child); 774 workspace_add_tiling(ws, child);
779 } 775 }
@@ -793,14 +789,18 @@ void workspace_detach(struct sway_workspace *workspace) {
793 789
794struct sway_container *workspace_add_tiling(struct sway_workspace *workspace, 790struct sway_container *workspace_add_tiling(struct sway_workspace *workspace,
795 struct sway_container *con) { 791 struct sway_container *con) {
796 if (con->workspace) { 792 if (con->pending.workspace) {
793 struct sway_container *old_parent = con->pending.parent;
797 container_detach(con); 794 container_detach(con);
795 if (old_parent) {
796 container_reap_empty(old_parent);
797 }
798 } 798 }
799 if (config->default_layout != L_NONE) { 799 if (config->default_layout != L_NONE) {
800 con = container_split(con, config->default_layout); 800 con = container_split(con, config->default_layout);
801 } 801 }
802 list_add(workspace->tiling, con); 802 list_add(workspace->tiling, con);
803 con->workspace = workspace; 803 con->pending.workspace = workspace;
804 container_for_each_child(con, set_workspace, NULL); 804 container_for_each_child(con, set_workspace, NULL);
805 container_handle_fullscreen_reparent(con); 805 container_handle_fullscreen_reparent(con);
806 workspace_update_representation(workspace); 806 workspace_update_representation(workspace);
@@ -811,11 +811,11 @@ struct sway_container *workspace_add_tiling(struct sway_workspace *workspace,
811 811
812void workspace_add_floating(struct sway_workspace *workspace, 812void workspace_add_floating(struct sway_workspace *workspace,
813 struct sway_container *con) { 813 struct sway_container *con) {
814 if (con->workspace) { 814 if (con->pending.workspace) {
815 container_detach(con); 815 container_detach(con);
816 } 816 }
817 list_add(workspace->floating, con); 817 list_add(workspace->floating, con);
818 con->workspace = workspace; 818 con->pending.workspace = workspace;
819 container_for_each_child(con, set_workspace, NULL); 819 container_for_each_child(con, set_workspace, NULL);
820 container_handle_fullscreen_reparent(con); 820 container_handle_fullscreen_reparent(con);
821 node_set_dirty(&workspace->node); 821 node_set_dirty(&workspace->node);
@@ -825,7 +825,7 @@ void workspace_add_floating(struct sway_workspace *workspace,
825void workspace_insert_tiling_direct(struct sway_workspace *workspace, 825void workspace_insert_tiling_direct(struct sway_workspace *workspace,
826 struct sway_container *con, int index) { 826 struct sway_container *con, int index) {
827 list_insert(workspace->tiling, index, con); 827 list_insert(workspace->tiling, index, con);
828 con->workspace = workspace; 828 con->pending.workspace = workspace;
829 container_for_each_child(con, set_workspace, NULL); 829 container_for_each_child(con, set_workspace, NULL);
830 container_handle_fullscreen_reparent(con); 830 container_handle_fullscreen_reparent(con);
831 workspace_update_representation(workspace); 831 workspace_update_representation(workspace);
@@ -835,7 +835,7 @@ void workspace_insert_tiling_direct(struct sway_workspace *workspace,
835 835
836struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace, 836struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace,
837 struct sway_container *con, int index) { 837 struct sway_container *con, int index) {
838 if (con->workspace) { 838 if (con->pending.workspace) {
839 container_detach(con); 839 container_detach(con);
840 } 840 }
841 if (config->default_layout != L_NONE) { 841 if (config->default_layout != L_NONE) {
@@ -845,24 +845,36 @@ struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace,
845 return con; 845 return con;
846} 846}
847 847
848bool workspace_has_single_visible_container(struct sway_workspace *ws) {
849 struct sway_seat *seat = input_manager_get_default_seat();
850 struct sway_container *focus =
851 seat_get_focus_inactive_tiling(seat, ws);
852 if (focus && !focus->view) {
853 focus = seat_get_focus_inactive_view(seat, &focus->node);
854 }
855 return (focus && focus->view && view_ancestor_is_only_visible(focus->view));
856}
857
848void workspace_add_gaps(struct sway_workspace *ws) { 858void workspace_add_gaps(struct sway_workspace *ws) {
849 if (config->smart_gaps) { 859 if (config->smart_gaps == SMART_GAPS_ON
850 struct sway_seat *seat = input_manager_get_default_seat(); 860 && workspace_has_single_visible_container(ws)) {
851 struct sway_container *focus = 861 ws->current_gaps.top = 0;
852 seat_get_focus_inactive_tiling(seat, ws); 862 ws->current_gaps.right = 0;
853 if (focus && !focus->view) { 863 ws->current_gaps.bottom = 0;
854 focus = seat_get_focus_inactive_view(seat, &focus->node); 864 ws->current_gaps.left = 0;
855 } 865 return;
856 if (focus && focus->view && view_ancestor_is_only_visible(focus->view)) { 866 }
857 ws->current_gaps.top = 0; 867
858 ws->current_gaps.right = 0; 868 if (config->smart_gaps == SMART_GAPS_INVERSE_OUTER
859 ws->current_gaps.bottom = 0; 869 && !workspace_has_single_visible_container(ws)) {
860 ws->current_gaps.left = 0; 870 ws->current_gaps.top = 0;
861 return; 871 ws->current_gaps.right = 0;
862 } 872 ws->current_gaps.bottom = 0;
873 ws->current_gaps.left = 0;
874 } else {
875 ws->current_gaps = ws->gaps_outer;
863 } 876 }
864 877
865 ws->current_gaps = ws->gaps_outer;
866 // Add inner gaps and make sure we don't turn out negative 878 // Add inner gaps and make sure we don't turn out negative
867 ws->current_gaps.top = fmax(0, ws->current_gaps.top + ws->gaps_inner); 879 ws->current_gaps.top = fmax(0, ws->current_gaps.top + ws->gaps_inner);
868 ws->current_gaps.right = fmax(0, ws->current_gaps.right + ws->gaps_inner); 880 ws->current_gaps.right = fmax(0, ws->current_gaps.right + ws->gaps_inner);
@@ -905,7 +917,7 @@ struct sway_container *workspace_split(struct sway_workspace *workspace,
905 enum sway_container_layout old_layout = workspace->layout; 917 enum sway_container_layout old_layout = workspace->layout;
906 struct sway_container *middle = workspace_wrap_children(workspace); 918 struct sway_container *middle = workspace_wrap_children(workspace);
907 workspace->layout = layout; 919 workspace->layout = layout;
908 middle->layout = old_layout; 920 middle->pending.layout = old_layout;
909 921
910 struct sway_seat *seat; 922 struct sway_seat *seat;
911 wl_list_for_each(seat, &server.input->seats, link) { 923 wl_list_for_each(seat, &server.input->seats, link) {
diff --git a/sway/xdg_activation_v1.c b/sway/xdg_activation_v1.c
new file mode 100644
index 00000000..c26ee19a
--- /dev/null
+++ b/sway/xdg_activation_v1.c
@@ -0,0 +1,50 @@
1#include <wlr/types/wlr_xdg_activation_v1.h>
2#include "sway/desktop/launcher.h"
3#include "sway/tree/view.h"
4#include "sway/tree/workspace.h"
5
6void xdg_activation_v1_handle_request_activate(struct wl_listener *listener,
7 void *data) {
8 const struct wlr_xdg_activation_v1_request_activate_event *event = data;
9
10 struct wlr_xdg_surface *xdg_surface =
11 wlr_xdg_surface_try_from_wlr_surface(event->surface);
12 if (xdg_surface == NULL) {
13 return;
14 }
15 struct sway_view *view = xdg_surface->data;
16 if (view == NULL) {
17 return;
18 }
19
20 if (!xdg_surface->surface->mapped) {
21 // This is a startup notification. If we are tracking it, the data
22 // field is a launcher_ctx.
23 struct launcher_ctx *ctx = event->token->data;
24 if (!ctx || ctx->activated) {
25 // This ctx has already been activated and cannot be used again
26 // for a startup notification. It will be destroyed
27 return;
28 } else {
29 ctx->activated = true;
30 view_assign_ctx(view, ctx);
31 }
32 return;
33 }
34
35 struct wlr_seat *wlr_seat = event->token->seat;
36 struct sway_seat *seat = wlr_seat ? wlr_seat->data : NULL;
37 view_request_activate(view, seat);
38}
39
40void xdg_activation_v1_handle_new_token(struct wl_listener *listener, void *data) {
41 struct wlr_xdg_activation_token_v1 *token = data;
42 struct sway_seat *seat = token->seat ? token->seat->data :
43 input_manager_current_seat();
44
45 struct sway_workspace *ws = seat_get_focused_workspace(seat);
46 if (ws) {
47 launcher_ctx_create(token, &ws->node);
48 return;
49 }
50}
diff --git a/sway/xdg_decoration.c b/sway/xdg_decoration.c
index e7c3ea73..fa8c6279 100644
--- a/sway/xdg_decoration.c
+++ b/sway/xdg_decoration.c
@@ -10,7 +10,7 @@ static void xdg_decoration_handle_destroy(struct wl_listener *listener,
10 void *data) { 10 void *data) {
11 struct sway_xdg_decoration *deco = 11 struct sway_xdg_decoration *deco =
12 wl_container_of(listener, deco, destroy); 12 wl_container_of(listener, deco, destroy);
13 if(deco->view) { 13 if (deco->view) {
14 deco->view->xdg_decoration = NULL; 14 deco->view->xdg_decoration = NULL;
15 } 15 }
16 wl_list_remove(&deco->destroy.link); 16 wl_list_remove(&deco->destroy.link);
@@ -23,13 +23,12 @@ static void xdg_decoration_handle_request_mode(struct wl_listener *listener,
23 void *data) { 23 void *data) {
24 struct sway_xdg_decoration *deco = 24 struct sway_xdg_decoration *deco =
25 wl_container_of(listener, deco, request_mode); 25 wl_container_of(listener, deco, request_mode);
26 wlr_xdg_toplevel_decoration_v1_set_mode(deco->wlr_xdg_decoration, 26 set_xdg_decoration_mode(deco);
27 WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
28} 27}
29 28
30void handle_xdg_decoration(struct wl_listener *listener, void *data) { 29void handle_xdg_decoration(struct wl_listener *listener, void *data) {
31 struct wlr_xdg_toplevel_decoration_v1 *wlr_deco = data; 30 struct wlr_xdg_toplevel_decoration_v1 *wlr_deco = data;
32 struct sway_xdg_shell_view *xdg_shell_view = wlr_deco->surface->data; 31 struct sway_xdg_shell_view *xdg_shell_view = wlr_deco->toplevel->base->data;
33 32
34 struct sway_xdg_decoration *deco = calloc(1, sizeof(*deco)); 33 struct sway_xdg_decoration *deco = calloc(1, sizeof(*deco));
35 if (deco == NULL) { 34 if (deco == NULL) {
@@ -48,16 +47,46 @@ void handle_xdg_decoration(struct wl_listener *listener, void *data) {
48 47
49 wl_list_insert(&server.xdg_decorations, &deco->link); 48 wl_list_insert(&server.xdg_decorations, &deco->link);
50 49
51 xdg_decoration_handle_request_mode(&deco->request_mode, wlr_deco); 50 set_xdg_decoration_mode(deco);
52} 51}
53 52
54struct sway_xdg_decoration *xdg_decoration_from_surface( 53struct sway_xdg_decoration *xdg_decoration_from_surface(
55 struct wlr_surface *surface) { 54 struct wlr_surface *surface) {
56 struct sway_xdg_decoration *deco; 55 struct sway_xdg_decoration *deco;
57 wl_list_for_each(deco, &server.xdg_decorations, link) { 56 wl_list_for_each(deco, &server.xdg_decorations, link) {
58 if (deco->wlr_xdg_decoration->surface->surface == surface) { 57 if (deco->wlr_xdg_decoration->toplevel->base->surface == surface) {
59 return deco; 58 return deco;
60 } 59 }
61 } 60 }
62 return NULL; 61 return NULL;
63} 62}
63
64void set_xdg_decoration_mode(struct sway_xdg_decoration *deco) {
65 struct sway_view *view = deco->view;
66 enum wlr_xdg_toplevel_decoration_v1_mode mode =
67 WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
68 enum wlr_xdg_toplevel_decoration_v1_mode client_mode =
69 deco->wlr_xdg_decoration->requested_mode;
70
71 bool floating;
72 if (view->container) {
73 floating = container_is_floating(view->container);
74 bool csd = false;
75 csd = client_mode ==
76 WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
77 view_update_csd_from_client(view, csd);
78 arrange_container(view->container);
79 transaction_commit_dirty();
80 } else {
81 floating = view->impl->wants_floating &&
82 view->impl->wants_floating(view);
83 }
84
85 if (floating && client_mode) {
86 mode = client_mode;
87 }
88
89 if (view->wlr_xdg_toplevel->base->initialized) {
90 wlr_xdg_toplevel_decoration_v1_set_mode(deco->wlr_xdg_decoration, mode);
91 }
92}
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 231c1ad7..021fc3bd 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -51,10 +51,6 @@ static void swaybar_output_free(struct swaybar_output *output) {
51 if (output->surface != NULL) { 51 if (output->surface != NULL) {
52 wl_surface_destroy(output->surface); 52 wl_surface_destroy(output->surface);
53 } 53 }
54 if (output->input_region != NULL) {
55 wl_region_destroy(output->input_region);
56 }
57 zxdg_output_v1_destroy(output->xdg_output);
58 wl_output_destroy(output->output); 54 wl_output_destroy(output->output);
59 destroy_buffer(&output->buffers[0]); 55 destroy_buffer(&output->buffers[0]);
60 destroy_buffer(&output->buffers[1]); 56 destroy_buffer(&output->buffers[1]);
@@ -90,7 +86,7 @@ static void layer_surface_closed(void *_output,
90 swaybar_output_free(output); 86 swaybar_output_free(output);
91} 87}
92 88
93struct zwlr_layer_surface_v1_listener layer_surface_listener = { 89static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
94 .configure = layer_surface_configure, 90 .configure = layer_surface_configure,
95 .closed = layer_surface_closed, 91 .closed = layer_surface_closed,
96}; 92};
@@ -114,10 +110,9 @@ static void add_layer_surface(struct swaybar_output *output) {
114 110
115 if (overlay) { 111 if (overlay) {
116 // Empty input region 112 // Empty input region
117 output->input_region = wl_compositor_create_region(bar->compositor); 113 struct wl_region *region = wl_compositor_create_region(bar->compositor);
118 assert(output->input_region); 114 wl_surface_set_input_region(output->surface, region);
119 115 wl_region_destroy(region);
120 wl_surface_set_input_region(output->surface, output->input_region);
121 } 116 }
122 117
123 zwlr_layer_surface_v1_set_anchor(output->layer_surface, config->position); 118 zwlr_layer_surface_v1_set_anchor(output->layer_surface, config->position);
@@ -172,7 +167,7 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) {
172 if (bar->status) { 167 if (bar->status) {
173 sway_log(SWAY_DEBUG, "Sending %s signal to status command", 168 sway_log(SWAY_DEBUG, "Sending %s signal to status command",
174 visible ? "cont" : "stop"); 169 visible ? "cont" : "stop");
175 kill(bar->status->pid, visible ? 170 kill(-bar->status->pid, visible ?
176 bar->status->cont_signal : bar->status->stop_signal); 171 bar->status->cont_signal : bar->status->stop_signal);
177 } 172 }
178 } 173 }
@@ -230,7 +225,7 @@ static void output_scale(void *data, struct wl_output *wl_output,
230 } 225 }
231} 226}
232 227
233struct wl_output_listener output_listener = { 228static const struct wl_output_listener output_listener = {
234 .geometry = output_geometry, 229 .geometry = output_geometry,
235 .mode = output_mode, 230 .mode = output_mode,
236 .done = output_done, 231 .done = output_done,
@@ -307,7 +302,7 @@ static void xdg_output_handle_description(void *data,
307 } 302 }
308} 303}
309 304
310struct zxdg_output_v1_listener xdg_output_listener = { 305static const struct zxdg_output_v1_listener xdg_output_listener = {
311 .logical_position = xdg_output_handle_logical_position, 306 .logical_position = xdg_output_handle_logical_position,
312 .logical_size = xdg_output_handle_logical_size, 307 .logical_size = xdg_output_handle_logical_size,
313 .done = xdg_output_handle_done, 308 .done = xdg_output_handle_done,
@@ -367,6 +362,9 @@ static void handle_global(void *data, struct wl_registry *registry,
367 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { 362 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
368 bar->xdg_output_manager = wl_registry_bind(registry, name, 363 bar->xdg_output_manager = wl_registry_bind(registry, name,
369 &zxdg_output_manager_v1_interface, 2); 364 &zxdg_output_manager_v1_interface, 2);
365 } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
366 bar->cursor_shape_manager = wl_registry_bind(registry, name,
367 &wp_cursor_shape_manager_v1_interface, 1);
370 } 368 }
371} 369}
372 370
@@ -430,15 +428,17 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
430 // Second roundtrip for xdg-output 428 // Second roundtrip for xdg-output
431 wl_display_roundtrip(bar->display); 429 wl_display_roundtrip(bar->display);
432 430
433 struct swaybar_seat *seat; 431 if (!bar->cursor_shape_manager) {
434 wl_list_for_each(seat, &bar->seats, link) { 432 struct swaybar_seat *seat;
435 struct swaybar_pointer *pointer = &seat->pointer; 433 wl_list_for_each(seat, &bar->seats, link) {
436 if (!pointer) { 434 struct swaybar_pointer *pointer = &seat->pointer;
437 continue; 435 if (!pointer) {
436 continue;
437 }
438 pointer->cursor_surface =
439 wl_compositor_create_surface(bar->compositor);
440 assert(pointer->cursor_surface);
438 } 441 }
439 pointer->cursor_surface =
440 wl_compositor_create_surface(bar->compositor);
441 assert(pointer->cursor_surface);
442 } 442 }
443 443
444 if (bar->config->status_command) { 444 if (bar->config->status_command) {
@@ -461,13 +461,28 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
461 461
462static void display_in(int fd, short mask, void *data) { 462static void display_in(int fd, short mask, void *data) {
463 struct swaybar *bar = data; 463 struct swaybar *bar = data;
464 if (mask & (POLLHUP | POLLERR)) {
465 if (mask & POLLERR) {
466 sway_log(SWAY_ERROR, "Wayland display poll error");
467 }
468 bar->running = false;
469 return;
470 }
464 if (wl_display_dispatch(bar->display) == -1) { 471 if (wl_display_dispatch(bar->display) == -1) {
472 sway_log(SWAY_ERROR, "wl_display_dispatch failed");
465 bar->running = false; 473 bar->running = false;
466 } 474 }
467} 475}
468 476
469static void ipc_in(int fd, short mask, void *data) { 477static void ipc_in(int fd, short mask, void *data) {
470 struct swaybar *bar = data; 478 struct swaybar *bar = data;
479 if (mask & (POLLHUP | POLLERR)) {
480 if (mask & POLLERR) {
481 sway_log(SWAY_ERROR, "IPC poll error");
482 }
483 bar->running = false;
484 return;
485 }
471 if (handle_ipc_readable(bar)) { 486 if (handle_ipc_readable(bar)) {
472 set_bar_dirty(bar); 487 set_bar_dirty(bar);
473 } 488 }
diff --git a/swaybar/config.c b/swaybar/config.c
index abedaec0..5e828773 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -26,7 +26,7 @@ struct swaybar_config *init_config(void) {
26 config->status_command = NULL; 26 config->status_command = NULL;
27 config->pango_markup = false; 27 config->pango_markup = false;
28 config->position = parse_position("bottom"); 28 config->position = parse_position("bottom");
29 config->font = strdup("monospace 10"); 29 config->font_description = pango_font_description_from_string("monospace 10");
30 config->mode = strdup("dock"); 30 config->mode = strdup("dock");
31 config->hidden_state = strdup("hide"); 31 config->hidden_state = strdup("hide");
32 config->sep_symbol = NULL; 32 config->sep_symbol = NULL;
@@ -105,7 +105,7 @@ void free_tray_binding(struct tray_binding *binding) {
105 105
106void free_config(struct swaybar_config *config) { 106void free_config(struct swaybar_config *config) {
107 free(config->status_command); 107 free(config->status_command);
108 free(config->font); 108 pango_font_description_free(config->font_description);
109 free(config->mode); 109 free(config->mode);
110 free(config->hidden_state); 110 free(config->hidden_state);
111 free(config->sep_symbol); 111 free(config->sep_symbol);
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
index 4bcd5843..ccd5a076 100644
--- a/swaybar/i3bar.c
+++ b/swaybar/i3bar.c
@@ -28,6 +28,19 @@ void i3bar_block_unref(struct i3bar_block *block) {
28 } 28 }
29} 29}
30 30
31static bool i3bar_parse_json_color(json_object *json, uint32_t *color) {
32 if (!json) {
33 return false;
34 }
35
36 const char *hexstring = json_object_get_string(json);
37 bool color_set = parse_color(hexstring, color);
38 if (!color_set) {
39 sway_log(SWAY_ERROR, "Ignoring invalid block hexadecimal color string: %s", hexstring);
40 }
41 return color_set;
42}
43
31static void i3bar_parse_json(struct status_line *status, 44static void i3bar_parse_json(struct status_line *status,
32 struct json_object *json_array) { 45 struct json_object *json_array) {
33 struct i3bar_block *block, *tmp; 46 struct i3bar_block *block, *tmp;
@@ -68,13 +81,7 @@ static void i3bar_parse_json(struct status_line *status,
68 strdup(json_object_get_string(full_text)) : NULL; 81 strdup(json_object_get_string(full_text)) : NULL;
69 block->short_text = short_text ? 82 block->short_text = short_text ?
70 strdup(json_object_get_string(short_text)) : NULL; 83 strdup(json_object_get_string(short_text)) : NULL;
71 if (color) { 84 block->color_set = i3bar_parse_json_color(color, &block->color);
72 const char *hexstring = json_object_get_string(color);
73 block->color_set = parse_color(hexstring, &block->color);
74 if (!block->color_set) {
75 sway_log(SWAY_ERROR, "Invalid block color: %s", hexstring);
76 }
77 }
78 if (min_width) { 85 if (min_width) {
79 json_type type = json_object_get_type(min_width); 86 json_type type = json_object_get_type(min_width);
80 if (type == json_type_int) { 87 if (type == json_type_int) {
@@ -100,14 +107,8 @@ static void i3bar_parse_json(struct status_line *status,
100 block->separator_block_width = separator_block_width ? 107 block->separator_block_width = separator_block_width ?
101 json_object_get_int(separator_block_width) : 9; 108 json_object_get_int(separator_block_width) : 9;
102 // Airblader features 109 // Airblader features
103 const char *hex = background ? json_object_get_string(background) : NULL; 110 i3bar_parse_json_color(background, &block->background);
104 if (hex && !parse_color(hex, &block->background)) { 111 block->border_set = i3bar_parse_json_color(border, &block->border);
105 sway_log(SWAY_ERROR, "Ignoring invalid block background: %s", hex);
106 }
107 hex = border ? json_object_get_string(border) : NULL;
108 if (hex && !parse_color(hex, &block->border)) {
109 sway_log(SWAY_ERROR, "Ignoring invalid block border: %s", hex);
110 }
111 block->border_top = border_top ? json_object_get_int(border_top) : 1; 112 block->border_top = border_top ? json_object_get_int(border_top) : 1;
112 block->border_bottom = border_bottom ? 113 block->border_bottom = border_bottom ?
113 json_object_get_int(border_bottom) : 1; 114 json_object_get_int(border_bottom) : 1;
@@ -268,11 +269,16 @@ bool i3bar_handle_readable(struct status_line *status) {
268 269
269enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, 270enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
270 struct i3bar_block *block, double x, double y, double rx, double ry, 271 struct i3bar_block *block, double x, double y, double rx, double ry,
271 double w, double h, int scale, uint32_t button) { 272 double w, double h, int scale, uint32_t button, bool released) {
272 sway_log(SWAY_DEBUG, "block %s clicked", block->name); 273 sway_log(SWAY_DEBUG, "block %s clicked", block->name);
273 if (!block->name || !status->click_events) { 274 if (!block->name || !status->click_events) {
274 return HOTSPOT_PROCESS; 275 return HOTSPOT_PROCESS;
275 } 276 }
277 if (released) {
278 // Since we handle the pressed event, also handle the released event
279 // to block it from falling through to a binding in the bar
280 return HOTSPOT_IGNORE;
281 }
276 282
277 struct json_object *event_json = json_object_new_object(); 283 struct json_object *event_json = json_object_new_object();
278 json_object_object_add(event_json, "name", 284 json_object_object_add(event_json, "name",
diff --git a/common/background-image.c b/swaybar/image.c
index de42e8e9..ed24b9f9 100644
--- a/common/background-image.c
+++ b/swaybar/image.c
@@ -1,29 +1,12 @@
1#include <assert.h> 1#include <assert.h>
2#include "background-image.h" 2#include "config.h"
3#include "cairo.h"
4#include "log.h" 3#include "log.h"
4#include "swaybar/image.h"
5
5#if HAVE_GDK_PIXBUF 6#if HAVE_GDK_PIXBUF
6#include <gdk-pixbuf/gdk-pixbuf.h> 7#include <gdk-pixbuf/gdk-pixbuf.h>
7#endif 8#endif
8 9
9enum background_mode parse_background_mode(const char *mode) {
10 if (strcmp(mode, "stretch") == 0) {
11 return BACKGROUND_MODE_STRETCH;
12 } else if (strcmp(mode, "fill") == 0) {
13 return BACKGROUND_MODE_FILL;
14 } else if (strcmp(mode, "fit") == 0) {
15 return BACKGROUND_MODE_FIT;
16 } else if (strcmp(mode, "center") == 0) {
17 return BACKGROUND_MODE_CENTER;
18 } else if (strcmp(mode, "tile") == 0) {
19 return BACKGROUND_MODE_TILE;
20 } else if (strcmp(mode, "solid_color") == 0) {
21 return BACKGROUND_MODE_SOLID_COLOR;
22 }
23 sway_log(SWAY_ERROR, "Unsupported background mode: %s", mode);
24 return BACKGROUND_MODE_INVALID;
25}
26
27#if HAVE_GDK_PIXBUF 10#if HAVE_GDK_PIXBUF
28static cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf( 11static cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(
29 const GdkPixbuf *gdkbuf) { 12 const GdkPixbuf *gdkbuf) {
@@ -121,7 +104,7 @@ static cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(
121} 104}
122#endif // HAVE_GDK_PIXBUF 105#endif // HAVE_GDK_PIXBUF
123 106
124cairo_surface_t *load_background_image(const char *path) { 107cairo_surface_t *load_image(const char *path) {
125 cairo_surface_t *image; 108 cairo_surface_t *image;
126#if HAVE_GDK_PIXBUF 109#if HAVE_GDK_PIXBUF
127 GError *err = NULL; 110 GError *err = NULL;
@@ -151,70 +134,3 @@ cairo_surface_t *load_background_image(const char *path) {
151 } 134 }
152 return image; 135 return image;
153} 136}
154
155void render_background_image(cairo_t *cairo, cairo_surface_t *image,
156 enum background_mode mode, int buffer_width, int buffer_height) {
157 double width = cairo_image_surface_get_width(image);
158 double height = cairo_image_surface_get_height(image);
159
160 cairo_save(cairo);
161 switch (mode) {
162 case BACKGROUND_MODE_STRETCH:
163 cairo_scale(cairo,
164 (double)buffer_width / width,
165 (double)buffer_height / height);
166 cairo_set_source_surface(cairo, image, 0, 0);
167 break;
168 case BACKGROUND_MODE_FILL: {
169 double window_ratio = (double)buffer_width / buffer_height;
170 double bg_ratio = width / height;
171
172 if (window_ratio > bg_ratio) {
173 double scale = (double)buffer_width / width;
174 cairo_scale(cairo, scale, scale);
175 cairo_set_source_surface(cairo, image,
176 0, (double)buffer_height / 2 / scale - height / 2);
177 } else {
178 double scale = (double)buffer_height / height;
179 cairo_scale(cairo, scale, scale);
180 cairo_set_source_surface(cairo, image,
181 (double)buffer_width / 2 / scale - width / 2, 0);
182 }
183 break;
184 }
185 case BACKGROUND_MODE_FIT: {
186 double window_ratio = (double)buffer_width / buffer_height;
187 double bg_ratio = width / height;
188
189 if (window_ratio > bg_ratio) {
190 double scale = (double)buffer_height / height;
191 cairo_scale(cairo, scale, scale);
192 cairo_set_source_surface(cairo, image,
193 (double)buffer_width / 2 / scale - width / 2, 0);
194 } else {
195 double scale = (double)buffer_width / width;
196 cairo_scale(cairo, scale, scale);
197 cairo_set_source_surface(cairo, image,
198 0, (double)buffer_height / 2 / scale - height / 2);
199 }
200 break;
201 }
202 case BACKGROUND_MODE_CENTER:
203 cairo_set_source_surface(cairo, image,
204 (double)buffer_width / 2 - width / 2,
205 (double)buffer_height / 2 - height / 2);
206 break;
207 case BACKGROUND_MODE_TILE: {
208 cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
209 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
210 cairo_set_source(cairo, pattern);
211 break;
212 }
213 case BACKGROUND_MODE_SOLID_COLOR:
214 case BACKGROUND_MODE_INVALID:
215 assert(0);
216 break;
217 }
218 cairo_paint(cairo);
219 cairo_restore(cairo);
220}
diff --git a/swaybar/input.c b/swaybar/input.c
index 4fe6dd93..ada4bc86 100644
--- a/swaybar/input.c
+++ b/swaybar/input.c
@@ -81,8 +81,16 @@ void update_cursor(struct swaybar_seat *seat) {
81 int scale = pointer->current ? pointer->current->scale : 1; 81 int scale = pointer->current ? pointer->current->scale : 1;
82 pointer->cursor_theme = wl_cursor_theme_load( 82 pointer->cursor_theme = wl_cursor_theme_load(
83 cursor_theme, cursor_size * scale, seat->bar->shm); 83 cursor_theme, cursor_size * scale, seat->bar->shm);
84 if (!pointer->cursor_theme) {
85 sway_log(SWAY_ERROR, "Failed to load cursor theme");
86 return;
87 }
84 struct wl_cursor *cursor; 88 struct wl_cursor *cursor;
85 cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); 89 cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "default");
90 if (!cursor) {
91 sway_log(SWAY_ERROR, "Failed to get default cursor from theme");
92 return;
93 }
86 pointer->cursor_image = cursor->images[0]; 94 pointer->cursor_image = cursor->images[0];
87 wl_surface_set_buffer_scale(pointer->cursor_surface, scale); 95 wl_surface_set_buffer_scale(pointer->cursor_surface, scale);
88 wl_surface_attach(pointer->cursor_surface, 96 wl_surface_attach(pointer->cursor_surface,
@@ -101,7 +109,9 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
101 wl_fixed_t surface_x, wl_fixed_t surface_y) { 109 wl_fixed_t surface_x, wl_fixed_t surface_y) {
102 struct swaybar_seat *seat = data; 110 struct swaybar_seat *seat = data;
103 struct swaybar_pointer *pointer = &seat->pointer; 111 struct swaybar_pointer *pointer = &seat->pointer;
104 pointer->serial = serial; 112 seat->pointer.x = wl_fixed_to_double(surface_x);
113 seat->pointer.y = wl_fixed_to_double(surface_y);
114
105 struct swaybar_output *output; 115 struct swaybar_output *output;
106 wl_list_for_each(output, &seat->bar->outputs, link) { 116 wl_list_for_each(output, &seat->bar->outputs, link) {
107 if (output->surface == surface) { 117 if (output->surface == surface) {
@@ -109,7 +119,18 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
109 break; 119 break;
110 } 120 }
111 } 121 }
112 update_cursor(seat); 122
123 if (seat->bar->cursor_shape_manager) {
124 struct wp_cursor_shape_device_v1 *device =
125 wp_cursor_shape_manager_v1_get_pointer(
126 seat->bar->cursor_shape_manager, wl_pointer);
127 wp_cursor_shape_device_v1_set_shape(device, serial,
128 WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
129 wp_cursor_shape_device_v1_destroy(device);
130 } else {
131 pointer->serial = serial;
132 update_cursor(seat);
133 }
113} 134}
114 135
115static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer, 136static void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
@@ -139,16 +160,15 @@ static bool check_bindings(struct swaybar *bar, uint32_t button,
139} 160}
140 161
141static bool process_hotspots(struct swaybar_output *output, 162static bool process_hotspots(struct swaybar_output *output,
142 double x, double y, uint32_t button) { 163 double x, double y, uint32_t button, uint32_t state) {
143 double px = x * output->scale; 164 bool released = state == WL_POINTER_BUTTON_STATE_RELEASED;
144 double py = y * output->scale;
145 struct swaybar_hotspot *hotspot; 165 struct swaybar_hotspot *hotspot;
146 wl_list_for_each(hotspot, &output->hotspots, link) { 166 wl_list_for_each(hotspot, &output->hotspots, link) {
147 if (px >= hotspot->x && py >= hotspot->y 167 if (x >= hotspot->x && y >= hotspot->y
148 && px < hotspot->x + hotspot->width 168 && x < hotspot->x + hotspot->width
149 && py < hotspot->y + hotspot->height) { 169 && y < hotspot->y + hotspot->height) {
150 if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y, 170 if (HOTSPOT_IGNORE == hotspot->callback(output, hotspot, x, y,
151 button, hotspot->data)) { 171 button, released, hotspot->data)) {
152 return true; 172 return true;
153 } 173 }
154 } 174 }
@@ -166,14 +186,11 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
166 return; 186 return;
167 } 187 }
168 188
169 if (check_bindings(seat->bar, button, state)) { 189 if (process_hotspots(output, pointer->x, pointer->y, button, state)) {
170 return; 190 return;
171 } 191 }
172 192
173 if (state != WL_POINTER_BUTTON_STATE_PRESSED) { 193 check_bindings(seat->bar, button, state);
174 return;
175 }
176 process_hotspots(output, pointer->x, pointer->y, button);
177} 194}
178 195
179static void workspace_next(struct swaybar *bar, struct swaybar_output *output, 196static void workspace_next(struct swaybar *bar, struct swaybar_output *output,
@@ -209,7 +226,7 @@ static void workspace_next(struct swaybar *bar, struct swaybar_output *output,
209 } 226 }
210 } 227 }
211 228
212 if (new) { 229 if (new && new != active) {
213 ipc_send_workspace_command(bar, new->name); 230 ipc_send_workspace_command(bar, new->name);
214 231
215 // Since we're asking Sway to switch to 'new', it should become visible. 232 // Since we're asking Sway to switch to 'new', it should become visible.
@@ -222,15 +239,15 @@ static void workspace_next(struct swaybar *bar, struct swaybar_output *output,
222static void process_discrete_scroll(struct swaybar_seat *seat, 239static void process_discrete_scroll(struct swaybar_seat *seat,
223 struct swaybar_output *output, struct swaybar_pointer *pointer, 240 struct swaybar_output *output, struct swaybar_pointer *pointer,
224 uint32_t axis, wl_fixed_t value) { 241 uint32_t axis, wl_fixed_t value) {
225 // If there is a button press binding, execute it, skip default behavior,
226 // and check button release bindings
227 uint32_t button = wl_axis_to_button(axis, value); 242 uint32_t button = wl_axis_to_button(axis, value);
228 if (check_bindings(seat->bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) { 243 if (process_hotspots(output, pointer->x, pointer->y, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
229 check_bindings(seat->bar, button, WL_POINTER_BUTTON_STATE_RELEASED); 244 // (Currently hotspots don't do anything on release events, so no need to emit one)
230 return; 245 return;
231 } 246 }
232 247
233 if (process_hotspots(output, pointer->x, pointer->y, button)) { 248 // If there is a button press binding, execute it, and check button release bindings
249 if (check_bindings(seat->bar, button, WL_POINTER_BUTTON_STATE_PRESSED)) {
250 check_bindings(seat->bar, button, WL_POINTER_BUTTON_STATE_RELEASED);
234 return; 251 return;
235 } 252 }
236 253
@@ -339,7 +356,7 @@ static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
339 seat->axis[axis].discrete_steps += abs(discrete); 356 seat->axis[axis].discrete_steps += abs(discrete);
340} 357}
341 358
342static struct wl_pointer_listener pointer_listener = { 359static const struct wl_pointer_listener pointer_listener = {
343 .enter = wl_pointer_enter, 360 .enter = wl_pointer_enter,
344 .leave = wl_pointer_leave, 361 .leave = wl_pointer_leave,
345 .motion = wl_pointer_motion, 362 .motion = wl_pointer_motion,
@@ -403,7 +420,8 @@ static void wl_touch_up(void *data, struct wl_touch *wl_touch,
403 } 420 }
404 if (time - slot->time < 500) { 421 if (time - slot->time < 500) {
405 // Tap, treat it like a pointer click 422 // Tap, treat it like a pointer click
406 process_hotspots(slot->output, slot->x, slot->y, BTN_LEFT); 423 process_hotspots(slot->output, slot->x, slot->y, BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
424 // (Currently hotspots don't do anything on release events, so no need to emit one)
407 } 425 }
408 slot->output = NULL; 426 slot->output = NULL;
409} 427}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 6bbe9408..33ae6544 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -147,8 +147,10 @@ static bool ipc_parse_config(
147 147
148 json_object *font = json_object_object_get(bar_config, "font"); 148 json_object *font = json_object_object_get(bar_config, "font");
149 if (font) { 149 if (font) {
150 free(config->font); 150 pango_font_description_free(config->font_description);
151 config->font = parse_font(json_object_get_string(font)); 151 char *font_value = parse_font(json_object_get_string(font));
152 config->font_description = pango_font_description_from_string(font_value);
153 free(font_value);
152 } 154 }
153 155
154 json_object *gaps = json_object_object_get(bar_config, "gaps"); 156 json_object *gaps = json_object_object_get(bar_config, "gaps");
@@ -424,12 +426,9 @@ bool ipc_initialize(struct swaybar *bar) {
424 } 426 }
425 free(res); 427 free(res);
426 428
427 struct swaybar_config *config = bar->config; 429 char *subscribe =
428 char subscribe[128]; // suitably large buffer 430 "[ \"barconfig_update\", \"bar_state_update\", \"mode\", \"workspace\" ]";
429 len = snprintf(subscribe, 128, 431 len = strlen(subscribe);
430 "[ \"barconfig_update\" , \"bar_state_update\" %s %s ]",
431 config->binding_mode_indicator ? ", \"mode\"" : "",
432 config->workspace_buttons ? ", \"workspace\"" : "");
433 free(ipc_single_command(bar->ipc_event_socketfd, 432 free(ipc_single_command(bar->ipc_event_socketfd,
434 IPC_SUBSCRIBE, subscribe, &len)); 433 IPC_SUBSCRIBE, subscribe, &len));
435 return true; 434 return true;
@@ -485,8 +484,7 @@ static bool handle_barconfig_update(struct swaybar *bar, const char *payload,
485 destroy_layer_surface(output); 484 destroy_layer_surface(output);
486 wl_list_remove(&output->link); 485 wl_list_remove(&output->link);
487 wl_list_insert(&bar->unused_outputs, &output->link); 486 wl_list_insert(&bar->unused_outputs, &output->link);
488 } else if (!oldcfg->font || !newcfg->font || 487 } else if (!pango_font_description_equal(oldcfg->font_description, newcfg->font_description)) {
489 strcmp(oldcfg->font, newcfg->font) != 0) {
490 output->height = 0; // force update height 488 output->height = 0; // force update height
491 } 489 }
492 } 490 }
@@ -547,9 +545,23 @@ bool handle_ipc_readable(struct swaybar *bar) {
547 return false; 545 return false;
548 } 546 }
549 547
550 json_object *result = json_tokener_parse(resp->payload); 548 // The default depth of 32 is too small to represent some nested layouts, but
551 if (!result) { 549 // we can't pass INT_MAX here because json-c (as of this writing) prefaults
552 sway_log(SWAY_ERROR, "failed to parse payload as json"); 550 // all the memory for its stack.
551 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
552 if (!tok) {
553 sway_log_errno(SWAY_ERROR, "failed to create tokener");
554 free_ipc_response(resp);
555 return false;
556 }
557
558 json_object *result = json_tokener_parse_ex(tok, resp->payload, -1);
559 enum json_tokener_error err = json_tokener_get_error(tok);
560 json_tokener_free(tok);
561
562 if (err != json_tokener_success) {
563 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
564 json_tokener_error_desc(err));
553 free_ipc_response(resp); 565 free_ipc_response(resp);
554 return false; 566 return false;
555 } 567 }
diff --git a/swaybar/main.c b/swaybar/main.c
index 5c36d66b..a44c1e63 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -18,7 +18,7 @@ int main(int argc, char **argv) {
18 char *socket_path = NULL; 18 char *socket_path = NULL;
19 bool debug = false; 19 bool debug = false;
20 20
21 static struct option long_options[] = { 21 static const struct option long_options[] = {
22 {"help", no_argument, NULL, 'h'}, 22 {"help", no_argument, NULL, 'h'},
23 {"version", no_argument, NULL, 'v'}, 23 {"version", no_argument, NULL, 'v'},
24 {"socket", required_argument, NULL, 's'}, 24 {"socket", required_argument, NULL, 's'},
diff --git a/swaybar/meson.build b/swaybar/meson.build
index 9feb3cd2..34bbdeea 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -8,7 +8,6 @@ tray_files = have_tray ? [
8 8
9swaybar_deps = [ 9swaybar_deps = [
10 cairo, 10 cairo,
11 client_protos,
12 gdk_pixbuf, 11 gdk_pixbuf,
13 jsonc, 12 jsonc,
14 math, 13 math,
@@ -27,12 +26,14 @@ executable(
27 'bar.c', 26 'bar.c',
28 'config.c', 27 'config.c',
29 'i3bar.c', 28 'i3bar.c',
29 'image.c',
30 'input.c', 30 'input.c',
31 'ipc.c', 31 'ipc.c',
32 'main.c', 32 'main.c',
33 'render.c', 33 'render.c',
34 'status_line.c', 34 'status_line.c',
35 tray_files 35 tray_files,
36 wl_protos_src,
36 ], 37 ],
37 include_directories: [sway_inc], 38 include_directories: [sway_inc],
38 dependencies: swaybar_deps, 39 dependencies: swaybar_deps,
diff --git a/swaybar/render.c b/swaybar/render.c
index df066622..1113ca44 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -5,7 +5,7 @@
5#include <stdlib.h> 5#include <stdlib.h>
6#include <stdint.h> 6#include <stdint.h>
7#include <string.h> 7#include <string.h>
8#include "cairo.h" 8#include "cairo_util.h"
9#include "pango.h" 9#include "pango.h"
10#include "pool-buffer.h" 10#include "pool-buffer.h"
11#include "swaybar/bar.h" 11#include "swaybar/bar.h"
@@ -14,6 +14,7 @@
14#include "swaybar/ipc.h" 14#include "swaybar/ipc.h"
15#include "swaybar/render.h" 15#include "swaybar/render.h"
16#include "swaybar/status_line.h" 16#include "swaybar/status_line.h"
17#include "log.h"
17#if HAVE_TRAY 18#if HAVE_TRAY
18#include "swaybar/tray/tray.h" 19#include "swaybar/tray/tray.h"
19#endif 20#endif
@@ -23,28 +24,51 @@ static const int WS_HORIZONTAL_PADDING = 5;
23static const double WS_VERTICAL_PADDING = 1.5; 24static const double WS_VERTICAL_PADDING = 1.5;
24static const double BORDER_WIDTH = 1; 25static const double BORDER_WIDTH = 1;
25 26
26static uint32_t render_status_line_error(cairo_t *cairo, 27struct render_context {
27 struct swaybar_output *output, double *x) { 28 cairo_t *cairo;
29 struct swaybar_output *output;
30 cairo_font_options_t *textaa_sharp;
31 cairo_font_options_t *textaa_safe;
32 uint32_t background_color;
33};
34
35static void choose_text_aa_mode(struct render_context *ctx, uint32_t fontcolor) {
36 uint32_t salpha = fontcolor & 0xFF;
37 uint32_t balpha = ctx->background_color & 0xFF;
38
39 // Subpixel antialiasing requires blend be done in cairo, not compositor
40 cairo_font_options_t *fo = salpha == balpha ?
41 ctx->textaa_sharp : ctx->textaa_safe;
42 cairo_set_font_options(ctx->cairo, fo);
43
44 // Color emojis, being semitransparent bitmaps, are leaky with 'SOURCE'
45 cairo_operator_t op = salpha == 0xFF ?
46 CAIRO_OPERATOR_OVER : CAIRO_OPERATOR_SOURCE;
47 cairo_set_operator(ctx->cairo, op);
48}
49
50static uint32_t render_status_line_error(struct render_context *ctx, double *x) {
51 struct swaybar_output *output = ctx->output;
28 const char *error = output->bar->status->text; 52 const char *error = output->bar->status->text;
29 if (!error) { 53 if (!error) {
30 return 0; 54 return 0;
31 } 55 }
32 56
33 uint32_t height = output->height * output->scale; 57 uint32_t height = output->height;
34 58
59 cairo_t *cairo = ctx->cairo;
35 cairo_set_source_u32(cairo, 0xFF0000FF); 60 cairo_set_source_u32(cairo, 0xFF0000FF);
36 61
37 int margin = 3 * output->scale; 62 int margin = 3;
38 double ws_vertical_padding = 63 double ws_vertical_padding = output->bar->config->status_padding;
39 output->bar->config->status_padding * output->scale;
40 64
41 char *font = output->bar->config->font; 65 PangoFontDescription *font = output->bar->config->font_description;
42 int text_width, text_height; 66 int text_width, text_height;
43 get_text_size(cairo, font, &text_width, &text_height, NULL, 67 get_text_size(cairo, font, &text_width, &text_height, NULL,
44 output->scale, false, "%s", error); 68 1, false, "%s", error);
45 69
46 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 70 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
47 uint32_t ideal_surface_height = ideal_height / output->scale; 71 uint32_t ideal_surface_height = ideal_height;
48 if (!output->bar->config->height && 72 if (!output->bar->config->height &&
49 output->height < ideal_surface_height) { 73 output->height < ideal_surface_height) {
50 return ideal_surface_height; 74 return ideal_surface_height;
@@ -53,42 +77,45 @@ static uint32_t render_status_line_error(cairo_t *cairo,
53 77
54 double text_y = height / 2.0 - text_height / 2.0; 78 double text_y = height / 2.0 - text_height / 2.0;
55 cairo_move_to(cairo, *x, (int)floor(text_y)); 79 cairo_move_to(cairo, *x, (int)floor(text_y));
56 pango_printf(cairo, font, output->scale, false, "%s", error); 80 choose_text_aa_mode(ctx, 0xFF0000FF);
81 render_text(cairo, font, 1, false, "%s", error);
57 *x -= margin; 82 *x -= margin;
58 return output->height; 83 return output->height;
59} 84}
60 85
61static uint32_t render_status_line_text(cairo_t *cairo, 86static uint32_t render_status_line_text(struct render_context *ctx, double *x) {
62 struct swaybar_output *output, double *x) { 87 struct swaybar_output *output = ctx->output;
63 const char *text = output->bar->status->text; 88 const char *text = output->bar->status->text;
64 if (!text) { 89 if (!text) {
65 return 0; 90 return 0;
66 } 91 }
67 92
93 cairo_t *cairo = ctx->cairo;
68 struct swaybar_config *config = output->bar->config; 94 struct swaybar_config *config = output->bar->config;
69 cairo_set_source_u32(cairo, output->focused ? 95 uint32_t fontcolor = output->focused ?
70 config->colors.focused_statusline : config->colors.statusline); 96 config->colors.focused_statusline : config->colors.statusline;
97 cairo_set_source_u32(cairo, fontcolor);
71 98
72 int text_width, text_height; 99 int text_width, text_height;
73 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 100 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
74 output->scale, config->pango_markup, "%s", text); 101 1, config->pango_markup, "%s", text);
75 102
76 double ws_vertical_padding = config->status_padding * output->scale; 103 double ws_vertical_padding = config->status_padding;
77 int margin = 3 * output->scale; 104 int margin = 3;
78 105
79 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 106 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
80 uint32_t ideal_surface_height = ideal_height / output->scale; 107 uint32_t ideal_surface_height = ideal_height;
81 if (!output->bar->config->height && 108 if (!output->bar->config->height &&
82 output->height < ideal_surface_height) { 109 output->height < ideal_surface_height) {
83 return ideal_surface_height; 110 return ideal_surface_height;
84 } 111 }
85 112
86 *x -= text_width + margin; 113 *x -= text_width + margin;
87 uint32_t height = output->height * output->scale; 114 uint32_t height = output->height;
88 double text_y = height / 2.0 - text_height / 2.0; 115 double text_y = height / 2.0 - text_height / 2.0;
89 cairo_move_to(cairo, *x, (int)floor(text_y)); 116 cairo_move_to(cairo, *x, (int)floor(text_y));
90 pango_printf(cairo, config->font, output->scale, 117 choose_text_aa_mode(ctx, fontcolor);
91 config->pango_markup, "%s", text); 118 render_text(cairo, config->font_description, 1, config->pango_markup, "%s", text);
92 *x -= margin; 119 *x -= margin;
93 return output->height; 120 return output->height;
94} 121}
@@ -96,6 +123,7 @@ static uint32_t render_status_line_text(cairo_t *cairo,
96static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, 123static void render_sharp_rectangle(cairo_t *cairo, uint32_t color,
97 double x, double y, double width, double height) { 124 double x, double y, double width, double height) {
98 cairo_save(cairo); 125 cairo_save(cairo);
126 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
99 cairo_set_source_u32(cairo, color); 127 cairo_set_source_u32(cairo, color);
100 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); 128 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
101 cairo_rectangle(cairo, x, y, width, height); 129 cairo_rectangle(cairo, x, y, width, height);
@@ -109,6 +137,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
109 render_sharp_rectangle(cairo, color, x, y, width, height); 137 render_sharp_rectangle(cairo, color, x, y, width, height);
110 } else { 138 } else {
111 cairo_save(cairo); 139 cairo_save(cairo);
140 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
112 cairo_set_source_u32(cairo, color); 141 cairo_set_source_u32(cairo, color);
113 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); 142 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
114 if (width == 1) { 143 if (width == 1) {
@@ -131,24 +160,23 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
131 160
132static enum hotspot_event_handling block_hotspot_callback( 161static enum hotspot_event_handling block_hotspot_callback(
133 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 162 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
134 double x, double y, uint32_t button, void *data) { 163 double x, double y, uint32_t button, bool released, void *data) {
135 struct i3bar_block *block = data; 164 struct i3bar_block *block = data;
136 struct status_line *status = output->bar->status; 165 struct status_line *status = output->bar->status;
137 return i3bar_block_send_click(status, block, x, y, 166 return i3bar_block_send_click(status, block, x, y,
138 x - (double)hotspot->x / output->scale, 167 x - (double)hotspot->x,
139 y - (double)hotspot->y / output->scale, 168 y - (double)hotspot->y,
140 (double)hotspot->width / output->scale, 169 (double)hotspot->width,
141 (double)hotspot->height / output->scale, 170 (double)hotspot->height,
142 output->scale, button); 171 output->scale, button, released);
143} 172}
144 173
145static void i3bar_block_unref_callback(void *data) { 174static void i3bar_block_unref_callback(void *data) {
146 i3bar_block_unref(data); 175 i3bar_block_unref(data);
147} 176}
148 177
149static uint32_t render_status_block(cairo_t *cairo, 178static uint32_t render_status_block(struct render_context *ctx,
150 struct swaybar_output *output, struct i3bar_block *block, double *x, 179 struct i3bar_block *block, double *x, bool edge, bool use_short_text) {
151 bool edge, bool use_short_text) {
152 if (!block->full_text || !*block->full_text) { 180 if (!block->full_text || !*block->full_text) {
153 return 0; 181 return 0;
154 } 182 }
@@ -158,20 +186,21 @@ static uint32_t render_status_block(cairo_t *cairo,
158 text = block->short_text; 186 text = block->short_text;
159 } 187 }
160 188
189 cairo_t *cairo = ctx->cairo;
190 struct swaybar_output *output = ctx->output;
161 struct swaybar_config *config = output->bar->config; 191 struct swaybar_config *config = output->bar->config;
162
163 int text_width, text_height; 192 int text_width, text_height;
164 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 193 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
165 output->scale, block->markup, "%s", text); 194 block->markup, "%s", text);
166 195
167 int margin = 3 * output->scale; 196 int margin = 3;
168 double ws_vertical_padding = config->status_padding * output->scale; 197 double ws_vertical_padding = config->status_padding;
169 198
170 int width = text_width; 199 int width = text_width;
171 if (block->min_width_str) { 200 if (block->min_width_str) {
172 int w; 201 int w;
173 get_text_size(cairo, config->font, &w, NULL, NULL, 202 get_text_size(cairo, config->font_description, &w, NULL, NULL, 1, block->markup,
174 output->scale, block->markup, "%s", block->min_width_str); 203 "%s", block->min_width_str);
175 block->min_width = w; 204 block->min_width = w;
176 } 205 }
177 if (width < block->min_width) { 206 if (width < block->min_width) {
@@ -180,30 +209,30 @@ static uint32_t render_status_block(cairo_t *cairo,
180 209
181 double block_width = width; 210 double block_width = width;
182 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 211 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
183 uint32_t ideal_surface_height = ideal_height / output->scale; 212 uint32_t ideal_surface_height = ideal_height;
184 if (!output->bar->config->height && 213 if (!output->bar->config->height &&
185 output->height < ideal_surface_height) { 214 output->height < ideal_surface_height) {
186 return ideal_surface_height; 215 return ideal_surface_height;
187 } 216 }
188 217
189 *x -= width; 218 *x -= width;
190 if ((block->border || block->urgent) && block->border_left > 0) { 219 if ((block->border_set || block->urgent) && block->border_left > 0) {
191 *x -= (block->border_left * output->scale + margin); 220 *x -= (block->border_left + margin);
192 block_width += block->border_left * output->scale + margin; 221 block_width += block->border_left + margin;
193 } 222 }
194 if ((block->border || block->urgent) && block->border_right > 0) { 223 if ((block->border_set || block->urgent) && block->border_right > 0) {
195 *x -= (block->border_right * output->scale + margin); 224 *x -= (block->border_right + margin);
196 block_width += block->border_right * output->scale + margin; 225 block_width += block->border_right + margin;
197 } 226 }
198 227
199 int sep_width, sep_height; 228 int sep_width, sep_height;
200 int sep_block_width = block->separator_block_width; 229 int sep_block_width = block->separator_block_width;
201 if (!edge) { 230 if (!edge) {
202 if (config->sep_symbol) { 231 if (config->sep_symbol) {
203 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, 232 get_text_size(cairo, config->font_description, &sep_width, &sep_height, NULL,
204 output->scale, false, "%s", config->sep_symbol); 233 1, false, "%s", config->sep_symbol);
205 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; 234 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
206 uint32_t _ideal_surface_height = _ideal_height / output->scale; 235 uint32_t _ideal_surface_height = _ideal_height;
207 if (!output->bar->config->height && 236 if (!output->bar->config->height &&
208 output->height < _ideal_surface_height) { 237 output->height < _ideal_surface_height) {
209 return _ideal_surface_height; 238 return _ideal_surface_height;
@@ -214,10 +243,10 @@ static uint32_t render_status_block(cairo_t *cairo,
214 } 243 }
215 *x -= sep_block_width; 244 *x -= sep_block_width;
216 } else if (config->status_edge_padding) { 245 } else if (config->status_edge_padding) {
217 *x -= config->status_edge_padding * output->scale; 246 *x -= config->status_edge_padding;
218 } 247 }
219 248
220 uint32_t height = output->height * output->scale; 249 uint32_t height = output->height;
221 if (output->bar->status->click_events) { 250 if (output->bar->status->click_events) {
222 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 251 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
223 hotspot->x = *x; 252 hotspot->x = *x;
@@ -240,27 +269,30 @@ static uint32_t render_status_block(cairo_t *cairo,
240 if (bg_color) { 269 if (bg_color) {
241 render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, 270 render_sharp_rectangle(cairo, bg_color, x_pos, y_pos,
242 block_width, render_height); 271 block_width, render_height);
272 ctx->background_color = bg_color;
243 } 273 }
244 274
245 uint32_t border_color = block->urgent 275 uint32_t border_color = block->urgent
246 ? config->colors.urgent_workspace.border : block->border; 276 ? config->colors.urgent_workspace.border : block->border;
247 if (border_color && block->border_top > 0) { 277 if (block->border_set || block->urgent) {
248 render_sharp_line(cairo, border_color, x_pos, y_pos, 278 if (block->border_top > 0) {
249 block_width, block->border_top * output->scale); 279 render_sharp_line(cairo, border_color, x_pos, y_pos,
250 } 280 block_width, block->border_top);
251 if (border_color && block->border_bottom > 0) { 281 }
252 render_sharp_line(cairo, border_color, x_pos, 282 if (block->border_bottom > 0) {
253 y_pos + render_height - block->border_bottom * output->scale, 283 render_sharp_line(cairo, border_color, x_pos,
254 block_width, block->border_bottom * output->scale); 284 y_pos + render_height - block->border_bottom,
255 } 285 block_width, block->border_bottom);
256 if (border_color && block->border_left > 0) { 286 }
257 render_sharp_line(cairo, border_color, x_pos, y_pos, 287 if (block->border_left > 0) {
258 block->border_left * output->scale, render_height); 288 render_sharp_line(cairo, border_color, x_pos, y_pos,
259 x_pos += block->border_left * output->scale + margin; 289 block->border_left, render_height);
290 }
291 x_pos += block->border_left + margin;
260 } 292 }
261 293
262 double offset = 0; 294 double offset = 0;
263 if (strncmp(block->align, "left", 5) == 0) { 295 if (strncmp(block->align, "left", 4) == 0) {
264 offset = x_pos; 296 offset = x_pos;
265 } else if (strncmp(block->align, "right", 5) == 0) { 297 } else if (strncmp(block->align, "right", 5) == 0) {
266 offset = x_pos + width - text_width; 298 offset = x_pos + width - text_width;
@@ -274,30 +306,35 @@ static uint32_t render_status_block(cairo_t *cairo,
274 color = block->color_set ? block->color : color; 306 color = block->color_set ? block->color : color;
275 color = block->urgent ? config->colors.urgent_workspace.text : color; 307 color = block->urgent ? config->colors.urgent_workspace.text : color;
276 cairo_set_source_u32(cairo, color); 308 cairo_set_source_u32(cairo, color);
277 pango_printf(cairo, config->font, output->scale, 309 choose_text_aa_mode(ctx, color);
278 block->markup, "%s", text); 310 render_text(cairo, config->font_description, 1, block->markup, "%s", text);
279 x_pos += width; 311 x_pos += width;
280 312
281 if (block->border && block->border_right > 0) { 313 if (block->border_set || block->urgent) {
282 x_pos += margin; 314 x_pos += margin;
283 render_sharp_line(cairo, border_color, x_pos, y_pos, 315 if (block->border_right > 0) {
284 block->border_right * output->scale, render_height); 316 render_sharp_line(cairo, border_color, x_pos, y_pos,
285 x_pos += block->border_right * output->scale; 317 block->border_right, render_height);
318 }
319 x_pos += block->border_right;
286 } 320 }
287 321
288 if (!edge && block->separator) { 322 if (!edge && block->separator) {
289 if (output->focused) { 323 if (output->focused) {
290 cairo_set_source_u32(cairo, config->colors.focused_separator); 324 color = config->colors.focused_separator;
291 } else { 325 } else {
292 cairo_set_source_u32(cairo, config->colors.separator); 326 color = config->colors.separator;
293 } 327 }
328 cairo_set_source_u32(cairo, color);
294 if (config->sep_symbol) { 329 if (config->sep_symbol) {
295 offset = x_pos + (sep_block_width - sep_width) / 2; 330 offset = x_pos + (sep_block_width - sep_width) / 2;
296 double sep_y = height / 2.0 - sep_height / 2.0; 331 double sep_y = height / 2.0 - sep_height / 2.0;
297 cairo_move_to(cairo, offset, (int)floor(sep_y)); 332 cairo_move_to(cairo, offset, (int)floor(sep_y));
298 pango_printf(cairo, config->font, output->scale, false, 333 choose_text_aa_mode(ctx, color);
334 render_text(cairo, config->font_description, 1, false,
299 "%s", config->sep_symbol); 335 "%s", config->sep_symbol);
300 } else { 336 } else {
337 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
301 cairo_set_line_width(cairo, 1); 338 cairo_set_line_width(cairo, 1);
302 cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); 339 cairo_move_to(cairo, x_pos + sep_block_width / 2, margin);
303 cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); 340 cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin);
@@ -317,18 +354,18 @@ static void predict_status_block_pos(cairo_t *cairo,
317 struct swaybar_config *config = output->bar->config; 354 struct swaybar_config *config = output->bar->config;
318 355
319 int text_width, text_height; 356 int text_width, text_height;
320 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 357 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
321 output->scale, block->markup, "%s", block->full_text); 358 block->markup, "%s", block->full_text);
322 359
323 int margin = 3 * output->scale; 360 int margin = 3;
324 double ws_vertical_padding = config->status_padding * output->scale; 361 double ws_vertical_padding = config->status_padding;
325 362
326 int width = text_width; 363 int width = text_width;
327 364
328 if (block->min_width_str) { 365 if (block->min_width_str) {
329 int w; 366 int w;
330 get_text_size(cairo, config->font, &w, NULL, NULL, 367 get_text_size(cairo, config->font_description, &w, NULL, NULL,
331 output->scale, block->markup, "%s", block->min_width_str); 368 1, block->markup, "%s", block->min_width_str);
332 block->min_width = w; 369 block->min_width = w;
333 } 370 }
334 if (width < block->min_width) { 371 if (width < block->min_width) {
@@ -336,28 +373,28 @@ static void predict_status_block_pos(cairo_t *cairo,
336 } 373 }
337 374
338 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 375 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
339 uint32_t ideal_surface_height = ideal_height / output->scale; 376 uint32_t ideal_surface_height = ideal_height;
340 if (!output->bar->config->height && 377 if (!output->bar->config->height &&
341 output->height < ideal_surface_height) { 378 output->height < ideal_surface_height) {
342 return; 379 return;
343 } 380 }
344 381
345 *x -= width; 382 *x -= width;
346 if ((block->border || block->urgent) && block->border_left > 0) { 383 if ((block->border_set || block->urgent) && block->border_left > 0) {
347 *x -= (block->border_left * output->scale + margin); 384 *x -= (block->border_left + margin);
348 } 385 }
349 if ((block->border || block->urgent) && block->border_right > 0) { 386 if ((block->border_set || block->urgent) && block->border_right > 0) {
350 *x -= (block->border_right * output->scale + margin); 387 *x -= (block->border_right + margin);
351 } 388 }
352 389
353 int sep_width, sep_height; 390 int sep_width, sep_height;
354 int sep_block_width = block->separator_block_width; 391 int sep_block_width = block->separator_block_width;
355 if (!edge) { 392 if (!edge) {
356 if (config->sep_symbol) { 393 if (config->sep_symbol) {
357 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, 394 get_text_size(cairo, config->font_description, &sep_width, &sep_height, NULL,
358 output->scale, false, "%s", config->sep_symbol); 395 1, false, "%s", config->sep_symbol);
359 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; 396 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
360 uint32_t _ideal_surface_height = _ideal_height / output->scale; 397 uint32_t _ideal_surface_height = _ideal_height;
361 if (!output->bar->config->height && 398 if (!output->bar->config->height &&
362 output->height < _ideal_surface_height) { 399 output->height < _ideal_surface_height) {
363 return; 400 return;
@@ -368,13 +405,13 @@ static void predict_status_block_pos(cairo_t *cairo,
368 } 405 }
369 *x -= sep_block_width; 406 *x -= sep_block_width;
370 } else if (config->status_edge_padding) { 407 } else if (config->status_edge_padding) {
371 *x -= config->status_edge_padding * output->scale; 408 *x -= config->status_edge_padding;
372 } 409 }
373} 410}
374 411
375static double predict_status_line_pos(cairo_t *cairo, 412static double predict_status_line_pos(cairo_t *cairo,
376 struct swaybar_output *output, double x) { 413 struct swaybar_output *output, double x) {
377 bool edge = x == output->width * output->scale; 414 bool edge = x == output->width;
378 struct i3bar_block *block; 415 struct i3bar_block *block;
379 wl_list_for_each(block, &output->bar->status->blocks, link) { 416 wl_list_for_each(block, &output->bar->status->blocks, link) {
380 predict_status_block_pos(cairo, output, block, &x, edge); 417 predict_status_block_pos(cairo, output, block, &x, edge);
@@ -389,24 +426,24 @@ static uint32_t predict_workspace_button_length(cairo_t *cairo,
389 struct swaybar_config *config = output->bar->config; 426 struct swaybar_config *config = output->bar->config;
390 427
391 int text_width, text_height; 428 int text_width, text_height;
392 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 429 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
393 output->scale, config->pango_markup, "%s", ws->label); 430 config->pango_markup, "%s", ws->label);
394 431
395 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 432 int ws_vertical_padding = WS_VERTICAL_PADDING;
396 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 433 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
397 int border_width = BORDER_WIDTH * output->scale; 434 int border_width = BORDER_WIDTH;
398 435
399 uint32_t ideal_height = ws_vertical_padding * 2 + text_height 436 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
400 + border_width * 2; 437 + border_width * 2;
401 uint32_t ideal_surface_height = ideal_height / output->scale; 438 uint32_t ideal_surface_height = ideal_height;
402 if (!output->bar->config->height && 439 if (!output->bar->config->height &&
403 output->height < ideal_surface_height) { 440 output->height < ideal_surface_height) {
404 return 0; 441 return 0;
405 } 442 }
406 443
407 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 444 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
408 if (width < config->workspace_min_width * output->scale) { 445 if (width < config->workspace_min_width) {
409 width = config->workspace_min_width * output->scale; 446 width = config->workspace_min_width;
410 } 447 }
411 return width; 448 return width;
412} 449}
@@ -437,39 +474,40 @@ static uint32_t predict_binding_mode_indicator_length(cairo_t *cairo,
437 } 474 }
438 475
439 int text_width, text_height; 476 int text_width, text_height;
440 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 477 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
441 output->scale, output->bar->mode_pango_markup, 478 1, output->bar->mode_pango_markup,
442 "%s", mode); 479 "%s", mode);
443 480
444 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 481 int ws_vertical_padding = WS_VERTICAL_PADDING;
445 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 482 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
446 int border_width = BORDER_WIDTH * output->scale; 483 int border_width = BORDER_WIDTH;
447 484
448 uint32_t ideal_height = text_height + ws_vertical_padding * 2 485 uint32_t ideal_height = text_height + ws_vertical_padding * 2
449 + border_width * 2; 486 + border_width * 2;
450 uint32_t ideal_surface_height = ideal_height / output->scale; 487 uint32_t ideal_surface_height = ideal_height;
451 if (!output->bar->config->height && 488 if (!output->bar->config->height &&
452 output->height < ideal_surface_height) { 489 output->height < ideal_surface_height) {
453 return 0; 490 return 0;
454 } 491 }
455 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 492 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
456 if (width < config->workspace_min_width * output->scale) { 493 if (width < config->workspace_min_width) {
457 width = config->workspace_min_width * output->scale; 494 width = config->workspace_min_width;
458 } 495 }
459 return width; 496 return width;
460} 497}
461 498
462static uint32_t render_status_line_i3bar(cairo_t *cairo, 499static uint32_t render_status_line_i3bar(struct render_context *ctx, double *x) {
463 struct swaybar_output *output, double *x) { 500 struct swaybar_output *output = ctx->output;
464 uint32_t max_height = 0; 501 uint32_t max_height = 0;
465 bool edge = *x == output->width * output->scale; 502 bool edge = *x == output->width;
466 struct i3bar_block *block; 503 struct i3bar_block *block;
467 bool use_short_text = false; 504 bool use_short_text = false;
468 505
506 cairo_t *cairo = ctx->cairo;
469 double reserved_width = 507 double reserved_width =
470 predict_workspace_buttons_length(cairo, output) + 508 predict_workspace_buttons_length(cairo, output) +
471 predict_binding_mode_indicator_length(cairo, output) + 509 predict_binding_mode_indicator_length(cairo, output) +
472 3 * output->scale; // require a bit of space for margin 510 3; // require a bit of space for margin
473 511
474 double predicted_full_pos = 512 double predicted_full_pos =
475 predict_status_line_pos(cairo, output, *x); 513 predict_status_line_pos(cairo, output, *x);
@@ -479,7 +517,7 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
479 } 517 }
480 518
481 wl_list_for_each(block, &output->bar->status->blocks, link) { 519 wl_list_for_each(block, &output->bar->status->blocks, link) {
482 uint32_t h = render_status_block(cairo, output, block, x, edge, 520 uint32_t h = render_status_block(ctx, block, x, edge,
483 use_short_text); 521 use_short_text);
484 max_height = h > max_height ? h : max_height; 522 max_height = h > max_height ? h : max_height;
485 edge = false; 523 edge = false;
@@ -487,53 +525,56 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
487 return max_height; 525 return max_height;
488} 526}
489 527
490static uint32_t render_status_line(cairo_t *cairo, 528static uint32_t render_status_line(struct render_context *ctx, double *x) {
491 struct swaybar_output *output, double *x) { 529 struct status_line *status = ctx->output->bar->status;
492 struct status_line *status = output->bar->status;
493 switch (status->protocol) { 530 switch (status->protocol) {
494 case PROTOCOL_ERROR: 531 case PROTOCOL_ERROR:
495 return render_status_line_error(cairo, output, x); 532 return render_status_line_error(ctx, x);
496 case PROTOCOL_TEXT: 533 case PROTOCOL_TEXT:
497 return render_status_line_text(cairo, output, x); 534 return render_status_line_text(ctx, x);
498 case PROTOCOL_I3BAR: 535 case PROTOCOL_I3BAR:
499 return render_status_line_i3bar(cairo, output, x); 536 return render_status_line_i3bar(ctx, x);
500 case PROTOCOL_UNDEF: 537 case PROTOCOL_UNDEF:
501 return 0; 538 return 0;
502 } 539 }
503 return 0; 540 return 0;
504} 541}
505 542
506static uint32_t render_binding_mode_indicator(cairo_t *cairo, 543static uint32_t render_binding_mode_indicator(struct render_context *ctx,
507 struct swaybar_output *output, double x) { 544 double x) {
545 struct swaybar_output *output = ctx->output;
508 const char *mode = output->bar->mode; 546 const char *mode = output->bar->mode;
509 if (!mode) { 547 if (!mode) {
510 return 0; 548 return 0;
511 } 549 }
512 550
551 cairo_t *cairo = ctx->cairo;
513 struct swaybar_config *config = output->bar->config; 552 struct swaybar_config *config = output->bar->config;
514 int text_width, text_height; 553 int text_width, text_height;
515 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 554 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
516 output->scale, output->bar->mode_pango_markup, 555 1, output->bar->mode_pango_markup,
517 "%s", mode); 556 "%s", mode);
518 557
519 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 558 int ws_vertical_padding = WS_VERTICAL_PADDING;
520 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 559 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
521 int border_width = BORDER_WIDTH * output->scale; 560 int border_width = BORDER_WIDTH;
522 561
523 uint32_t ideal_height = text_height + ws_vertical_padding * 2 562 uint32_t ideal_height = text_height + ws_vertical_padding * 2
524 + border_width * 2; 563 + border_width * 2;
525 uint32_t ideal_surface_height = ideal_height / output->scale; 564 uint32_t ideal_surface_height = ideal_height;
526 if (!output->bar->config->height && 565 if (!output->bar->config->height &&
527 output->height < ideal_surface_height) { 566 output->height < ideal_surface_height) {
528 return ideal_surface_height; 567 return ideal_surface_height;
529 } 568 }
530 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 569 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
531 if (width < config->workspace_min_width * output->scale) { 570 if (width < config->workspace_min_width) {
532 width = config->workspace_min_width * output->scale; 571 width = config->workspace_min_width;
533 } 572 }
534 573
535 uint32_t height = output->height * output->scale; 574 uint32_t height = output->height;
575 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
536 cairo_set_source_u32(cairo, config->colors.binding_mode.background); 576 cairo_set_source_u32(cairo, config->colors.binding_mode.background);
577 ctx->background_color = config->colors.binding_mode.background;
537 cairo_rectangle(cairo, x, 0, width, height); 578 cairo_rectangle(cairo, x, 0, width, height);
538 cairo_fill(cairo); 579 cairo_fill(cairo);
539 580
@@ -550,24 +591,30 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
550 double text_y = height / 2.0 - text_height / 2.0; 591 double text_y = height / 2.0 - text_height / 2.0;
551 cairo_set_source_u32(cairo, config->colors.binding_mode.text); 592 cairo_set_source_u32(cairo, config->colors.binding_mode.text);
552 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); 593 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y));
553 pango_printf(cairo, config->font, output->scale, 594 choose_text_aa_mode(ctx, config->colors.binding_mode.text);
554 output->bar->mode_pango_markup, "%s", mode); 595 render_text(cairo, config->font_description, 1, output->bar->mode_pango_markup,
596 "%s", mode);
555 return output->height; 597 return output->height;
556} 598}
557 599
558static enum hotspot_event_handling workspace_hotspot_callback( 600static enum hotspot_event_handling workspace_hotspot_callback(
559 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 601 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
560 double x, double y, uint32_t button, void *data) { 602 double x, double y, uint32_t button, bool released, void *data) {
561 if (button != BTN_LEFT) { 603 if (button != BTN_LEFT) {
562 return HOTSPOT_PROCESS; 604 return HOTSPOT_PROCESS;
563 } 605 }
606 if (released) {
607 // Since we handle the pressed event, also handle the released event
608 // to block it from falling through to a binding in the bar
609 return HOTSPOT_IGNORE;
610 }
564 ipc_send_workspace_command(output->bar, (const char *)data); 611 ipc_send_workspace_command(output->bar, (const char *)data);
565 return HOTSPOT_IGNORE; 612 return HOTSPOT_IGNORE;
566} 613}
567 614
568static uint32_t render_workspace_button(cairo_t *cairo, 615static uint32_t render_workspace_button(struct render_context *ctx,
569 struct swaybar_output *output,
570 struct swaybar_workspace *ws, double *x) { 616 struct swaybar_workspace *ws, double *x) {
617 struct swaybar_output *output = ctx->output;
571 struct swaybar_config *config = output->bar->config; 618 struct swaybar_config *config = output->bar->config;
572 struct box_colors box_colors; 619 struct box_colors box_colors;
573 if (ws->urgent) { 620 if (ws->urgent) {
@@ -580,30 +627,33 @@ static uint32_t render_workspace_button(cairo_t *cairo,
580 box_colors = config->colors.inactive_workspace; 627 box_colors = config->colors.inactive_workspace;
581 } 628 }
582 629
583 uint32_t height = output->height * output->scale; 630 uint32_t height = output->height;
584 631
632 cairo_t *cairo = ctx->cairo;
585 int text_width, text_height; 633 int text_width, text_height;
586 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 634 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
587 output->scale, config->pango_markup, "%s", ws->label); 635 1, config->pango_markup, "%s", ws->label);
588 636
589 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 637 int ws_vertical_padding = WS_VERTICAL_PADDING;
590 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 638 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
591 int border_width = BORDER_WIDTH * output->scale; 639 int border_width = BORDER_WIDTH;
592 640
593 uint32_t ideal_height = ws_vertical_padding * 2 + text_height 641 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
594 + border_width * 2; 642 + border_width * 2;
595 uint32_t ideal_surface_height = ideal_height / output->scale; 643 uint32_t ideal_surface_height = ideal_height;
596 if (!output->bar->config->height && 644 if (!output->bar->config->height &&
597 output->height < ideal_surface_height) { 645 output->height < ideal_surface_height) {
598 return ideal_surface_height; 646 return ideal_surface_height;
599 } 647 }
600 648
601 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 649 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
602 if (width < config->workspace_min_width * output->scale) { 650 if (width < config->workspace_min_width) {
603 width = config->workspace_min_width * output->scale; 651 width = config->workspace_min_width;
604 } 652 }
605 653
654 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
606 cairo_set_source_u32(cairo, box_colors.background); 655 cairo_set_source_u32(cairo, box_colors.background);
656 ctx->background_color = box_colors.background;
607 cairo_rectangle(cairo, *x, 0, width, height); 657 cairo_rectangle(cairo, *x, 0, width, height);
608 cairo_fill(cairo); 658 cairo_fill(cairo);
609 659
@@ -620,7 +670,8 @@ static uint32_t render_workspace_button(cairo_t *cairo,
620 double text_y = height / 2.0 - text_height / 2.0; 670 double text_y = height / 2.0 - text_height / 2.0;
621 cairo_set_source_u32(cairo, box_colors.text); 671 cairo_set_source_u32(cairo, box_colors.text);
622 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); 672 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y));
623 pango_printf(cairo, config->font, output->scale, config->pango_markup, 673 choose_text_aa_mode(ctx, box_colors.text);
674 render_text(cairo, config->font_description, 1, config->pango_markup,
624 "%s", ws->label); 675 "%s", ws->label);
625 676
626 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 677 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
@@ -637,20 +688,15 @@ static uint32_t render_workspace_button(cairo_t *cairo,
637 return output->height; 688 return output->height;
638} 689}
639 690
640static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) { 691static uint32_t render_to_cairo(struct render_context *ctx) {
692 cairo_t *cairo = ctx->cairo;
693 struct swaybar_output *output = ctx->output;
641 struct swaybar *bar = output->bar; 694 struct swaybar *bar = output->bar;
642 struct swaybar_config *config = bar->config; 695 struct swaybar_config *config = bar->config;
643 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
644 if (output->focused) {
645 cairo_set_source_u32(cairo, config->colors.focused_background);
646 } else {
647 cairo_set_source_u32(cairo, config->colors.background);
648 }
649 cairo_paint(cairo);
650 696
651 int th; 697 int th;
652 get_text_size(cairo, config->font, NULL, &th, NULL, output->scale, false, ""); 698 get_text_size(cairo, config->font_description, NULL, &th, NULL, 1, false, "");
653 uint32_t max_height = (th + WS_VERTICAL_PADDING * 4) / output->scale; 699 uint32_t max_height = (th + WS_VERTICAL_PADDING * 4);
654 /* 700 /*
655 * Each render_* function takes the actual height of the bar, and returns 701 * Each render_* function takes the actual height of the bar, and returns
656 * the ideal height. If the actual height is too short, the render function 702 * the ideal height. If the actual height is too short, the render function
@@ -658,7 +704,7 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
658 * height is too tall, the render function should adapt its drawing to 704 * height is too tall, the render function should adapt its drawing to
659 * utilize the available space. 705 * utilize the available space.
660 */ 706 */
661 double x = output->width * output->scale; 707 double x = output->width;
662#if HAVE_TRAY 708#if HAVE_TRAY
663 if (bar->tray) { 709 if (bar->tray) {
664 uint32_t h = render_tray(cairo, output, &x); 710 uint32_t h = render_tray(cairo, output, &x);
@@ -666,19 +712,19 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
666 } 712 }
667#endif 713#endif
668 if (bar->status) { 714 if (bar->status) {
669 uint32_t h = render_status_line(cairo, output, &x); 715 uint32_t h = render_status_line(ctx, &x);
670 max_height = h > max_height ? h : max_height; 716 max_height = h > max_height ? h : max_height;
671 } 717 }
672 x = 0; 718 x = 0;
673 if (config->workspace_buttons) { 719 if (config->workspace_buttons) {
674 struct swaybar_workspace *ws; 720 struct swaybar_workspace *ws;
675 wl_list_for_each(ws, &output->workspaces, link) { 721 wl_list_for_each(ws, &output->workspaces, link) {
676 uint32_t h = render_workspace_button(cairo, output, ws, &x); 722 uint32_t h = render_workspace_button(ctx, ws, &x);
677 max_height = h > max_height ? h : max_height; 723 max_height = h > max_height ? h : max_height;
678 } 724 }
679 } 725 }
680 if (config->binding_mode_indicator) { 726 if (config->binding_mode_indicator) {
681 uint32_t h = render_binding_mode_indicator(cairo, output, x); 727 uint32_t h = render_binding_mode_indicator(ctx, x);
682 max_height = h > max_height ? h : max_height; 728 max_height = h > max_height ? h : max_height;
683 } 729 }
684 730
@@ -708,26 +754,44 @@ void render_frame(struct swaybar_output *output) {
708 754
709 free_hotspots(&output->hotspots); 755 free_hotspots(&output->hotspots);
710 756
757 uint32_t background_color;
758 if (output->focused) {
759 background_color = output->bar->config->colors.focused_background;
760 } else {
761 background_color = output->bar->config->colors.background;
762 }
763
764 struct render_context ctx = { 0 };
765 ctx.output = output;
766 // initial background color used for deciding the best way to antialias text
767 ctx.background_color = background_color;
768
711 cairo_surface_t *recorder = cairo_recording_surface_create( 769 cairo_surface_t *recorder = cairo_recording_surface_create(
712 CAIRO_CONTENT_COLOR_ALPHA, NULL); 770 CAIRO_CONTENT_COLOR_ALPHA, NULL);
713 cairo_t *cairo = cairo_create(recorder); 771 cairo_t *cairo = cairo_create(recorder);
772 cairo_scale(cairo, output->scale, output->scale);
714 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 773 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
774 ctx.cairo = cairo;
775
715 cairo_font_options_t *fo = cairo_font_options_create(); 776 cairo_font_options_t *fo = cairo_font_options_create();
716 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); 777 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
778 ctx.textaa_safe = fo;
717 if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) { 779 if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) {
718 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY); 780 ctx.textaa_sharp = ctx.textaa_safe;
719 } else { 781 } else {
782 fo = cairo_font_options_create();
720 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); 783 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
721 cairo_font_options_set_subpixel_order(fo, 784 cairo_font_options_set_subpixel_order(fo,
722 to_cairo_subpixel_order(output->subpixel)); 785 to_cairo_subpixel_order(output->subpixel));
786 ctx.textaa_sharp = fo;
723 } 787 }
724 cairo_set_font_options(cairo, fo); 788
725 cairo_font_options_destroy(fo); 789
726 cairo_save(cairo); 790 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
727 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 791 cairo_set_source_u32(cairo, background_color);
728 cairo_paint(cairo); 792 cairo_paint(cairo);
729 cairo_restore(cairo); 793
730 uint32_t height = render_to_cairo(cairo, output); 794 uint32_t height = render_to_cairo(&ctx);
731 int config_height = output->bar->config->height; 795 int config_height = output->bar->config->height;
732 if (config_height > 0) { 796 if (config_height > 0) {
733 height = config_height; 797 height = config_height;
@@ -753,9 +817,7 @@ void render_frame(struct swaybar_output *output) {
753 output->width * output->scale, 817 output->width * output->scale,
754 output->height * output->scale); 818 output->height * output->scale);
755 if (!output->current_buffer) { 819 if (!output->current_buffer) {
756 cairo_surface_destroy(recorder); 820 goto cleanup;
757 cairo_destroy(cairo);
758 return;
759 } 821 }
760 cairo_t *shm = output->current_buffer->cairo; 822 cairo_t *shm = output->current_buffer->cairo;
761 823
@@ -773,12 +835,29 @@ void render_frame(struct swaybar_output *output) {
773 wl_surface_damage(output->surface, 0, 0, 835 wl_surface_damage(output->surface, 0, 0,
774 output->width, output->height); 836 output->width, output->height);
775 837
838 uint32_t bg_alpha = background_color & 0xFF;
839 if (bg_alpha == 0xFF) {
840 struct wl_region *region =
841 wl_compositor_create_region(output->bar->compositor);
842 wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
843 wl_surface_set_opaque_region(output->surface, region);
844 wl_region_destroy(region);
845 } else {
846 wl_surface_set_opaque_region(output->surface, NULL);
847 }
848
776 struct wl_callback *frame_callback = wl_surface_frame(output->surface); 849 struct wl_callback *frame_callback = wl_surface_frame(output->surface);
777 wl_callback_add_listener(frame_callback, &output_frame_listener, output); 850 wl_callback_add_listener(frame_callback, &output_frame_listener, output);
778 output->frame_scheduled = true; 851 output->frame_scheduled = true;
779 852
780 wl_surface_commit(output->surface); 853 wl_surface_commit(output->surface);
781 } 854 }
855
856cleanup:
857 if (ctx.textaa_sharp != ctx.textaa_safe) {
858 cairo_font_options_destroy(ctx.textaa_sharp);
859 }
860 cairo_font_options_destroy(ctx.textaa_safe);
782 cairo_surface_destroy(recorder); 861 cairo_surface_destroy(recorder);
783 cairo_destroy(cairo); 862 cairo_destroy(cairo);
784} 863}
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index ecd91032..2e9bb7f1 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -117,11 +117,11 @@ bool status_handle_readable(struct status_line *status) {
117 status->text = status->buffer; 117 status->text = status->buffer;
118 // intentional fall-through 118 // intentional fall-through
119 case PROTOCOL_TEXT: 119 case PROTOCOL_TEXT:
120 errno = 0;
121 while (true) { 120 while (true) {
122 if (status->buffer[read_bytes - 1] == '\n') { 121 if (status->buffer[read_bytes - 1] == '\n') {
123 status->buffer[read_bytes - 1] = '\0'; 122 status->buffer[read_bytes - 1] = '\0';
124 } 123 }
124 errno = 0;
125 read_bytes = getline(&status->buffer, 125 read_bytes = getline(&status->buffer,
126 &status->buffer_size, status->read); 126 &status->buffer_size, status->read);
127 if (errno == EAGAIN) { 127 if (errno == EAGAIN) {
@@ -157,7 +157,12 @@ struct status_line *status_line_init(char *cmd) {
157 assert(!getenv("WAYLAND_SOCKET") && "display must be initialized before " 157 assert(!getenv("WAYLAND_SOCKET") && "display must be initialized before "
158 " starting `status-command`; WAYLAND_SOCKET should not be set"); 158 " starting `status-command`; WAYLAND_SOCKET should not be set");
159 status->pid = fork(); 159 status->pid = fork();
160 if (status->pid == 0) { 160 if (status->pid < 0) {
161 sway_log_errno(SWAY_ERROR, "fork failed");
162 exit(1);
163 } else if (status->pid == 0) {
164 setpgid(0, 0);
165
161 dup2(pipe_read_fd[1], STDOUT_FILENO); 166 dup2(pipe_read_fd[1], STDOUT_FILENO);
162 close(pipe_read_fd[0]); 167 close(pipe_read_fd[0]);
163 close(pipe_read_fd[1]); 168 close(pipe_read_fd[1]);
@@ -185,8 +190,8 @@ struct status_line *status_line_init(char *cmd) {
185 190
186void status_line_free(struct status_line *status) { 191void status_line_free(struct status_line *status) {
187 status_line_close_fds(status); 192 status_line_close_fds(status);
188 kill(status->pid, status->cont_signal); 193 kill(-status->pid, status->cont_signal);
189 kill(status->pid, SIGTERM); 194 kill(-status->pid, SIGTERM);
190 waitpid(status->pid, NULL, 0); 195 waitpid(status->pid, NULL, 0);
191 if (status->protocol == PROTOCOL_I3BAR) { 196 if (status->protocol == PROTOCOL_I3BAR) {
192 struct i3bar_block *block, *tmp; 197 struct i3bar_block *block, *tmp;
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
index ddf2416d..eea2caa5 100644
--- a/swaybar/tray/host.c
+++ b/swaybar/tray/host.c
@@ -10,6 +10,7 @@
10#include "swaybar/tray/tray.h" 10#include "swaybar/tray/tray.h"
11#include "list.h" 11#include "list.h"
12#include "log.h" 12#include "log.h"
13#include "stringop.h"
13 14
14static const char *watcher_path = "/StatusNotifierWatcher"; 15static const char *watcher_path = "/StatusNotifierWatcher";
15 16
@@ -138,12 +139,10 @@ static int handle_new_watcher(sd_bus_message *msg,
138 139
139bool init_host(struct swaybar_host *host, char *protocol, 140bool init_host(struct swaybar_host *host, char *protocol,
140 struct swaybar_tray *tray) { 141 struct swaybar_tray *tray) {
141 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; 142 host->watcher_interface = format_str("org.%s.StatusNotifierWatcher", protocol);
142 host->watcher_interface = malloc(len);
143 if (!host->watcher_interface) { 143 if (!host->watcher_interface) {
144 return false; 144 return false;
145 } 145 }
146 snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
147 146
148 sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL; 147 sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
149 int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface, 148 int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
@@ -173,13 +172,10 @@ bool init_host(struct swaybar_host *host, char *protocol,
173 } 172 }
174 173
175 pid_t pid = getpid(); 174 pid_t pid = getpid();
176 size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", 175 host->service = format_str("org.%s.StatusNotifierHost-%d", protocol, pid);
177 protocol, pid) + 1;
178 host->service = malloc(service_len);
179 if (!host->service) { 176 if (!host->service) {
180 goto error; 177 goto error;
181 } 178 }
182 snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
183 ret = sd_bus_request_name(tray->bus, host->service, 0); 179 ret = sd_bus_request_name(tray->bus, host->service, 0);
184 if (ret < 0) { 180 if (ret < 0) {
185 sway_log(SWAY_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); 181 sway_log(SWAY_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
index c426c3d4..b513dca5 100644
--- a/swaybar/tray/icon.c
+++ b/swaybar/tray/icon.c
@@ -40,9 +40,7 @@ static list_t *get_basedirs(void) {
40 data_dirs = strdup(data_dirs); 40 data_dirs = strdup(data_dirs);
41 char *dir = strtok(data_dirs, ":"); 41 char *dir = strtok(data_dirs, ":");
42 do { 42 do {
43 size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1; 43 char *path = format_str("%s/icons", dir);
44 char *path = malloc(path_len);
45 snprintf(path, path_len, "%s/icons", dir);
46 list_add(basedirs, path); 44 list_add(basedirs, path);
47 } while ((dir = strtok(NULL, ":"))); 45 } while ((dir = strtok(NULL, ":")));
48 free(data_dirs); 46 free(data_dirs);
@@ -206,13 +204,7 @@ static const char *entry_handler(char *group, char *key, char *value,
206 */ 204 */
207static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { 205static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
208 // look for index.theme file 206 // look for index.theme file
209 size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir, 207 char *path = format_str("%s/%s/index.theme", basedir, theme_name);
210 theme_name) + 1;
211 char *path = malloc(path_len);
212 if (!path) {
213 return NULL;
214 }
215 snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
216 FILE *theme_file = fopen(path, "r"); 208 FILE *theme_file = fopen(path, "r");
217 free(path); 209 free(path);
218 if (!theme_file) { 210 if (!theme_file) {
@@ -416,26 +408,20 @@ static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
416#endif 408#endif
417 }; 409 };
418 410
419 size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
420 subdir, name) + 1;
421 char *path = malloc(path_len);
422
423 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) { 411 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
424 snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir, 412 char *path = format_str("%s/%s/%s/%s.%s",
425 name, extensions[i]); 413 basedir, theme, subdir, name, extensions[i]);
426 if (access(path, R_OK) == 0) { 414 if (access(path, R_OK) == 0) {
427 return path; 415 return path;
428 } 416 }
417 free(path);
429 } 418 }
430 419
431 free(path);
432 return NULL; 420 return NULL;
433} 421}
434 422
435static bool theme_exists_in_basedir(char *theme, char *basedir) { 423static bool theme_exists_in_basedir(char *theme, char *basedir) {
436 size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1; 424 char *path = format_str("%s/%s", basedir, theme);
437 char *path = malloc(path_len);
438 snprintf(path, path_len, "%s/%s", basedir, theme);
439 bool ret = dir_exists(path); 425 bool ret = dir_exists(path);
440 free(path); 426 free(path);
441 return ret; 427 return ret;
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
index a5660f62..d5fe50b1 100644
--- a/swaybar/tray/item.c
+++ b/swaybar/tray/item.c
@@ -7,13 +7,13 @@
7#include <string.h> 7#include <string.h>
8#include "swaybar/bar.h" 8#include "swaybar/bar.h"
9#include "swaybar/config.h" 9#include "swaybar/config.h"
10#include "swaybar/image.h"
10#include "swaybar/input.h" 11#include "swaybar/input.h"
11#include "swaybar/tray/host.h" 12#include "swaybar/tray/host.h"
12#include "swaybar/tray/icon.h" 13#include "swaybar/tray/icon.h"
13#include "swaybar/tray/item.h" 14#include "swaybar/tray/item.h"
14#include "swaybar/tray/tray.h" 15#include "swaybar/tray/tray.h"
15#include "background-image.h" 16#include "cairo_util.h"
16#include "cairo.h"
17#include "list.h" 17#include "list.h"
18#include "log.h" 18#include "log.h"
19#include "wlr-layer-shell-unstable-v1-client-protocol.h" 19#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@@ -118,8 +118,13 @@ static int get_property_callback(sd_bus_message *msg, void *data,
118 118
119 int ret; 119 int ret;
120 if (sd_bus_message_is_method_error(msg, NULL)) { 120 if (sd_bus_message_is_method_error(msg, NULL)) {
121 sway_log(SWAY_ERROR, "%s %s: %s", sni->watcher_id, prop, 121 const sd_bus_error *err = sd_bus_message_get_error(msg);
122 sd_bus_message_get_error(msg)->message); 122 sway_log_importance_t log_lv = SWAY_ERROR;
123 if ((!strcmp(prop, "IconThemePath")) &&
124 (!strcmp(err->name, SD_BUS_ERROR_UNKNOWN_PROPERTY))) {
125 log_lv = SWAY_DEBUG;
126 }
127 sway_log(log_lv, "%s %s: %s", sni->watcher_id, prop, err->message);
123 ret = sd_bus_message_get_errno(msg); 128 ret = sd_bus_message_get_errno(msg);
124 goto cleanup; 129 goto cleanup;
125 } 130 }
@@ -380,13 +385,18 @@ static int cmp_sni_id(const void *item, const void *cmp_to) {
380 385
381static enum hotspot_event_handling icon_hotspot_callback( 386static enum hotspot_event_handling icon_hotspot_callback(
382 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 387 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
383 double x, double y, uint32_t button, void *data) { 388 double x, double y, uint32_t button, bool released, void *data) {
384 sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data); 389 sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data);
385 390
386 struct swaybar_tray *tray = output->bar->tray; 391 struct swaybar_tray *tray = output->bar->tray;
387 int idx = list_seq_find(tray->items, cmp_sni_id, data); 392 int idx = list_seq_find(tray->items, cmp_sni_id, data);
388 393
389 if (idx != -1) { 394 if (idx != -1) {
395 if (released) {
396 // Since we handle the pressed event, also handle the released event
397 // to block it from falling through to a binding in the bar
398 return HOTSPOT_IGNORE;
399 }
390 struct swaybar_sni *sni = tray->items->items[idx]; 400 struct swaybar_sni *sni = tray->items->items[idx];
391 // guess global position since wayland doesn't expose it 401 // guess global position since wayland doesn't expose it
392 struct swaybar_config *config = tray->bar->config; 402 struct swaybar_config *config = tray->bar->config;
@@ -421,7 +431,7 @@ static void reload_sni(struct swaybar_sni *sni, char *icon_theme,
421 list_free(icon_search_paths); 431 list_free(icon_search_paths);
422 if (icon_path) { 432 if (icon_path) {
423 cairo_surface_destroy(sni->icon); 433 cairo_surface_destroy(sni->icon);
424 sni->icon = load_background_image(icon_path); 434 sni->icon = load_image(icon_path);
425 free(icon_path); 435 free(icon_path);
426 return; 436 return;
427 } 437 }
@@ -461,6 +471,11 @@ uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
461 sni->target_size = target_size; 471 sni->target_size = target_size;
462 } 472 }
463 473
474 // Passive
475 if (sni->status && sni->status[0] == 'P') {
476 return 0;
477 }
478
464 int icon_size; 479 int icon_size;
465 cairo_surface_t *icon; 480 cairo_surface_t *icon;
466 if (sni->icon) { 481 if (sni->icon) {
@@ -488,24 +503,36 @@ uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
488 cairo_destroy(cairo_icon); 503 cairo_destroy(cairo_icon);
489 } 504 }
490 505
491 int padded_size = icon_size + 2*padding; 506 double descaled_padding = (double)padding / output->scale;
492 *x -= padded_size; 507 double descaled_icon_size = (double)icon_size / output->scale;
493 int y = floor((height - padded_size) / 2.0); 508
509 int size = descaled_icon_size + 2 * descaled_padding;
510 *x -= size;
511 int icon_y = floor((output->height - size) / 2.0);
494 512
495 cairo_operator_t op = cairo_get_operator(cairo); 513 cairo_operator_t op = cairo_get_operator(cairo);
496 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); 514 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
497 cairo_set_source_surface(cairo, icon, *x + padding, y + padding); 515
498 cairo_rectangle(cairo, *x, y, padded_size, padded_size); 516 cairo_matrix_t scale_matrix;
517 cairo_pattern_t *icon_pattern = cairo_pattern_create_for_surface(icon);
518 // TODO: check cairo_pattern_status for "ENOMEM"
519 cairo_matrix_init_scale(&scale_matrix, output->scale, output->scale);
520 cairo_matrix_translate(&scale_matrix, -(*x + descaled_padding), -(icon_y + descaled_padding));
521 cairo_pattern_set_matrix(icon_pattern, &scale_matrix);
522 cairo_set_source(cairo, icon_pattern);
523 cairo_rectangle(cairo, *x, icon_y, size, size);
499 cairo_fill(cairo); 524 cairo_fill(cairo);
525
500 cairo_set_operator(cairo, op); 526 cairo_set_operator(cairo, op);
501 527
528 cairo_pattern_destroy(icon_pattern);
502 cairo_surface_destroy(icon); 529 cairo_surface_destroy(icon);
503 530
504 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 531 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
505 hotspot->x = *x; 532 hotspot->x = *x;
506 hotspot->y = 0; 533 hotspot->y = 0;
507 hotspot->width = height; 534 hotspot->width = size;
508 hotspot->height = height; 535 hotspot->height = output->height;
509 hotspot->callback = icon_hotspot_callback; 536 hotspot->callback = icon_hotspot_callback;
510 hotspot->destroy = free; 537 hotspot->destroy = free;
511 hotspot->data = strdup(sni->watcher_id); 538 hotspot->data = strdup(sni->watcher_id);
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
index 5fe6f9c3..b0545f4a 100644
--- a/swaybar/tray/tray.c
+++ b/swaybar/tray/tray.c
@@ -116,8 +116,8 @@ uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
116 } 116 }
117 } // else display on all 117 } // else display on all
118 118
119 if ((int) output->height*output->scale <= 2*config->tray_padding) { 119 if ((int)(output->height * output->scale) <= 2 * config->tray_padding) {
120 return 2*config->tray_padding + 1; 120 return (2 * config->tray_padding + 1) / output->scale;
121 } 121 }
122 122
123 uint32_t max_height = 0; 123 uint32_t max_height = 0;
diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c
index 16afc27c..2458a8c2 100644
--- a/swaybar/tray/watcher.c
+++ b/swaybar/tray/watcher.c
@@ -6,6 +6,7 @@
6#include <string.h> 6#include <string.h>
7#include "list.h" 7#include "list.h"
8#include "log.h" 8#include "log.h"
9#include "stringop.h"
9#include "swaybar/tray/watcher.h" 10#include "swaybar/tray/watcher.h"
10 11
11static const char *obj_path = "/StatusNotifierWatcher"; 12static const char *obj_path = "/StatusNotifierWatcher";
@@ -76,9 +77,7 @@ static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
76 service = service_or_path; 77 service = service_or_path;
77 path = "/StatusNotifierItem"; 78 path = "/StatusNotifierItem";
78 } 79 }
79 size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1; 80 id = format_str("%s%s", service, path);
80 id = malloc(id_len);
81 snprintf(id, id_len, "%s%s", service, path);
82 } 81 }
83 82
84 if (list_seq_find(watcher->items, cmp_id, id) == -1) { 83 if (list_seq_find(watcher->items, cmp_id, id) == -1) {
@@ -107,7 +106,7 @@ static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
107 sway_log(SWAY_DEBUG, "Registering Status Notifier Host '%s'", service); 106 sway_log(SWAY_DEBUG, "Registering Status Notifier Host '%s'", service);
108 list_add(watcher->hosts, strdup(service)); 107 list_add(watcher->hosts, strdup(service));
109 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, 108 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
110 "StatusNotifierHostRegistered", "s", service); 109 "StatusNotifierHostRegistered", "");
111 } else { 110 } else {
112 sway_log(SWAY_DEBUG, "Status Notifier Host '%s' already registered", service); 111 sway_log(SWAY_DEBUG, "Status Notifier Host '%s' already registered", service);
113 } 112 }
@@ -159,9 +158,7 @@ struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
159 return NULL; 158 return NULL;
160 } 159 }
161 160
162 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; 161 watcher->interface = format_str("org.%s.StatusNotifierWatcher", protocol);
163 watcher->interface = malloc(len);
164 snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
165 162
166 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL; 163 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
167 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path, 164 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
diff --git a/swaymsg/main.c b/swaymsg/main.c
index 60536e48..db9346c4 100644
--- a/swaymsg/main.c
+++ b/swaymsg/main.c
@@ -1,4 +1,6 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2
3#include <limits.h>
2#include <stdio.h> 4#include <stdio.h>
3#include <stdlib.h> 5#include <stdlib.h>
4#include <string.h> 6#include <string.h>
@@ -58,7 +60,7 @@ static void pretty_print_cmd(json_object *r) {
58 if (!success_object(r)) { 60 if (!success_object(r)) {
59 json_object *error; 61 json_object *error;
60 if (!json_object_object_get_ex(r, "error", &error)) { 62 if (!json_object_object_get_ex(r, "error", &error)) {
61 printf("An unknkown error occurred"); 63 printf("An unknown error occurred");
62 } else { 64 } else {
63 printf("Error: %s\n", json_object_get_string(error)); 65 printf("Error: %s\n", json_object_get_string(error));
64 } 66 }
@@ -183,12 +185,14 @@ static void pretty_print_seat(json_object *i) {
183} 185}
184 186
185static void pretty_print_output(json_object *o) { 187static void pretty_print_output(json_object *o) {
186 json_object *name, *rect, *focused, *active, *ws, *current_mode; 188 json_object *name, *rect, *focused, *active, *power, *ws, *current_mode, *non_desktop;
187 json_object_object_get_ex(o, "name", &name); 189 json_object_object_get_ex(o, "name", &name);
188 json_object_object_get_ex(o, "rect", &rect); 190 json_object_object_get_ex(o, "rect", &rect);
189 json_object_object_get_ex(o, "focused", &focused); 191 json_object_object_get_ex(o, "focused", &focused);
190 json_object_object_get_ex(o, "active", &active); 192 json_object_object_get_ex(o, "active", &active);
193 json_object_object_get_ex(o, "power", &power);
191 json_object_object_get_ex(o, "current_workspace", &ws); 194 json_object_object_get_ex(o, "current_workspace", &ws);
195 json_object_object_get_ex(o, "non_desktop", &non_desktop);
192 json_object *make, *model, *serial, *scale, *scale_filter, *subpixel, 196 json_object *make, *model, *serial, *scale, *scale_filter, *subpixel,
193 *transform, *max_render_time, *adaptive_sync_status; 197 *transform, *max_render_time, *adaptive_sync_status;
194 json_object_object_get_ex(o, "make", &make); 198 json_object_object_get_ex(o, "make", &make);
@@ -211,10 +215,19 @@ static void pretty_print_output(json_object *o) {
211 json_object_object_get_ex(current_mode, "height", &height); 215 json_object_object_get_ex(current_mode, "height", &height);
212 json_object_object_get_ex(current_mode, "refresh", &refresh); 216 json_object_object_get_ex(current_mode, "refresh", &refresh);
213 217
214 if (json_object_get_boolean(active)) { 218 if (json_object_get_boolean(non_desktop)) {
219 printf(
220 "Output %s '%s %s %s' (non-desktop)\n",
221 json_object_get_string(name),
222 json_object_get_string(make),
223 json_object_get_string(model),
224 json_object_get_string(serial)
225 );
226 } else if (json_object_get_boolean(active)) {
215 printf( 227 printf(
216 "Output %s '%s %s %s'%s\n" 228 "Output %s '%s %s %s'%s\n"
217 " Current mode: %dx%d @ %.3f Hz\n" 229 " Current mode: %dx%d @ %.3f Hz\n"
230 " Power: %s\n"
218 " Position: %d,%d\n" 231 " Position: %d,%d\n"
219 " Scale factor: %f\n" 232 " Scale factor: %f\n"
220 " Scale filter: %s\n" 233 " Scale filter: %s\n"
@@ -229,6 +242,7 @@ static void pretty_print_output(json_object *o) {
229 json_object_get_int(width), 242 json_object_get_int(width),
230 json_object_get_int(height), 243 json_object_get_int(height),
231 (double)json_object_get_int(refresh) / 1000, 244 (double)json_object_get_int(refresh) / 1000,
245 json_object_get_boolean(power) ? "on" : "off",
232 json_object_get_int(x), json_object_get_int(y), 246 json_object_get_int(x), json_object_get_int(y),
233 json_object_get_double(scale), 247 json_object_get_double(scale),
234 json_object_get_string(scale_filter), 248 json_object_get_string(scale_filter),
@@ -245,7 +259,7 @@ static void pretty_print_output(json_object *o) {
245 json_object_get_string(adaptive_sync_status)); 259 json_object_get_string(adaptive_sync_status));
246 } else { 260 } else {
247 printf( 261 printf(
248 "Output %s '%s %s %s' (inactive)\n", 262 "Output %s '%s %s %s' (disabled)\n",
249 json_object_get_string(name), 263 json_object_get_string(name),
250 json_object_get_string(make), 264 json_object_get_string(make),
251 json_object_get_string(model), 265 json_object_get_string(model),
@@ -260,14 +274,22 @@ static void pretty_print_output(json_object *o) {
260 for (size_t i = 0; i < modes_len; ++i) { 274 for (size_t i = 0; i < modes_len; ++i) {
261 json_object *mode = json_object_array_get_idx(modes, i); 275 json_object *mode = json_object_array_get_idx(modes, i);
262 276
263 json_object *mode_width, *mode_height, *mode_refresh; 277 json_object *mode_width, *mode_height, *mode_refresh,
278 *mode_picture_aspect_ratio;
264 json_object_object_get_ex(mode, "width", &mode_width); 279 json_object_object_get_ex(mode, "width", &mode_width);
265 json_object_object_get_ex(mode, "height", &mode_height); 280 json_object_object_get_ex(mode, "height", &mode_height);
266 json_object_object_get_ex(mode, "refresh", &mode_refresh); 281 json_object_object_get_ex(mode, "refresh", &mode_refresh);
282 json_object_object_get_ex(mode, "picture_aspect_ratio",
283 &mode_picture_aspect_ratio);
267 284
268 printf(" %dx%d @ %.3f Hz\n", json_object_get_int(mode_width), 285 printf(" %dx%d @ %.3f Hz", json_object_get_int(mode_width),
269 json_object_get_int(mode_height), 286 json_object_get_int(mode_height),
270 (double)json_object_get_int(mode_refresh) / 1000); 287 (double)json_object_get_int(mode_refresh) / 1000);
288 if (mode_picture_aspect_ratio &&
289 strcmp("none", json_object_get_string(mode_picture_aspect_ratio)) != 0) {
290 printf(" (%s)", json_object_get_string(mode_picture_aspect_ratio));
291 }
292 printf("\n");
271 } 293 }
272 } 294 }
273 295
@@ -286,28 +308,83 @@ static void pretty_print_config(json_object *c) {
286 printf("%s\n", json_object_get_string(config)); 308 printf("%s\n", json_object_get_string(config));
287} 309}
288 310
289static void pretty_print(int type, json_object *resp) { 311static void pretty_print_tree(json_object *obj, int indent) {
290 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES && 312 for (int i = 0; i < indent; i++) {
291 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS && 313 printf(" ");
292 type != IPC_GET_VERSION && type != IPC_GET_SEATS &&
293 type != IPC_GET_CONFIG && type != IPC_SEND_TICK) {
294 printf("%s\n", json_object_to_json_string_ext(resp,
295 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
296 return;
297 } 314 }
298 315
299 if (type == IPC_SEND_TICK) { 316 int id = json_object_get_int(json_object_object_get(obj, "id"));
300 return; 317 const char *name = json_object_get_string(json_object_object_get(obj, "name"));
318 const char *type = json_object_get_string(json_object_object_get(obj, "type"));
319 const char *shell = json_object_get_string(json_object_object_get(obj, "shell"));
320
321 printf("#%d: %s \"%s\"", id, type, name);
322
323 if (shell != NULL) {
324 int pid = json_object_get_int(json_object_object_get(obj, "pid"));
325 const char *app_id = json_object_get_string(json_object_object_get(obj, "app_id"));
326 json_object *window_props_obj = json_object_object_get(obj, "window_properties");
327 const char *instance = json_object_get_string(json_object_object_get(window_props_obj, "instance"));
328 const char *class = json_object_get_string(json_object_object_get(window_props_obj, "class"));
329 int x11_id = json_object_get_int(json_object_object_get(obj, "window"));
330
331 printf(" (%s, pid: %d", shell, pid);
332 if (app_id != NULL) {
333 printf(", app_id: \"%s\"", app_id);
334 }
335 if (instance != NULL) {
336 printf(", instance: \"%s\"", instance);
337 }
338 if (class != NULL) {
339 printf(", class: \"%s\"", class);
340 }
341 if (x11_id != 0) {
342 printf(", X11 window: 0x%X", x11_id);
343 }
344 printf(")");
301 } 345 }
302 346
303 if (type == IPC_GET_VERSION) { 347 printf("\n");
304 pretty_print_version(resp); 348
305 return; 349 json_object *nodes_obj = json_object_object_get(obj, "nodes");
350 size_t len = json_object_array_length(nodes_obj);
351 for (size_t i = 0; i < len; i++) {
352 pretty_print_tree(json_object_array_get_idx(nodes_obj, i), indent + 1);
306 } 353 }
307 354
308 if (type == IPC_GET_CONFIG) { 355 json_object *floating_nodes_obj;
356 json_bool floating_nodes = json_object_object_get_ex(obj, "floating_nodes", &floating_nodes_obj);
357 if (floating_nodes) {
358 size_t len = json_object_array_length(floating_nodes_obj);
359 for (size_t i = 0; i < len; i++) {
360 pretty_print_tree(json_object_array_get_idx(floating_nodes_obj, i), indent + 1);
361 }
362 }
363}
364
365static void pretty_print(int type, json_object *resp) {
366 switch (type) {
367 case IPC_SEND_TICK:
368 return;
369 case IPC_GET_VERSION:
370 pretty_print_version(resp);
371 return;
372 case IPC_GET_CONFIG:
309 pretty_print_config(resp); 373 pretty_print_config(resp);
310 return; 374 return;
375 case IPC_GET_TREE:
376 pretty_print_tree(resp, 0);
377 return;
378 case IPC_COMMAND:
379 case IPC_GET_WORKSPACES:
380 case IPC_GET_INPUTS:
381 case IPC_GET_OUTPUTS:
382 case IPC_GET_SEATS:
383 break;
384 default:
385 printf("%s\n", json_object_to_json_string_ext(resp,
386 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
387 return;
311 } 388 }
312 389
313 json_object *obj; 390 json_object *obj;
@@ -343,7 +420,7 @@ int main(int argc, char **argv) {
343 420
344 sway_log_init(SWAY_INFO, NULL); 421 sway_log_init(SWAY_INFO, NULL);
345 422
346 static struct option long_options[] = { 423 static const struct option long_options[] = {
347 {"help", no_argument, NULL, 'h'}, 424 {"help", no_argument, NULL, 'h'},
348 {"monitor", no_argument, NULL, 'm'}, 425 {"monitor", no_argument, NULL, 'm'},
349 {"pretty", no_argument, NULL, 'p'}, 426 {"pretty", no_argument, NULL, 'p'},
@@ -480,12 +557,20 @@ int main(int argc, char **argv) {
480 char *resp = ipc_single_command(socketfd, type, command, &len); 557 char *resp = ipc_single_command(socketfd, type, command, &len);
481 558
482 // pretty print the json 559 // pretty print the json
483 json_object *obj = json_tokener_parse(resp); 560 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
484 if (obj == NULL) { 561 if (tok == NULL) {
562 if (quiet) {
563 exit(EXIT_FAILURE);
564 }
565 sway_abort("failed allocating json_tokener");
566 }
567 json_object *obj = json_tokener_parse_ex(tok, resp, -1);
568 enum json_tokener_error err = json_tokener_get_error(tok);
569 json_tokener_free(tok);
570 if (obj == NULL || err != json_tokener_success) {
485 if (!quiet) { 571 if (!quiet) {
486 fprintf(stderr, "ERROR: Could not parse json response from ipc. " 572 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
487 "This is a bug in sway."); 573 json_tokener_error_desc(err));
488 printf("%s\n", resp);
489 } 574 }
490 ret = 1; 575 ret = 1;
491 } else { 576 } else {
@@ -517,13 +602,22 @@ int main(int argc, char **argv) {
517 break; 602 break;
518 } 603 }
519 604
520 json_object *obj = json_tokener_parse(reply->payload); 605 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
521 if (obj == NULL) { 606 if (tok == NULL) {
607 if (quiet) {
608 exit(EXIT_FAILURE);
609 }
610 sway_abort("failed allocating json_tokener");
611 }
612 json_object *obj = json_tokener_parse_ex(tok, reply->payload, -1);
613 enum json_tokener_error err = json_tokener_get_error(tok);
614 json_tokener_free(tok);
615 if (obj == NULL || err != json_tokener_success) {
522 if (!quiet) { 616 if (!quiet) {
523 fprintf(stderr, "ERROR: Could not parse json response from" 617 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
524 " ipc. This is a bug in sway."); 618 json_tokener_error_desc(err));
525 ret = 1;
526 } 619 }
620 ret = 1;
527 break; 621 break;
528 } else if (quiet) { 622 } else if (quiet) {
529 json_object_put(obj); 623 json_object_put(obj);
diff --git a/swaymsg/swaymsg.1.scd b/swaymsg/swaymsg.1.scd
index b69013b5..abee1bb9 100644
--- a/swaymsg/swaymsg.1.scd
+++ b/swaymsg/swaymsg.1.scd
@@ -21,12 +21,13 @@ _swaymsg_ [options...] [message]
21 21
22*-p, --pretty* 22*-p, --pretty*
23 Use pretty output even when not using a tty. 23 Use pretty output even when not using a tty.
24 Not available for all message types.
24 25
25*-q, --quiet* 26*-q, --quiet*
26 Sends the IPC message but does not print the response from sway. 27 Sends the IPC message but does not print the response from sway.
27 28
28*-r, --raw* 29*-r, --raw*
29 Use raw output even if using a tty. 30 Use raw JSON output even if using a tty.
30 31
31*-s, --socket* <path> 32*-s, --socket* <path>
32 Use the specified socket path. Otherwise, swaymsg will ask sway where the 33 Use the specified socket path. Otherwise, swaymsg will ask sway where the
@@ -46,6 +47,11 @@ _swaymsg_ [options...] [message]
46 47
47 See *sway*(5) for a list of commands. 48 See *sway*(5) for a list of commands.
48 49
50 _swaymsg_ can return pretty printed (standalone-default) or JSON-formatted
51 (*--raw*) output. For detailed documentation on the returned JSON-data of
52 each message type listed below, refer to *sway-ipc*(7). The JSON-format can
53 contain more information than the pretty print.
54
49 Tips: 55 Tips:
50 - Command expansion is performed twice: once by swaymsg, and again by sway. 56 - Command expansion is performed twice: once by swaymsg, and again by sway.
51 If you have quoted multi-word strings in your command, enclose the entire 57 If you have quoted multi-word strings in your command, enclose the entire
@@ -60,20 +66,20 @@ _swaymsg_ [options...] [message]
60 _swaymsg -- mark --add test_ instead of _swaymsg mark --add test_. 66 _swaymsg -- mark --add test_ instead of _swaymsg mark --add test_.
61 67
62*get\_workspaces* 68*get\_workspaces*
63 Gets a JSON-encoded list of workspaces and their status. 69 Gets a list of workspaces and their status.
64 70
65*get\_inputs* 71*get\_inputs*
66 Gets a JSON-encoded list of current inputs. 72 Gets a list of current inputs.
67 73
68*get\_outputs* 74*get\_outputs*
69 Gets a JSON-encoded list of current outputs. 75 Gets a list of current outputs.
70 76
71*get\_tree* 77*get\_tree*
72 Gets a JSON-encoded layout tree of all open windows, containers, outputs, 78 Gets a JSON-encoded layout tree of all open windows, containers, outputs,
73 workspaces, and so on. 79 workspaces, and so on.
74 80
75*get\_seats* 81*get\_seats*
76 Gets a JSON-encoded list of all seats, 82 Gets a list of all seats,
77 its properties and all assigned devices. 83 its properties and all assigned devices.
78 84
79*get\_marks* 85*get\_marks*
@@ -83,7 +89,7 @@ _swaymsg_ [options...] [message]
83 Get a JSON-encoded configuration for swaybar. 89 Get a JSON-encoded configuration for swaybar.
84 90
85*get\_version* 91*get\_version*
86 Get JSON-encoded version information for the running instance of sway. 92 Get version information for the running instance of sway.
87 93
88*get\_binding\_modes* 94*get\_binding\_modes*
89 Gets a JSON-encoded list of currently configured binding modes. 95 Gets a JSON-encoded list of currently configured binding modes.
@@ -92,7 +98,7 @@ _swaymsg_ [options...] [message]
92 Gets JSON-encoded info about the current binding state. 98 Gets JSON-encoded info about the current binding state.
93 99
94*get\_config* 100*get\_config*
95 Gets a JSON-encoded copy of the current configuration. 101 Gets a copy of the current configuration. Doesn't expand includes.
96 102
97*send\_tick* 103*send\_tick*
98 Sends a tick event to all subscribed clients. 104 Sends a tick event to all subscribed clients.
@@ -101,6 +107,8 @@ _swaymsg_ [options...] [message]
101 Subscribe to a list of event types. The argument for this type should be 107 Subscribe to a list of event types. The argument for this type should be
102 provided in the form of a valid JSON array. If any of the types are invalid 108 provided in the form of a valid JSON array. If any of the types are invalid
103 or if a valid JSON array is not provided, this will result in a failure. 109 or if a valid JSON array is not provided, this will result in a failure.
110 For a list of valid event types and the data returned with them refer to
111 *sway-ipc*(7).
104 112
105# RETURN CODES 113# RETURN CODES
106 114
diff --git a/swaynag/config.c b/swaynag/config.c
index ca7f4eb2..cff3930f 100644
--- a/swaynag/config.c
+++ b/swaynag/config.c
@@ -11,24 +11,40 @@
11#include "util.h" 11#include "util.h"
12#include "wlr-layer-shell-unstable-v1-client-protocol.h" 12#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13 13
14static char *read_from_stdin(void) { 14static char *read_and_trim_stdin(void) {
15 char *buffer = NULL; 15 char *buffer = NULL, *line = NULL;
16 size_t buffer_len = 0; 16 size_t buffer_len = 0, line_size = 0;
17 char *line = NULL; 17 while (1) {
18 size_t line_size = 0; 18 ssize_t nread = getline(&line, &line_size, stdin);
19 ssize_t nread; 19 if (nread == -1) {
20 while ((nread = getline(&line, &line_size, stdin)) != -1) { 20 if (feof(stdin)) {
21 break;
22 } else {
23 perror("getline");
24 goto freeline;
25 }
26 }
21 buffer = realloc(buffer, buffer_len + nread + 1); 27 buffer = realloc(buffer, buffer_len + nread + 1);
22 snprintf(&buffer[buffer_len], nread + 1, "%s", line); 28 if (!buffer) {
29 perror("realloc");
30 goto freebuf;
31 }
32 memcpy(&buffer[buffer_len], line, nread + 1);
23 buffer_len += nread; 33 buffer_len += nread;
24 } 34 }
25 free(line); 35 free(line);
26 36
27 while (buffer && buffer[buffer_len - 1] == '\n') { 37 while (buffer_len && buffer[buffer_len - 1] == '\n') {
28 buffer[--buffer_len] = '\0'; 38 buffer[--buffer_len] = '\0';
29 } 39 }
30 40
31 return buffer; 41 return buffer;
42
43freeline:
44 free(line);
45freebuf:
46 free(buffer);
47 return NULL;
32} 48}
33 49
34int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, 50int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
@@ -51,7 +67,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
51 TO_PADDING_BTN, 67 TO_PADDING_BTN,
52 }; 68 };
53 69
54 static struct option opts[] = { 70 static const struct option opts[] = {
55 {"button", required_argument, NULL, 'b'}, 71 {"button", required_argument, NULL, 'b'},
56 {"button-no-terminal", required_argument, NULL, 'B'}, 72 {"button-no-terminal", required_argument, NULL, 'B'},
57 {"button-dismiss", required_argument, NULL, 'z'}, 73 {"button-dismiss", required_argument, NULL, 'z'},
@@ -59,6 +75,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
59 {"config", required_argument, NULL, 'c'}, 75 {"config", required_argument, NULL, 'c'},
60 {"debug", no_argument, NULL, 'd'}, 76 {"debug", no_argument, NULL, 'd'},
61 {"edge", required_argument, NULL, 'e'}, 77 {"edge", required_argument, NULL, 'e'},
78 {"layer", required_argument, NULL, 'y'},
62 {"font", required_argument, NULL, 'f'}, 79 {"font", required_argument, NULL, 'f'},
63 {"help", no_argument, NULL, 'h'}, 80 {"help", no_argument, NULL, 'h'},
64 {"detailed-message", no_argument, NULL, 'l'}, 81 {"detailed-message", no_argument, NULL, 'l'},
@@ -104,6 +121,8 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
104 " -c, --config <path> Path to config file.\n" 121 " -c, --config <path> Path to config file.\n"
105 " -d, --debug Enable debugging.\n" 122 " -d, --debug Enable debugging.\n"
106 " -e, --edge top|bottom Set the edge to use.\n" 123 " -e, --edge top|bottom Set the edge to use.\n"
124 " -y, --layer overlay|top|bottom|background\n"
125 " Set the layer to use.\n"
107 " -f, --font <font> Set the font to use.\n" 126 " -f, --font <font> Set the font to use.\n"
108 " -h, --help Show help message and quit.\n" 127 " -h, --help Show help message and quit.\n"
109 " -l, --detailed-message Read a detailed message from stdin.\n" 128 " -l, --detailed-message Read a detailed message from stdin.\n"
@@ -133,7 +152,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
133 152
134 optind = 1; 153 optind = 1;
135 while (1) { 154 while (1) {
136 int c = getopt_long(argc, argv, "b:B:z:Z:c:de:f:hlL:m:o:s:t:v", opts, NULL); 155 int c = getopt_long(argc, argv, "b:B:z:Z:c:de:y:f:hlL:m:o:s:t:v", opts, NULL);
137 if (c == -1) { 156 if (c == -1) {
138 break; 157 break;
139 } 158 }
@@ -147,8 +166,11 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
147 fprintf(stderr, "Missing action for button %s\n", optarg); 166 fprintf(stderr, "Missing action for button %s\n", optarg);
148 return EXIT_FAILURE; 167 return EXIT_FAILURE;
149 } 168 }
150 struct swaynag_button *button; 169 struct swaynag_button *button = calloc(1, sizeof(struct swaynag_button));
151 button = calloc(sizeof(struct swaynag_button), 1); 170 if (!button) {
171 perror("calloc");
172 return EXIT_FAILURE;
173 }
152 button->text = strdup(optarg); 174 button->text = strdup(optarg);
153 button->type = SWAYNAG_ACTION_COMMAND; 175 button->type = SWAYNAG_ACTION_COMMAND;
154 button->action = strdup(argv[optind]); 176 button->action = strdup(argv[optind]);
@@ -184,24 +206,45 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
184 } 206 }
185 } 207 }
186 break; 208 break;
209 case 'y': // Layer
210 if (type) {
211 if (strcmp(optarg, "background") == 0) {
212 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
213 } else if (strcmp(optarg, "bottom") == 0) {
214 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
215 } else if (strcmp(optarg, "top") == 0) {
216 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
217 } else if (strcmp(optarg, "overlay") == 0) {
218 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
219 } else {
220 fprintf(stderr, "Invalid layer: %s\n"
221 "Usage: --layer overlay|top|bottom|background\n",
222 optarg);
223 return EXIT_FAILURE;
224 }
225 }
226 break;
187 case 'f': // Font 227 case 'f': // Font
188 if (type) { 228 if (type) {
189 free(type->font); 229 pango_font_description_free(type->font_description);
190 type->font = strdup(optarg); 230 type->font_description = pango_font_description_from_string(optarg);
191 } 231 }
192 break; 232 break;
193 case 'l': // Detailed Message 233 case 'l': // Detailed Message
194 if (swaynag) { 234 if (swaynag) {
195 free(swaynag->details.message); 235 free(swaynag->details.message);
196 swaynag->details.message = read_from_stdin(); 236 swaynag->details.message = read_and_trim_stdin();
237 if (!swaynag->details.message) {
238 return EXIT_FAILURE;
239 }
197 swaynag->details.button_up.text = strdup("â–²"); 240 swaynag->details.button_up.text = strdup("â–²");
198 swaynag->details.button_down.text = strdup("â–¼"); 241 swaynag->details.button_down.text = strdup("â–¼");
199 } 242 }
200 break; 243 break;
201 case 'L': // Detailed Button Text 244 case 'L': // Detailed Button Text
202 if (swaynag) { 245 if (swaynag) {
203 free(swaynag->details.button_details->text); 246 free(swaynag->details.details_text);
204 swaynag->details.button_details->text = strdup(optarg); 247 swaynag->details.details_text = strdup(optarg);
205 } 248 }
206 break; 249 break;
207 case 'm': // Message 250 case 'm': // Message
@@ -218,8 +261,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
218 break; 261 break;
219 case 's': // Dismiss Button Text 262 case 's': // Dismiss Button Text
220 if (swaynag) { 263 if (swaynag) {
221 struct swaynag_button *button_close; 264 struct swaynag_button *button_close = swaynag->buttons->items[0];
222 button_close = swaynag->buttons->items[0];
223 free(button_close->text); 265 free(button_close->text);
224 button_close->text = strdup(optarg); 266 button_close->text = strdup(optarg);
225 } 267 }
@@ -378,23 +420,24 @@ int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) {
378 420
379 if (line[0] == '[') { 421 if (line[0] == '[') {
380 char *close = strchr(line, ']'); 422 char *close = strchr(line, ']');
381 if (!close) { 423 if (!close || close != &line[nread - 2] || nread <= 3) {
382 fprintf(stderr, "Closing bracket not found on line %d\n", 424 fprintf(stderr, "Line %d is malformed\n", line_number);
383 line_number);
384 result = 1; 425 result = 1;
385 break; 426 break;
386 } 427 }
387 char *name = calloc(1, close - line); 428 *close = '\0';
388 strncat(name, line + 1, close - line - 1); 429 type = swaynag_type_get(types, &line[1]);
389 type = swaynag_type_get(types, name);
390 if (!type) { 430 if (!type) {
391 type = swaynag_type_new(name); 431 type = swaynag_type_new(&line[1]);
392 list_add(types, type); 432 list_add(types, type);
393 } 433 }
394 free(name);
395 } else { 434 } else {
396 char *flag = malloc(sizeof(char) * (nread + 3)); 435 char *flag = malloc(nread + 3);
397 sprintf(flag, "--%s", line); 436 if (!flag) {
437 perror("calloc");
438 return EXIT_FAILURE;
439 }
440 snprintf(flag, nread + 3, "--%s", line);
398 char *argv[] = {"swaynag", flag}; 441 char *argv[] = {"swaynag", flag};
399 result = swaynag_parse_options(2, argv, swaynag, types, type, 442 result = swaynag_parse_options(2, argv, swaynag, types, type,
400 NULL, NULL); 443 NULL, NULL);
diff --git a/swaynag/main.c b/swaynag/main.c
index 88007818..20390207 100644
--- a/swaynag/main.c
+++ b/swaynag/main.c
@@ -20,33 +20,27 @@ void sway_terminate(int code) {
20} 20}
21 21
22int main(int argc, char **argv) { 22int main(int argc, char **argv) {
23 int exit_code = EXIT_SUCCESS; 23 int status = EXIT_SUCCESS;
24 24
25 list_t *types = create_list(); 25 list_t *types = create_list();
26 swaynag_types_add_default(types); 26 swaynag_types_add_default(types);
27 27
28 memset(&swaynag, 0, sizeof(swaynag));
29 swaynag.buttons = create_list(); 28 swaynag.buttons = create_list();
30 wl_list_init(&swaynag.outputs); 29 wl_list_init(&swaynag.outputs);
31 wl_list_init(&swaynag.seats); 30 wl_list_init(&swaynag.seats);
32 31
33 struct swaynag_button *button_close = 32 struct swaynag_button *button_close = calloc(1, sizeof(struct swaynag_button));
34 calloc(sizeof(struct swaynag_button), 1);
35 button_close->text = strdup("X"); 33 button_close->text = strdup("X");
36 button_close->type = SWAYNAG_ACTION_DISMISS; 34 button_close->type = SWAYNAG_ACTION_DISMISS;
37 list_add(swaynag.buttons, button_close); 35 list_add(swaynag.buttons, button_close);
38 36
39 swaynag.details.button_details = 37 swaynag.details.details_text = strdup("Toggle details");
40 calloc(sizeof(struct swaynag_button), 1);
41 swaynag.details.button_details->text = strdup("Toggle details");
42 swaynag.details.button_details->type = SWAYNAG_ACTION_EXPAND;
43 38
44 char *config_path = NULL; 39 char *config_path = NULL;
45 bool debug = false; 40 bool debug = false;
46 int launch_status = swaynag_parse_options(argc, argv, NULL, NULL, NULL, 41 status = swaynag_parse_options(argc, argv, NULL, NULL, NULL,
47 &config_path, &debug); 42 &config_path, &debug);
48 if (launch_status != 0) { 43 if (status != 0) {
49 exit_code = launch_status;
50 goto cleanup; 44 goto cleanup;
51 } 45 }
52 sway_log_init(debug ? SWAY_DEBUG : SWAY_ERROR, NULL); 46 sway_log_init(debug ? SWAY_DEBUG : SWAY_ERROR, NULL);
@@ -56,29 +50,27 @@ int main(int argc, char **argv) {
56 } 50 }
57 if (config_path) { 51 if (config_path) {
58 sway_log(SWAY_DEBUG, "Loading config file: %s", config_path); 52 sway_log(SWAY_DEBUG, "Loading config file: %s", config_path);
59 int config_status = swaynag_load_config(config_path, &swaynag, types); 53 status = swaynag_load_config(config_path, &swaynag, types);
60 free(config_path); 54 if (status != 0) {
61 if (config_status != 0) {
62 exit_code = config_status;
63 goto cleanup; 55 goto cleanup;
64 } 56 }
65 } 57 }
66 58
59
67 if (argc > 1) { 60 if (argc > 1) {
68 struct swaynag_type *type_args = swaynag_type_new("<args>"); 61 struct swaynag_type *type_args = swaynag_type_new("<args>");
69 list_add(types, type_args); 62 list_add(types, type_args);
70 63
71 int result = swaynag_parse_options(argc, argv, &swaynag, types, 64 status = swaynag_parse_options(argc, argv, &swaynag, types,
72 type_args, NULL, NULL); 65 type_args, NULL, NULL);
73 if (result != 0) { 66 if (status != 0) {
74 exit_code = result;
75 goto cleanup; 67 goto cleanup;
76 } 68 }
77 } 69 }
78 70
79 if (!swaynag.message) { 71 if (!swaynag.message) {
80 sway_log(SWAY_ERROR, "No message passed. Please provide --message/-m"); 72 sway_log(SWAY_ERROR, "No message passed. Please provide --message/-m");
81 exit_code = EXIT_FAILURE; 73 status = EXIT_FAILURE;
82 goto cleanup; 74 goto cleanup;
83 } 75 }
84 76
@@ -96,20 +88,20 @@ int main(int argc, char **argv) {
96 swaynag_type_merge(type, swaynag_type_get(types, "<args>")); 88 swaynag_type_merge(type, swaynag_type_get(types, "<args>"));
97 swaynag.type = type; 89 swaynag.type = type;
98 90
99 swaynag_types_free(types);
100
101 if (swaynag.details.message) { 91 if (swaynag.details.message) {
92 swaynag.details.button_details = calloc(1, sizeof(struct swaynag_button));
93 swaynag.details.button_details->text = strdup(swaynag.details.details_text);
94 swaynag.details.button_details->type = SWAYNAG_ACTION_EXPAND;
102 list_add(swaynag.buttons, swaynag.details.button_details); 95 list_add(swaynag.buttons, swaynag.details.button_details);
103 } else {
104 free(swaynag.details.button_details->text);
105 free(swaynag.details.button_details);
106 } 96 }
107 97
108 sway_log(SWAY_DEBUG, "Output: %s", swaynag.type->output); 98 sway_log(SWAY_DEBUG, "Output: %s", swaynag.type->output);
109 sway_log(SWAY_DEBUG, "Anchors: %" PRIu32, swaynag.type->anchors); 99 sway_log(SWAY_DEBUG, "Anchors: %" PRIu32, swaynag.type->anchors);
110 sway_log(SWAY_DEBUG, "Type: %s", swaynag.type->name); 100 sway_log(SWAY_DEBUG, "Type: %s", swaynag.type->name);
111 sway_log(SWAY_DEBUG, "Message: %s", swaynag.message); 101 sway_log(SWAY_DEBUG, "Message: %s", swaynag.message);
112 sway_log(SWAY_DEBUG, "Font: %s", swaynag.type->font); 102 char *font = pango_font_description_to_string(swaynag.type->font_description);
103 sway_log(SWAY_DEBUG, "Font: %s", font);
104 free(font);
113 sway_log(SWAY_DEBUG, "Buttons"); 105 sway_log(SWAY_DEBUG, "Buttons");
114 for (int i = 0; i < swaynag.buttons->length; i++) { 106 for (int i = 0; i < swaynag.buttons->length; i++) {
115 struct swaynag_button *button = swaynag.buttons->items[i]; 107 struct swaynag_button *button = swaynag.buttons->items[i];
@@ -120,12 +112,9 @@ int main(int argc, char **argv) {
120 112
121 swaynag_setup(&swaynag); 113 swaynag_setup(&swaynag);
122 swaynag_run(&swaynag); 114 swaynag_run(&swaynag);
123 return exit_code;
124 115
125cleanup: 116cleanup:
126 swaynag_types_free(types); 117 swaynag_types_free(types);
127 free(swaynag.details.button_details->text);
128 free(swaynag.details.button_details);
129 swaynag_destroy(&swaynag); 118 swaynag_destroy(&swaynag);
130 return exit_code; 119 return status;
131} 120}
diff --git a/swaynag/meson.build b/swaynag/meson.build
index 71f2fc2d..aef21483 100644
--- a/swaynag/meson.build
+++ b/swaynag/meson.build
@@ -5,13 +5,14 @@ executable(
5 'render.c', 5 'render.c',
6 'swaynag.c', 6 'swaynag.c',
7 'types.c', 7 'types.c',
8 wl_protos_src,
8 ], 9 ],
9 include_directories: [sway_inc], 10 include_directories: [sway_inc],
10 dependencies: [ 11 dependencies: [
11 cairo, 12 cairo,
12 client_protos,
13 pango, 13 pango,
14 pangocairo, 14 pangocairo,
15 rt,
15 wayland_client, 16 wayland_client,
16 wayland_cursor, 17 wayland_cursor,
17 ], 18 ],
diff --git a/swaynag/render.c b/swaynag/render.c
index cf2cc9e0..21b03289 100644
--- a/swaynag/render.c
+++ b/swaynag/render.c
@@ -1,5 +1,5 @@
1#include <stdint.h> 1#include <stdint.h>
2#include "cairo.h" 2#include "cairo_util.h"
3#include "log.h" 3#include "log.h"
4#include "pango.h" 4#include "pango.h"
5#include "pool-buffer.h" 5#include "pool-buffer.h"
@@ -9,20 +9,20 @@
9 9
10static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) { 10static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) {
11 int text_width, text_height; 11 int text_width, text_height;
12 get_text_size(cairo, swaynag->type->font, &text_width, &text_height, NULL, 12 get_text_size(cairo, swaynag->type->font_description, &text_width, &text_height, NULL,
13 swaynag->scale, true, "%s", swaynag->message); 13 1, true, "%s", swaynag->message);
14 14
15 int padding = swaynag->type->message_padding * swaynag->scale; 15 int padding = swaynag->type->message_padding;
16 16
17 uint32_t ideal_height = text_height + padding * 2; 17 uint32_t ideal_height = text_height + padding * 2;
18 uint32_t ideal_surface_height = ideal_height / swaynag->scale; 18 uint32_t ideal_surface_height = ideal_height;
19 if (swaynag->height < ideal_surface_height) { 19 if (swaynag->height < ideal_surface_height) {
20 return ideal_surface_height; 20 return ideal_surface_height;
21 } 21 }
22 22
23 cairo_set_source_u32(cairo, swaynag->type->text); 23 cairo_set_source_u32(cairo, swaynag->type->text);
24 cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2); 24 cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2);
25 pango_printf(cairo, swaynag->type->font, swaynag->scale, false, 25 render_text(cairo, swaynag->type->font_description, 1, false,
26 "%s", swaynag->message); 26 "%s", swaynag->message);
27 27
28 return ideal_surface_height; 28 return ideal_surface_height;
@@ -31,11 +31,11 @@ static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) {
31static void render_details_scroll_button(cairo_t *cairo, 31static void render_details_scroll_button(cairo_t *cairo,
32 struct swaynag *swaynag, struct swaynag_button *button) { 32 struct swaynag *swaynag, struct swaynag_button *button) {
33 int text_width, text_height; 33 int text_width, text_height;
34 get_text_size(cairo, swaynag->type->font, &text_width, &text_height, NULL, 34 get_text_size(cairo, swaynag->type->font_description, &text_width, &text_height, NULL,
35 swaynag->scale, true, "%s", button->text); 35 1, true, "%s", button->text);
36 36
37 int border = swaynag->type->button_border_thickness * swaynag->scale; 37 int border = swaynag->type->button_border_thickness;
38 int padding = swaynag->type->button_padding * swaynag->scale; 38 int padding = swaynag->type->button_padding;
39 39
40 cairo_set_source_u32(cairo, swaynag->type->details_background); 40 cairo_set_source_u32(cairo, swaynag->type->details_background);
41 cairo_rectangle(cairo, button->x, button->y, 41 cairo_rectangle(cairo, button->x, button->y,
@@ -50,41 +50,41 @@ static void render_details_scroll_button(cairo_t *cairo,
50 cairo_set_source_u32(cairo, swaynag->type->button_text); 50 cairo_set_source_u32(cairo, swaynag->type->button_text);
51 cairo_move_to(cairo, button->x + border + padding, 51 cairo_move_to(cairo, button->x + border + padding,
52 button->y + border + (button->height - text_height) / 2); 52 button->y + border + (button->height - text_height) / 2);
53 pango_printf(cairo, swaynag->type->font, swaynag->scale, true, 53 render_text(cairo, swaynag->type->font_description, 1, true,
54 "%s", button->text); 54 "%s", button->text);
55} 55}
56 56
57static int get_detailed_scroll_button_width(cairo_t *cairo, 57static int get_detailed_scroll_button_width(cairo_t *cairo,
58 struct swaynag *swaynag) { 58 struct swaynag *swaynag) {
59 int up_width, down_width, temp_height; 59 int up_width, down_width, temp_height;
60 get_text_size(cairo, swaynag->type->font, &up_width, &temp_height, NULL, 60 get_text_size(cairo, swaynag->type->font_description, &up_width, &temp_height, NULL,
61 swaynag->scale, true, 61 1, true,
62 "%s", swaynag->details.button_up.text); 62 "%s", swaynag->details.button_up.text);
63 get_text_size(cairo, swaynag->type->font, &down_width, &temp_height, NULL, 63 get_text_size(cairo, swaynag->type->font_description, &down_width, &temp_height, NULL,
64 swaynag->scale, true, 64 1, true,
65 "%s", swaynag->details.button_down.text); 65 "%s", swaynag->details.button_down.text);
66 66
67 int text_width = up_width > down_width ? up_width : down_width; 67 int text_width = up_width > down_width ? up_width : down_width;
68 int border = swaynag->type->button_border_thickness * swaynag->scale; 68 int border = swaynag->type->button_border_thickness;
69 int padding = swaynag->type->button_padding * swaynag->scale; 69 int padding = swaynag->type->button_padding;
70 70
71 return text_width + border * 2 + padding * 2; 71 return text_width + border * 2 + padding * 2;
72} 72}
73 73
74static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag, 74static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag,
75 uint32_t y) { 75 uint32_t y) {
76 uint32_t width = swaynag->width * swaynag->scale; 76 uint32_t width = swaynag->width;
77 77
78 int border = swaynag->type->details_border_thickness * swaynag->scale; 78 int border = swaynag->type->details_border_thickness;
79 int padding = swaynag->type->message_padding * swaynag->scale; 79 int padding = swaynag->type->message_padding;
80 int decor = padding + border; 80 int decor = padding + border;
81 81
82 swaynag->details.x = decor; 82 swaynag->details.x = decor;
83 swaynag->details.y = y * swaynag->scale + decor; 83 swaynag->details.y = y + decor;
84 swaynag->details.width = width - decor * 2; 84 swaynag->details.width = width - decor * 2;
85 85
86 PangoLayout *layout = get_pango_layout(cairo, swaynag->type->font, 86 PangoLayout *layout = get_pango_layout(cairo, swaynag->type->font_description,
87 swaynag->details.message, swaynag->scale, false); 87 swaynag->details.message, 1, false);
88 pango_layout_set_width(layout, 88 pango_layout_set_width(layout,
89 (swaynag->details.width - padding * 2) * PANGO_SCALE); 89 (swaynag->details.width - padding * 2) * PANGO_SCALE);
90 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); 90 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
@@ -164,7 +164,7 @@ static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag,
164 pango_cairo_show_layout(cairo, layout); 164 pango_cairo_show_layout(cairo, layout);
165 g_object_unref(layout); 165 g_object_unref(layout);
166 166
167 return ideal_height / swaynag->scale; 167 return ideal_height;
168} 168}
169 169
170static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag, 170static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag,
@@ -172,14 +172,14 @@ static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag,
172 struct swaynag_button *button = swaynag->buttons->items[button_index]; 172 struct swaynag_button *button = swaynag->buttons->items[button_index];
173 173
174 int text_width, text_height; 174 int text_width, text_height;
175 get_text_size(cairo, swaynag->type->font, &text_width, &text_height, NULL, 175 get_text_size(cairo, swaynag->type->font_description, &text_width, &text_height, NULL,
176 swaynag->scale, true, "%s", button->text); 176 1, true, "%s", button->text);
177 177
178 int border = swaynag->type->button_border_thickness * swaynag->scale; 178 int border = swaynag->type->button_border_thickness;
179 int padding = swaynag->type->button_padding * swaynag->scale; 179 int padding = swaynag->type->button_padding;
180 180
181 uint32_t ideal_height = text_height + padding * 2 + border * 2; 181 uint32_t ideal_height = text_height + padding * 2 + border * 2;
182 uint32_t ideal_surface_height = ideal_height / swaynag->scale; 182 uint32_t ideal_surface_height = ideal_height;
183 if (swaynag->height < ideal_surface_height) { 183 if (swaynag->height < ideal_surface_height) {
184 return ideal_surface_height; 184 return ideal_surface_height;
185 } 185 }
@@ -201,7 +201,7 @@ static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag,
201 201
202 cairo_set_source_u32(cairo, swaynag->type->button_text); 202 cairo_set_source_u32(cairo, swaynag->type->button_text);
203 cairo_move_to(cairo, button->x + padding, button->y + padding); 203 cairo_move_to(cairo, button->x + padding, button->y + padding);
204 pango_printf(cairo, swaynag->type->font, swaynag->scale, true, 204 render_text(cairo, swaynag->type->font_description, 1, true,
205 "%s", button->text); 205 "%s", button->text);
206 206
207 *x = button->x - border; 207 *x = button->x - border;
@@ -220,13 +220,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaynag *swaynag) {
220 max_height = h > max_height ? h : max_height; 220 max_height = h > max_height ? h : max_height;
221 221
222 int x = swaynag->width - swaynag->type->button_margin_right; 222 int x = swaynag->width - swaynag->type->button_margin_right;
223 x *= swaynag->scale;
224 for (int i = 0; i < swaynag->buttons->length; i++) { 223 for (int i = 0; i < swaynag->buttons->length; i++) {
225 h = render_button(cairo, swaynag, i, &x); 224 h = render_button(cairo, swaynag, i, &x);
226 max_height = h > max_height ? h : max_height; 225 max_height = h > max_height ? h : max_height;
227 x -= swaynag->type->button_gap * swaynag->scale; 226 x -= swaynag->type->button_gap;
228 if (i == 0) { 227 if (i == 0) {
229 x -= swaynag->type->button_gap_close * swaynag->scale; 228 x -= swaynag->type->button_gap_close;
230 } 229 }
231 } 230 }
232 231
@@ -235,14 +234,14 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaynag *swaynag) {
235 max_height = h > max_height ? h : max_height; 234 max_height = h > max_height ? h : max_height;
236 } 235 }
237 236
238 int border = swaynag->type->bar_border_thickness * swaynag->scale; 237 int border = swaynag->type->bar_border_thickness;
239 if (max_height > swaynag->height) { 238 if (max_height > swaynag->height) {
240 max_height += border; 239 max_height += border;
241 } 240 }
242 cairo_set_source_u32(cairo, swaynag->type->border_bottom); 241 cairo_set_source_u32(cairo, swaynag->type->border_bottom);
243 cairo_rectangle(cairo, 0, 242 cairo_rectangle(cairo, 0,
244 swaynag->height * swaynag->scale - border, 243 swaynag->height - border,
245 swaynag->width * swaynag->scale, 244 swaynag->width,
246 border); 245 border);
247 cairo_fill(cairo); 246 cairo_fill(cairo);
248 247
@@ -257,6 +256,7 @@ void render_frame(struct swaynag *swaynag) {
257 cairo_surface_t *recorder = cairo_recording_surface_create( 256 cairo_surface_t *recorder = cairo_recording_surface_create(
258 CAIRO_CONTENT_COLOR_ALPHA, NULL); 257 CAIRO_CONTENT_COLOR_ALPHA, NULL);
259 cairo_t *cairo = cairo_create(recorder); 258 cairo_t *cairo = cairo_create(recorder);
259 cairo_scale(cairo, swaynag->scale, swaynag->scale);
260 cairo_save(cairo); 260 cairo_save(cairo);
261 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 261 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
262 cairo_paint(cairo); 262 cairo_paint(cairo);
diff --git a/swaynag/swaynag.1.scd b/swaynag/swaynag.1.scd
index 4a03469e..1cc85db7 100644
--- a/swaynag/swaynag.1.scd
+++ b/swaynag/swaynag.1.scd
@@ -48,6 +48,9 @@ _swaynag_ [options...]
48*-e, --edge* top|bottom 48*-e, --edge* top|bottom
49 Set the edge to use. 49 Set the edge to use.
50 50
51*-y, --layer* overlay|top|bottom|background
52 Set the layer to use.
53
51*-f, --font* <font> 54*-f, --font* <font>
52 Set the font to use. 55 Set the font to use.
53 56
diff --git a/swaynag/swaynag.5.scd b/swaynag/swaynag.5.scd
index a078a4d7..3c367d0f 100644
--- a/swaynag/swaynag.5.scd
+++ b/swaynag/swaynag.5.scd
@@ -53,7 +53,7 @@ The following sizing options can also be set:
53*message-padding=<padding>* 53*message-padding=<padding>*
54 Set the padding for the message. 54 Set the padding for the message.
55 55
56*details-gackground=<color>* 56*details-background=<color>*
57 The background color for the details. 57 The background color for the details.
58 58
59*details-border-size=<size>* 59*details-border-size=<size>*
@@ -79,6 +79,9 @@ Additionally, the following options can be assigned a default per-type:
79*edge=top|bottom* 79*edge=top|bottom*
80 Set the edge to use. 80 Set the edge to use.
81 81
82*layer=overlay|top|bottom|background*
83 Set the layer to use.
84
82*font=<font>* 85*font=<font>*
83 Set the font to use. 86 Set the font to use.
84 87
diff --git a/swaynag/swaynag.c b/swaynag/swaynag.c
index d9bec368..6ea739e3 100644
--- a/swaynag/swaynag.c
+++ b/swaynag/swaynag.c
@@ -28,10 +28,15 @@ static bool terminal_execute(char *terminal, char *command) {
28 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command); 28 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command);
29 fclose(tmp); 29 fclose(tmp);
30 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR); 30 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR);
31 char *cmd = malloc(sizeof(char) * (strlen(terminal) + strlen(" -e ") + strlen(fname) + 1)); 31 size_t cmd_size = strlen(terminal) + strlen(" -e ") + strlen(fname) + 1;
32 sprintf(cmd, "%s -e %s", terminal, fname); 32 char *cmd = malloc(cmd_size);
33 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); 33 if (!cmd) {
34 sway_log_errno(SWAY_ERROR, "Failed to run command, execl() returned."); 34 perror("malloc");
35 return false;
36 }
37 snprintf(cmd, cmd_size, "%s -e %s", terminal, fname);
38 execlp("sh", "sh", "-c", cmd, NULL);
39 sway_log_errno(SWAY_ERROR, "Failed to run command, execlp() returned.");
35 free(cmd); 40 free(cmd);
36 return false; 41 return false;
37} 42}
@@ -58,7 +63,7 @@ static void swaynag_button_execute(struct swaynag *swaynag,
58 } else if (pid == 0) { 63 } else if (pid == 0) {
59 // Child of the child. Will be reparented to the init process 64 // Child of the child. Will be reparented to the init process
60 char *terminal = getenv("TERMINAL"); 65 char *terminal = getenv("TERMINAL");
61 if (button->terminal && terminal && strlen(terminal)) { 66 if (button->terminal && terminal && *terminal) {
62 sway_log(SWAY_DEBUG, "Found $TERMINAL: %s", terminal); 67 sway_log(SWAY_DEBUG, "Found $TERMINAL: %s", terminal);
63 if (!terminal_execute(terminal, button->action)) { 68 if (!terminal_execute(terminal, button->action)) {
64 swaynag_destroy(swaynag); 69 swaynag_destroy(swaynag);
@@ -69,8 +74,8 @@ static void swaynag_button_execute(struct swaynag *swaynag,
69 sway_log(SWAY_DEBUG, 74 sway_log(SWAY_DEBUG,
70 "$TERMINAL not found. Running directly"); 75 "$TERMINAL not found. Running directly");
71 } 76 }
72 execl("/bin/sh", "/bin/sh", "-c", button->action, NULL); 77 execlp("sh", "sh", "-c", button->action, NULL);
73 sway_log_errno(SWAY_DEBUG, "execl failed"); 78 sway_log_errno(SWAY_DEBUG, "execlp failed");
74 _exit(EXIT_FAILURE); 79 _exit(EXIT_FAILURE);
75 } 80 }
76 } 81 }
@@ -103,7 +108,7 @@ static void layer_surface_closed(void *data,
103 swaynag_destroy(swaynag); 108 swaynag_destroy(swaynag);
104} 109}
105 110
106static struct zwlr_layer_surface_v1_listener layer_surface_listener = { 111static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
107 .configure = layer_surface_configure, 112 .configure = layer_surface_configure,
108 .closed = layer_surface_closed, 113 .closed = layer_surface_closed,
109}; 114};
@@ -124,7 +129,7 @@ static void surface_enter(void *data, struct wl_surface *surface,
124 }; 129 };
125} 130}
126 131
127static struct wl_surface_listener surface_listener = { 132static const struct wl_surface_listener surface_listener = {
128 .enter = surface_enter, 133 .enter = surface_enter,
129 .leave = nop, 134 .leave = nop,
130}; 135};
@@ -138,7 +143,7 @@ static void update_cursor(struct swaynag_seat *seat) {
138 const char *cursor_theme = getenv("XCURSOR_THEME"); 143 const char *cursor_theme = getenv("XCURSOR_THEME");
139 unsigned cursor_size = 24; 144 unsigned cursor_size = 24;
140 const char *env_cursor_size = getenv("XCURSOR_SIZE"); 145 const char *env_cursor_size = getenv("XCURSOR_SIZE");
141 if (env_cursor_size && strlen(env_cursor_size) > 0) { 146 if (env_cursor_size && *env_cursor_size) {
142 errno = 0; 147 errno = 0;
143 char *end; 148 char *end;
144 unsigned size = strtoul(env_cursor_size, &end, 10); 149 unsigned size = strtoul(env_cursor_size, &end, 10);
@@ -148,8 +153,15 @@ static void update_cursor(struct swaynag_seat *seat) {
148 } 153 }
149 pointer->cursor_theme = wl_cursor_theme_load( 154 pointer->cursor_theme = wl_cursor_theme_load(
150 cursor_theme, cursor_size * swaynag->scale, swaynag->shm); 155 cursor_theme, cursor_size * swaynag->scale, swaynag->shm);
151 struct wl_cursor *cursor = 156 if (!pointer->cursor_theme) {
152 wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); 157 sway_log(SWAY_ERROR, "Failed to load cursor theme");
158 return;
159 }
160 struct wl_cursor *cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "default");
161 if (!cursor) {
162 sway_log(SWAY_ERROR, "Failed to get default cursor from theme");
163 return;
164 }
153 pointer->cursor_image = cursor->images[0]; 165 pointer->cursor_image = cursor->images[0];
154 wl_surface_set_buffer_scale(pointer->cursor_surface, 166 wl_surface_set_buffer_scale(pointer->cursor_surface,
155 swaynag->scale); 167 swaynag->scale);
@@ -177,9 +189,22 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
177 uint32_t serial, struct wl_surface *surface, 189 uint32_t serial, struct wl_surface *surface,
178 wl_fixed_t surface_x, wl_fixed_t surface_y) { 190 wl_fixed_t surface_x, wl_fixed_t surface_y) {
179 struct swaynag_seat *seat = data; 191 struct swaynag_seat *seat = data;
192
180 struct swaynag_pointer *pointer = &seat->pointer; 193 struct swaynag_pointer *pointer = &seat->pointer;
181 pointer->serial = serial; 194 pointer->x = wl_fixed_to_int(surface_x);
182 update_cursor(seat); 195 pointer->y = wl_fixed_to_int(surface_y);
196
197 if (seat->swaynag->cursor_shape_manager) {
198 struct wp_cursor_shape_device_v1 *device =
199 wp_cursor_shape_manager_v1_get_pointer(
200 seat->swaynag->cursor_shape_manager, wl_pointer);
201 wp_cursor_shape_device_v1_set_shape(device, serial,
202 WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
203 wp_cursor_shape_device_v1_destroy(device);
204 } else {
205 pointer->serial = serial;
206 update_cursor(seat);
207 }
183} 208}
184 209
185static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 210static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
@@ -198,8 +223,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
198 return; 223 return;
199 } 224 }
200 225
201 double x = seat->pointer.x * swaynag->scale; 226 double x = seat->pointer.x;
202 double y = seat->pointer.y * swaynag->scale; 227 double y = seat->pointer.y;
203 for (int i = 0; i < swaynag->buttons->length; i++) { 228 for (int i = 0; i < swaynag->buttons->length; i++) {
204 struct swaynag_button *nagbutton = swaynag->buttons->items[i]; 229 struct swaynag_button *nagbutton = swaynag->buttons->items[i];
205 if (x >= nagbutton->x 230 if (x >= nagbutton->x
@@ -263,7 +288,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
263 render_frame(swaynag); 288 render_frame(swaynag);
264} 289}
265 290
266static struct wl_pointer_listener pointer_listener = { 291static const struct wl_pointer_listener pointer_listener = {
267 .enter = wl_pointer_enter, 292 .enter = wl_pointer_enter,
268 .leave = nop, 293 .leave = nop,
269 .motion = wl_pointer_motion, 294 .motion = wl_pointer_motion,
@@ -289,7 +314,7 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
289 } 314 }
290} 315}
291 316
292const struct wl_seat_listener seat_listener = { 317static const struct wl_seat_listener seat_listener = {
293 .capabilities = seat_handle_capabilities, 318 .capabilities = seat_handle_capabilities,
294 .name = nop, 319 .name = nop,
295}; 320};
@@ -305,33 +330,25 @@ static void output_scale(void *data, struct wl_output *output,
305 } 330 }
306} 331}
307 332
308static struct wl_output_listener output_listener = { 333static void output_name(void *data, struct wl_output *output,
309 .geometry = nop, 334 const char *name) {
310 .mode = nop,
311 .done = nop,
312 .scale = output_scale,
313};
314
315static void xdg_output_handle_name(void *data,
316 struct zxdg_output_v1 *xdg_output, const char *name) {
317 struct swaynag_output *swaynag_output = data; 335 struct swaynag_output *swaynag_output = data;
318 char *outname = swaynag_output->swaynag->type->output; 336 swaynag_output->name = strdup(name);
319 sway_log(SWAY_DEBUG, "Checking against output %s for %s", name, outname); 337
320 if (!swaynag_output->swaynag->output && outname && name 338 const char *outname = swaynag_output->swaynag->type->output;
321 && strcmp(outname, name) == 0) { 339 if (!swaynag_output->swaynag->output && outname &&
340 strcmp(outname, name) == 0) {
322 sway_log(SWAY_DEBUG, "Using output %s", name); 341 sway_log(SWAY_DEBUG, "Using output %s", name);
323 swaynag_output->swaynag->output = swaynag_output; 342 swaynag_output->swaynag->output = swaynag_output;
324 } 343 }
325 swaynag_output->name = strdup(name);
326 zxdg_output_v1_destroy(xdg_output);
327 swaynag_output->swaynag->querying_outputs--;
328} 344}
329 345
330static struct zxdg_output_v1_listener xdg_output_listener = { 346static const struct wl_output_listener output_listener = {
331 .logical_position = nop, 347 .geometry = nop,
332 .logical_size = nop, 348 .mode = nop,
333 .done = nop, 349 .done = nop,
334 .name = xdg_output_handle_name, 350 .scale = output_scale,
351 .name = output_name,
335 .description = nop, 352 .description = nop,
336}; 353};
337 354
@@ -345,6 +362,7 @@ static void handle_global(void *data, struct wl_registry *registry,
345 struct swaynag_seat *seat = 362 struct swaynag_seat *seat =
346 calloc(1, sizeof(struct swaynag_seat)); 363 calloc(1, sizeof(struct swaynag_seat));
347 if (!seat) { 364 if (!seat) {
365 perror("calloc");
348 return; 366 return;
349 } 367 }
350 368
@@ -359,33 +377,28 @@ static void handle_global(void *data, struct wl_registry *registry,
359 } else if (strcmp(interface, wl_shm_interface.name) == 0) { 377 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
360 swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 378 swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
361 } else if (strcmp(interface, wl_output_interface.name) == 0) { 379 } else if (strcmp(interface, wl_output_interface.name) == 0) {
362 if (!swaynag->output && swaynag->xdg_output_manager) { 380 if (!swaynag->output) {
363 swaynag->querying_outputs++;
364 struct swaynag_output *output = 381 struct swaynag_output *output =
365 calloc(1, sizeof(struct swaynag_output)); 382 calloc(1, sizeof(struct swaynag_output));
383 if (!output) {
384 perror("calloc");
385 return;
386 }
366 output->wl_output = wl_registry_bind(registry, name, 387 output->wl_output = wl_registry_bind(registry, name,
367 &wl_output_interface, 3); 388 &wl_output_interface, 4);
368 output->wl_name = name; 389 output->wl_name = name;
369 output->scale = 1; 390 output->scale = 1;
370 output->swaynag = swaynag; 391 output->swaynag = swaynag;
371 wl_list_insert(&swaynag->outputs, &output->link); 392 wl_list_insert(&swaynag->outputs, &output->link);
372 wl_output_add_listener(output->wl_output, 393 wl_output_add_listener(output->wl_output,
373 &output_listener, output); 394 &output_listener, output);
374
375 struct zxdg_output_v1 *xdg_output;
376 xdg_output = zxdg_output_manager_v1_get_xdg_output(
377 swaynag->xdg_output_manager, output->wl_output);
378 zxdg_output_v1_add_listener(xdg_output,
379 &xdg_output_listener, output);
380 } 395 }
381 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { 396 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
382 swaynag->layer_shell = wl_registry_bind( 397 swaynag->layer_shell = wl_registry_bind(
383 registry, name, &zwlr_layer_shell_v1_interface, 1); 398 registry, name, &zwlr_layer_shell_v1_interface, 1);
384 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 399 } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
385 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { 400 swaynag->cursor_shape_manager = wl_registry_bind(
386 swaynag->xdg_output_manager = wl_registry_bind(registry, name, 401 registry, name, &wp_cursor_shape_manager_v1_interface, 1);
387 &zxdg_output_manager_v1_interface,
388 ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
389 } 402 }
390} 403}
391 404
@@ -451,12 +464,11 @@ void swaynag_setup(struct swaynag *swaynag) {
451 464
452 assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm); 465 assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm);
453 466
454 while (swaynag->querying_outputs > 0) { 467 // Second roundtrip to get wl_output properties
455 if (wl_display_roundtrip(swaynag->display) < 0) { 468 if (wl_display_roundtrip(swaynag->display) < 0) {
456 sway_log(SWAY_ERROR, "Error during outputs init."); 469 sway_log(SWAY_ERROR, "Error during outputs init.");
457 swaynag_destroy(swaynag); 470 swaynag_destroy(swaynag);
458 exit(EXIT_FAILURE); 471 exit(EXIT_FAILURE);
459 }
460 } 472 }
461 473
462 if (!swaynag->output && swaynag->type->output) { 474 if (!swaynag->output && swaynag->type->output) {
@@ -465,7 +477,9 @@ void swaynag_setup(struct swaynag *swaynag) {
465 exit(EXIT_FAILURE); 477 exit(EXIT_FAILURE);
466 } 478 }
467 479
468 swaynag_setup_cursors(swaynag); 480 if (!swaynag->cursor_shape_manager) {
481 swaynag_setup_cursors(swaynag);
482 }
469 483
470 swaynag->surface = wl_compositor_create_surface(swaynag->compositor); 484 swaynag->surface = wl_compositor_create_surface(swaynag->compositor);
471 assert(swaynag->surface); 485 assert(swaynag->surface);
@@ -474,7 +488,8 @@ void swaynag_setup(struct swaynag *swaynag) {
474 swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface( 488 swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
475 swaynag->layer_shell, swaynag->surface, 489 swaynag->layer_shell, swaynag->surface,
476 swaynag->output ? swaynag->output->wl_output : NULL, 490 swaynag->output ? swaynag->output->wl_output : NULL,
477 ZWLR_LAYER_SHELL_V1_LAYER_TOP, "swaynag"); 491 swaynag->type->layer,
492 "swaynag");
478 assert(swaynag->layer_surface); 493 assert(swaynag->layer_surface);
479 zwlr_layer_surface_v1_add_listener(swaynag->layer_surface, 494 zwlr_layer_surface_v1_add_listener(swaynag->layer_surface,
480 &layer_surface_listener, swaynag); 495 &layer_surface_listener, swaynag);
@@ -491,10 +506,6 @@ void swaynag_run(struct swaynag *swaynag) {
491 && wl_display_dispatch(swaynag->display) != -1) { 506 && wl_display_dispatch(swaynag->display) != -1) {
492 // This is intentionally left blank 507 // This is intentionally left blank
493 } 508 }
494
495 if (swaynag->display) {
496 wl_display_disconnect(swaynag->display);
497 }
498} 509}
499 510
500void swaynag_destroy(struct swaynag *swaynag) { 511void swaynag_destroy(struct swaynag *swaynag) {
@@ -509,6 +520,7 @@ void swaynag_destroy(struct swaynag *swaynag) {
509 } 520 }
510 list_free(swaynag->buttons); 521 list_free(swaynag->buttons);
511 free(swaynag->details.message); 522 free(swaynag->details.message);
523 free(swaynag->details.details_text);
512 free(swaynag->details.button_up.text); 524 free(swaynag->details.button_up.text);
513 free(swaynag->details.button_down.text); 525 free(swaynag->details.button_down.text);
514 526
@@ -529,13 +541,8 @@ void swaynag_destroy(struct swaynag *swaynag) {
529 swaynag_seat_destroy(seat); 541 swaynag_seat_destroy(seat);
530 } 542 }
531 543
532 if (&swaynag->buffers[0]) { 544 destroy_buffer(&swaynag->buffers[0]);
533 destroy_buffer(&swaynag->buffers[0]); 545 destroy_buffer(&swaynag->buffers[1]);
534 }
535
536 if (&swaynag->buffers[1]) {
537 destroy_buffer(&swaynag->buffers[1]);
538 }
539 546
540 if (swaynag->outputs.prev || swaynag->outputs.next) { 547 if (swaynag->outputs.prev || swaynag->outputs.next) {
541 struct swaynag_output *output, *temp; 548 struct swaynag_output *output, *temp;
@@ -554,4 +561,8 @@ void swaynag_destroy(struct swaynag *swaynag) {
554 if (swaynag->shm) { 561 if (swaynag->shm) {
555 wl_shm_destroy(swaynag->shm); 562 wl_shm_destroy(swaynag->shm);
556 } 563 }
564
565 if (swaynag->display) {
566 wl_display_disconnect(swaynag->display);
567 }
557} 568}
diff --git a/swaynag/types.c b/swaynag/types.c
index fa045532..409cc668 100644
--- a/swaynag/types.c
+++ b/swaynag/types.c
@@ -26,15 +26,18 @@ struct swaynag_type *swaynag_type_new(const char *name) {
26 type->button_gap_close = -1; 26 type->button_gap_close = -1;
27 type->button_margin_right = -1; 27 type->button_margin_right = -1;
28 type->button_padding = -1; 28 type->button_padding = -1;
29 type->layer = -1;
29 return type; 30 return type;
30} 31}
31 32
32void swaynag_types_add_default(list_t *types) { 33void swaynag_types_add_default(list_t *types) {
33 struct swaynag_type *type_defaults = swaynag_type_new("<defaults>"); 34 struct swaynag_type *type_defaults = swaynag_type_new("<defaults>");
34 type_defaults->font = strdup("pango:Monospace 10"); 35 type_defaults->font_description =
36 pango_font_description_from_string("pango:Monospace 10");
35 type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP 37 type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
36 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT 38 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
37 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; 39 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
40 type_defaults->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
38 type_defaults->button_background = 0x333333FF; 41 type_defaults->button_background = 0x333333FF;
39 type_defaults->details_background = 0x333333FF; 42 type_defaults->details_background = 0x333333FF;
40 type_defaults->background = 0x323232FF; 43 type_defaults->background = 0x323232FF;
@@ -88,8 +91,8 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
88 return; 91 return;
89 } 92 }
90 93
91 if (src->font) { 94 if (src->font_description) {
92 dest->font = strdup(src->font); 95 dest->font_description = pango_font_description_copy(src->font_description);
93 } 96 }
94 97
95 if (src->output) { 98 if (src->output) {
@@ -100,6 +103,10 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
100 dest->anchors = src->anchors; 103 dest->anchors = src->anchors;
101 } 104 }
102 105
106 if (src->layer >= 0) {
107 dest->layer = src->layer;
108 }
109
103 // Colors 110 // Colors
104 if (src->button_background > 0) { 111 if (src->button_background > 0) {
105 dest->button_background = src->button_background; 112 dest->button_background = src->button_background;
@@ -166,7 +173,7 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
166 173
167void swaynag_type_free(struct swaynag_type *type) { 174void swaynag_type_free(struct swaynag_type *type) {
168 free(type->name); 175 free(type->name);
169 free(type->font); 176 pango_font_description_free(type->font_description);
170 free(type->output); 177 free(type->output);
171 free(type); 178 free(type);
172} 179}