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.c69
-rw-r--r--common/cairo.c4
-rw-r--r--common/gesture.c332
-rw-r--r--common/ipc-client.c1
-rw-r--r--common/log.c1
-rw-r--r--common/loop.c1
-rw-r--r--common/meson.build3
-rw-r--r--common/pango.c50
-rw-r--r--common/stringop.c34
-rw-r--r--common/util.c13
-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.h130
-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.h40
-rw-r--r--include/sway/desktop/transaction.h24
-rw-r--r--include/sway/input/cursor.h19
-rw-r--r--include/sway/input/input-manager.h8
-rw-r--r--include/sway/input/keyboard.h1
-rw-r--r--include/sway/input/libinput.h7
-rw-r--r--include/sway/input/seat.h121
-rw-r--r--include/sway/input/switch.h1
-rw-r--r--include/sway/input/tablet.h3
-rw-r--r--include/sway/input/text_input.h11
-rw-r--r--include/sway/input/text_input_popup.h20
-rw-r--r--include/sway/ipc-json.h2
-rw-r--r--include/sway/ipc-server.h1
-rw-r--r--include/sway/layers.h53
-rw-r--r--include/sway/output.h114
-rw-r--r--include/sway/scene_descriptor.h33
-rw-r--r--include/sway/server.h116
-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.h138
-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.build190
-rw-r--r--meson_options.txt4
-rw-r--r--protocols/meson.build77
-rw-r--r--protocols/wlr-input-inhibitor-unstable-v1.xml67
-rwxr-xr-xrelease.sh31
-rw-r--r--sway/commands.c110
-rw-r--r--sway/commands/assign.c3
-rw-r--r--sway/commands/bar.c10
-rw-r--r--sway/commands/bar/bind.c2
-rw-r--r--sway/commands/bar/colors.c2
-rw-r--r--sway/commands/bar/font.c16
-rw-r--r--sway/commands/bar/hidden_state.c3
-rw-r--r--sway/commands/bar/icon_theme.c1
-rw-r--r--sway/commands/bar/id.c1
-rw-r--r--sway/commands/bar/mode.c3
-rw-r--r--sway/commands/bar/output.c1
-rw-r--r--sway/commands/bar/position.c1
-rw-r--r--sway/commands/bar/separator_symbol.c1
-rw-r--r--sway/commands/bar/tray_bind.c2
-rw-r--r--sway/commands/bar/tray_output.c1
-rw-r--r--sway/commands/bind.c28
-rw-r--r--sway/commands/border.c8
-rw-r--r--sway/commands/client.c28
-rw-r--r--sway/commands/exec_always.c51
-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.c30
-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.c165
-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/calibration_matrix.c1
-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.c19
-rw-r--r--sway/commands/input/map_to_output.c1
-rw-r--r--sway/commands/input/map_to_region.c4
-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_file.c1
-rw-r--r--sway/commands/input/xkb_layout.c1
-rw-r--r--sway/commands/input/xkb_model.c1
-rw-r--r--sway/commands/input/xkb_numlock.c1
-rw-r--r--sway/commands/input/xkb_options.c1
-rw-r--r--sway/commands/input/xkb_rules.c1
-rw-r--r--sway/commands/input/xkb_switch_layout.c37
-rw-r--r--sway/commands/input/xkb_variant.c1
-rw-r--r--sway/commands/layout.c14
-rw-r--r--sway/commands/mark.c3
-rw-r--r--sway/commands/mode.c6
-rw-r--r--sway/commands/move.c207
-rw-r--r--sway/commands/no_focus.c2
-rw-r--r--sway/commands/opacity.c3
-rw-r--r--sway/commands/output.c21
-rw-r--r--sway/commands/output/background.c16
-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/toggle.c2
-rw-r--r--sway/commands/output/transform.c1
-rw-r--r--sway/commands/output/unplug.c54
-rw-r--r--sway/commands/primary_selection.c25
-rw-r--r--sway/commands/reload.c9
-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.c3
-rw-r--r--sway/commands/seat/cursor.c29
-rw-r--r--sway/commands/seat/hide_cursor.c1
-rw-r--r--sway/commands/seat/idle.c7
-rw-r--r--sway/commands/seat/xcursor_theme.c1
-rw-r--r--sway/commands/set.c1
-rw-r--r--sway/commands/show_marks.c12
-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.c183
-rw-r--r--sway/commands/title_align.c9
-rw-r--r--sway/commands/title_format.c2
-rw-r--r--sway/commands/titlebar_border_thickness.c1
-rw-r--r--sway/commands/titlebar_padding.c1
-rw-r--r--sway/commands/unmark.c14
-rw-r--r--sway/commands/workspace.c32
-rw-r--r--sway/commands/xwayland.c2
-rw-r--r--sway/config.c214
-rw-r--r--sway/config/bar.c7
-rw-r--r--sway/config/input.c13
-rw-r--r--sway/config/output.c719
-rw-r--r--sway/config/seat.c2
-rw-r--r--sway/criteria.c103
-rw-r--r--sway/desktop/desktop.c39
-rw-r--r--sway/desktop/idle_inhibit_v1.c62
-rw-r--r--sway/desktop/launcher.c267
-rw-r--r--sway/desktop/layer_shell.c771
-rw-r--r--sway/desktop/output.c1086
-rw-r--r--sway/desktop/render.c1125
-rw-r--r--sway/desktop/surface.c46
-rw-r--r--sway/desktop/transaction.c731
-rw-r--r--sway/desktop/xdg_shell.c401
-rw-r--r--sway/desktop/xwayland.c340
-rw-r--r--sway/input/cursor.c646
-rw-r--r--sway/input/input-manager.c149
-rw-r--r--sway/input/keyboard.c251
-rw-r--r--sway/input/libinput.c124
-rw-r--r--sway/input/seat.c530
-rw-r--r--sway/input/seatop_default.c570
-rw-r--r--sway/input/seatop_down.c181
-rw-r--r--sway/input/seatop_move_floating.c16
-rw-r--r--sway/input/seatop_move_tiling.c233
-rw-r--r--sway/input/seatop_resize_floating.c57
-rw-r--r--sway/input/seatop_resize_tiling.c31
-rw-r--r--sway/input/switch.c38
-rw-r--r--sway/input/tablet.c50
-rw-r--r--sway/input/text_input.c278
-rw-r--r--sway/ipc-json.c380
-rw-r--r--sway/ipc-server.c36
-rw-r--r--sway/lock.c354
-rw-r--r--sway/main.c226
-rw-r--r--sway/meson.build34
-rw-r--r--sway/realtime.c40
-rw-r--r--sway/scene_descriptor.c66
-rw-r--r--sway/server.c338
-rw-r--r--sway/sway-bar.5.scd2
-rw-r--r--sway/sway-input.5.scd47
-rw-r--r--sway/sway-ipc.7.scd55
-rw-r--r--sway/sway-output.5.scd55
-rw-r--r--sway/sway.5.scd159
-rw-r--r--sway/sway_text_node.c302
-rw-r--r--sway/swaynag.c21
-rw-r--r--sway/tree/arrange.c106
-rw-r--r--sway/tree/container.c1687
-rw-r--r--sway/tree/node.c56
-rw-r--r--sway/tree/output.c131
-rw-r--r--sway/tree/root.c288
-rw-r--r--sway/tree/view.c813
-rw-r--r--sway/tree/workspace.c175
-rw-r--r--sway/xdg_activation_v1.c65
-rw-r--r--sway/xdg_decoration.c41
-rw-r--r--swaybar/bar.c56
-rw-r--r--swaybar/config.c5
-rw-r--r--swaybar/i3bar.c39
-rw-r--r--swaybar/image.c (renamed from common/background-image.c)92
-rw-r--r--swaybar/input.c64
-rw-r--r--swaybar/ipc.c39
-rw-r--r--swaybar/main.c3
-rw-r--r--swaybar/meson.build5
-rw-r--r--swaybar/render.c416
-rw-r--r--swaybar/status_line.c14
-rw-r--r--swaybar/tray/host.c11
-rw-r--r--swaybar/tray/icon.c27
-rw-r--r--swaybar/tray/item.c54
-rw-r--r--swaybar/tray/tray.c4
-rw-r--r--swaybar/tray/watcher.c12
-rw-r--r--swaymsg/main.c157
-rw-r--r--swaymsg/swaymsg.1.scd22
-rw-r--r--swaynag/config.c104
-rw-r--r--swaynag/main.c48
-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.c154
-rw-r--r--swaynag/types.c16
273 files changed, 12607 insertions, 9388 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..c47c40eb 100644
--- a/client/pool-buffer.c
+++ b/client/pool-buffer.c
@@ -1,50 +1,42 @@
1#define _POSIX_C_SOURCE 200809
2#include <assert.h> 1#include <assert.h>
3#include <cairo/cairo.h> 2#include <cairo.h>
3#include <errno.h>
4#include <fcntl.h> 4#include <fcntl.h>
5#include <pango/pangocairo.h> 5#include <pango/pangocairo.h>
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/mman.h> 9#include <sys/mman.h>
10#include <time.h>
10#include <unistd.h> 11#include <unistd.h>
11#include <wayland-client.h> 12#include <wayland-client.h>
12#include "config.h" 13#include "config.h"
13#include "pool-buffer.h" 14#include "pool-buffer.h"
14#include "util.h" 15#include "util.h"
15 16
16static int create_pool_file(size_t size, char **name) { 17static int anonymous_shm_open(void) {
17 static const char template[] = "sway-client-XXXXXX"; 18 int retries = 100;
18 const char *path = getenv("XDG_RUNTIME_DIR"); 19
19 if (path == NULL) { 20 do {
20 fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); 21 // try a probably-unique name
21 return -1; 22 struct timespec ts;
22 } 23 clock_gettime(CLOCK_MONOTONIC, &ts);
23 24 pid_t pid = getpid();
24 size_t name_size = strlen(template) + 1 + strlen(path) + 1; 25 char name[50];
25 *name = malloc(name_size); 26 snprintf(name, sizeof(name), "/sway-%x-%x",
26 if (*name == NULL) { 27 (unsigned int)pid, (unsigned int)ts.tv_nsec);
27 fprintf(stderr, "allocation failed\n"); 28
28 return -1; 29 // shm_open guarantees that O_CLOEXEC is set
29 } 30 int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
30 snprintf(*name, name_size, "%s/%s", path, template); 31 if (fd >= 0) {
31 32 shm_unlink(name);
32 int fd = mkstemp(*name); 33 return fd;
33 if (fd < 0) { 34 }
34 return -1;
35 }
36
37 if (!sway_set_cloexec(fd, true)) {
38 close(fd);
39 return -1;
40 }
41 35
42 if (ftruncate(fd, size) < 0) { 36 --retries;
43 close(fd); 37 } while (retries > 0 && errno == EEXIST);
44 return -1;
45 }
46 38
47 return fd; 39 return -1;
48} 40}
49 41
50static void buffer_release(void *data, struct wl_buffer *wl_buffer) { 42static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
@@ -62,17 +54,20 @@ static struct pool_buffer *create_buffer(struct wl_shm *shm,
62 uint32_t stride = width * 4; 54 uint32_t stride = width * 4;
63 size_t size = stride * height; 55 size_t size = stride * height;
64 56
65 char *name; 57 int fd = anonymous_shm_open();
66 int fd = create_pool_file(size, &name); 58 if (fd == -1) {
67 assert(fd != -1); 59 return NULL;
60 }
61 if (ftruncate(fd, size) < 0) {
62 close(fd);
63 return NULL;
64 }
68 void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 65 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); 66 struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
70 buf->buffer = wl_shm_pool_create_buffer(pool, 0, 67 buf->buffer = wl_shm_pool_create_buffer(pool, 0,
71 width, height, stride, format); 68 width, height, stride, format);
72 wl_shm_pool_destroy(pool); 69 wl_shm_pool_destroy(pool);
73 close(fd); 70 close(fd);
74 unlink(name);
75 free(name);
76 71
77 buf->size = size; 72 buf->size = size;
78 buf->width = width; 73 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..272aa837
--- /dev/null
+++ b/common/gesture.c
@@ -0,0 +1,332 @@
1#include "gesture.h"
2
3#include <math.h>
4#include <stdarg.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include "list.h"
9#include "log.h"
10#include "stringop.h"
11
12const uint8_t GESTURE_FINGERS_ANY = 0;
13
14char *gesture_parse(const char *input, struct gesture *output) {
15 // Clear output in case of failure
16 output->type = GESTURE_TYPE_NONE;
17 output->fingers = GESTURE_FINGERS_ANY;
18 output->directions = GESTURE_DIRECTION_NONE;
19
20 // Split input type, fingers and directions
21 list_t *split = split_string(input, ":");
22 if (split->length < 1 || split->length > 3) {
23 return format_str(
24 "expected <gesture>[:<fingers>][:direction], got %s",
25 input);
26 }
27
28 // Parse gesture type
29 if (strcmp(split->items[0], "hold") == 0) {
30 output->type = GESTURE_TYPE_HOLD;
31 } else if (strcmp(split->items[0], "pinch") == 0) {
32 output->type = GESTURE_TYPE_PINCH;
33 } else if (strcmp(split->items[0], "swipe") == 0) {
34 output->type = GESTURE_TYPE_SWIPE;
35 } else {
36 return format_str("expected hold|pinch|swipe, got %s",
37 (const char *)split->items[0]);
38 }
39
40 // Parse optional arguments
41 if (split->length > 1) {
42 char *next = split->items[1];
43
44 // Try to parse as finger count (1-9)
45 if (strlen(next) == 1 && '1' <= next[0] && next[0] <= '9') {
46 output->fingers = atoi(next);
47
48 // Move to next if available
49 next = split->length == 3 ? split->items[2] : NULL;
50 } else if (split->length == 3) {
51 // Fail here if argument can only be finger count
52 return format_str("expected 1-9, got %s", next);
53 }
54
55 // If there is an argument left, try to parse as direction
56 if (next) {
57 list_t *directions = split_string(next, "+");
58
59 for (int i = 0; i < directions->length; ++i) {
60 const char *item = directions->items[i];
61 if (strcmp(item, "any") == 0) {
62 continue;
63 } else if (strcmp(item, "up") == 0) {
64 output->directions |= GESTURE_DIRECTION_UP;
65 } else if (strcmp(item, "down") == 0) {
66 output->directions |= GESTURE_DIRECTION_DOWN;
67 } else if (strcmp(item, "left") == 0) {
68 output->directions |= GESTURE_DIRECTION_LEFT;
69 } else if (strcmp(item, "right") == 0) {
70 output->directions |= GESTURE_DIRECTION_RIGHT;
71 } else if (strcmp(item, "inward") == 0) {
72 output->directions |= GESTURE_DIRECTION_INWARD;
73 } else if (strcmp(item, "outward") == 0) {
74 output->directions |= GESTURE_DIRECTION_OUTWARD;
75 } else if (strcmp(item, "clockwise") == 0) {
76 output->directions |= GESTURE_DIRECTION_CLOCKWISE;
77 } else if (strcmp(item, "counterclockwise") == 0) {
78 output->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
79 } else {
80 return format_str("expected direction, got %s", item);
81 }
82 }
83 list_free_items_and_destroy(directions);
84 }
85 } // if optional args
86
87 list_free_items_and_destroy(split);
88
89 return NULL;
90}
91
92const char *gesture_type_string(enum gesture_type type) {
93 switch (type) {
94 case GESTURE_TYPE_NONE:
95 return "none";
96 case GESTURE_TYPE_HOLD:
97 return "hold";
98 case GESTURE_TYPE_PINCH:
99 return "pinch";
100 case GESTURE_TYPE_SWIPE:
101 return "swipe";
102 }
103
104 return NULL;
105}
106
107const char *gesture_direction_string(enum gesture_direction direction) {
108 switch (direction) {
109 case GESTURE_DIRECTION_NONE:
110 return "none";
111 case GESTURE_DIRECTION_UP:
112 return "up";
113 case GESTURE_DIRECTION_DOWN:
114 return "down";
115 case GESTURE_DIRECTION_LEFT:
116 return "left";
117 case GESTURE_DIRECTION_RIGHT:
118 return "right";
119 case GESTURE_DIRECTION_INWARD:
120 return "inward";
121 case GESTURE_DIRECTION_OUTWARD:
122 return "outward";
123 case GESTURE_DIRECTION_CLOCKWISE:
124 return "clockwise";
125 case GESTURE_DIRECTION_COUNTERCLOCKWISE:
126 return "counterclockwise";
127 }
128
129 return NULL;
130}
131
132// Helper to turn combination of directions flags into string representation.
133static char *gesture_directions_to_string(uint32_t directions) {
134 char *result = NULL;
135
136 for (uint8_t bit = 0; bit < 32; bit++) {
137 uint32_t masked = directions & (1 << bit);
138 if (masked) {
139 const char *name = gesture_direction_string(masked);
140
141 if (!name) {
142 name = "unknown";
143 }
144
145 if (!result) {
146 result = strdup(name);
147 } else {
148 char *new = format_str("%s+%s", result, name);
149 free(result);
150 result = new;
151 }
152 }
153 }
154
155 if(!result) {
156 return strdup("any");
157 }
158
159 return result;
160}
161
162char *gesture_to_string(struct gesture *gesture) {
163 char *directions = gesture_directions_to_string(gesture->directions);
164 char *result = format_str("%s:%u:%s",
165 gesture_type_string(gesture->type),
166 gesture->fingers, directions);
167 free(directions);
168 return result;
169}
170
171bool gesture_check(struct gesture *target, enum gesture_type type, uint8_t fingers) {
172 // Check that gesture type matches
173 if (target->type != type) {
174 return false;
175 }
176
177 // Check that finger count matches
178 if (target->fingers != GESTURE_FINGERS_ANY && target->fingers != fingers) {
179 return false;
180 }
181
182 return true;
183}
184
185bool gesture_match(struct gesture *target, struct gesture *to_match, bool exact) {
186 // Check type and fingers
187 if (!gesture_check(target, to_match->type, to_match->fingers)) {
188 return false;
189 }
190
191 // Enforce exact matches ...
192 if (exact && target->directions != to_match->directions) {
193 return false;
194 }
195
196 // ... or ensure all target directions are matched
197 return (target->directions & to_match->directions) == target->directions;
198}
199
200bool gesture_equal(struct gesture *a, struct gesture *b) {
201 return a->type == b->type
202 && a->fingers == b->fingers
203 && a->directions == b->directions;
204}
205
206// Return count of set bits in directions bit field.
207static uint8_t gesture_directions_count(uint32_t directions) {
208 uint8_t count = 0;
209 for (; directions; directions >>= 1) {
210 count += directions & 1;
211 }
212 return count;
213}
214
215// Compare direction bit count of two direction.
216static int8_t gesture_directions_compare(uint32_t a, uint32_t b) {
217 return gesture_directions_count(a) - gesture_directions_count(b);
218}
219
220// Compare two direction based on their direction bit count
221int8_t gesture_compare(struct gesture *a, struct gesture *b) {
222 return gesture_directions_compare(a->directions, b->directions);
223}
224
225void gesture_tracker_begin(struct gesture_tracker *tracker,
226 enum gesture_type type, uint8_t fingers) {
227 tracker->type = type;
228 tracker->fingers = fingers;
229
230 tracker->dx = 0.0;
231 tracker->dy = 0.0;
232 tracker->scale = 1.0;
233 tracker->rotation = 0.0;
234
235 sway_log(SWAY_DEBUG, "begin tracking %s:%u:? gesture",
236 gesture_type_string(type), fingers);
237}
238
239bool gesture_tracker_check(struct gesture_tracker *tracker, enum gesture_type type) {
240 return tracker->type == type;
241}
242
243void gesture_tracker_update(struct gesture_tracker *tracker,
244 double dx, double dy, double scale, double rotation) {
245 if (tracker->type == GESTURE_TYPE_HOLD) {
246 sway_assert(false, "hold does not update.");
247 return;
248 }
249
250 tracker->dx += dx;
251 tracker->dy += dy;
252
253 if (tracker->type == GESTURE_TYPE_PINCH) {
254 tracker->scale = scale;
255 tracker->rotation += rotation;
256 }
257
258 sway_log(SWAY_DEBUG, "update tracking %s:%u:? gesture: %f %f %f %f",
259 gesture_type_string(tracker->type),
260 tracker->fingers,
261 tracker->dx, tracker->dy,
262 tracker->scale, tracker->rotation);
263}
264
265void gesture_tracker_cancel(struct gesture_tracker *tracker) {
266 sway_log(SWAY_DEBUG, "cancel tracking %s:%u:? gesture",
267 gesture_type_string(tracker->type), tracker->fingers);
268
269 tracker->type = GESTURE_TYPE_NONE;
270}
271
272struct gesture *gesture_tracker_end(struct gesture_tracker *tracker) {
273 struct gesture *result = calloc(1, sizeof(struct gesture));
274
275 // Ignore gesture under some threshold
276 // TODO: Make configurable
277 const double min_rotation = 5;
278 const double min_scale_delta = 0.1;
279
280 // Determine direction
281 switch(tracker->type) {
282 // Gestures with scale and rotation
283 case GESTURE_TYPE_PINCH:
284 if (tracker->rotation > min_rotation) {
285 result->directions |= GESTURE_DIRECTION_CLOCKWISE;
286 }
287 if (tracker->rotation < -min_rotation) {
288 result->directions |= GESTURE_DIRECTION_COUNTERCLOCKWISE;
289 }
290
291 if (tracker->scale > (1.0 + min_scale_delta)) {
292 result->directions |= GESTURE_DIRECTION_OUTWARD;
293 }
294 if (tracker->scale < (1.0 - min_scale_delta)) {
295 result->directions |= GESTURE_DIRECTION_INWARD;
296 }
297 __attribute__ ((fallthrough));
298 // Gestures with dx and dy
299 case GESTURE_TYPE_SWIPE:
300 if (fabs(tracker->dx) > fabs(tracker->dy)) {
301 if (tracker->dx > 0) {
302 result->directions |= GESTURE_DIRECTION_RIGHT;
303 } else {
304 result->directions |= GESTURE_DIRECTION_LEFT;
305 }
306 } else {
307 if (tracker->dy > 0) {
308 result->directions |= GESTURE_DIRECTION_DOWN;
309 } else {
310 result->directions |= GESTURE_DIRECTION_UP;
311 }
312 }
313 // Gesture without any direction
314 case GESTURE_TYPE_HOLD:
315 break;
316 // Not tracking any gesture
317 case GESTURE_TYPE_NONE:
318 sway_assert(false, "Not tracking any gesture.");
319 return result;
320 }
321
322 result->type = tracker->type;
323 result->fingers = tracker->fingers;
324
325 char *description = gesture_to_string(result);
326 sway_log(SWAY_DEBUG, "end tracking gesture: %s", description);
327 free(description);
328
329 tracker->type = GESTURE_TYPE_NONE;
330
331 return result;
332}
diff --git a/common/ipc-client.c b/common/ipc-client.c
index d30212d2..a0be2b2d 100644
--- a/common/ipc-client.c
+++ b/common/ipc-client.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 1#include <stdio.h>
3#include <stdint.h> 2#include <stdint.h>
4#include <stdlib.h> 3#include <stdlib.h>
diff --git a/common/log.c b/common/log.c
index 483420e7..3eacdb34 100644
--- a/common/log.c
+++ b/common/log.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200112L
2#include <signal.h> 1#include <signal.h>
3#include <stdarg.h> 2#include <stdarg.h>
4#include <stdio.h> 3#include <stdio.h>
diff --git a/common/loop.c b/common/loop.c
index 80fe18ea..b99c6d55 100644
--- a/common/loop.c
+++ b/common/loop.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200112L
2#include <limits.h> 1#include <limits.h>
3#include <string.h> 2#include <string.h>
4#include <stdbool.h> 3#include <stdbool.h>
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..16d04917 100644
--- a/common/stringop.c
+++ b/common/stringop.c
@@ -1,5 +1,5 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
2#include <stdarg.h>
3#include <stdbool.h> 3#include <stdbool.h>
4#include <stdio.h> 4#include <stdio.h>
5#include <stdlib.h> 5#include <stdlib.h>
@@ -328,3 +328,35 @@ bool expand_path(char **path) {
328 wordfree(&p); 328 wordfree(&p);
329 return true; 329 return true;
330} 330}
331
332char *vformat_str(const char *fmt, va_list args) {
333 char *str = NULL;
334 va_list args_copy;
335 va_copy(args_copy, args);
336
337 int len = vsnprintf(NULL, 0, fmt, args);
338 if (len < 0) {
339 sway_log_errno(SWAY_ERROR, "vsnprintf(\"%s\") failed", fmt);
340 goto out;
341 }
342
343 str = malloc(len + 1);
344 if (str == NULL) {
345 sway_log_errno(SWAY_ERROR, "malloc() failed");
346 goto out;
347 }
348
349 vsnprintf(str, len + 1, fmt, args_copy);
350
351out:
352 va_end(args_copy);
353 return str;
354}
355
356char *format_str(const char *fmt, ...) {
357 va_list args;
358 va_start(args, fmt);
359 char *str = vformat_str(fmt, args);
360 va_end(args);
361 return str;
362}
diff --git a/common/util.c b/common/util.c
index 5ea94f48..7c492bcb 100644
--- a/common/util.c
+++ b/common/util.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <fcntl.h> 2#include <fcntl.h>
4#include <math.h> 3#include <math.h>
@@ -10,12 +9,6 @@
10#include "log.h" 9#include "log.h"
11#include "util.h" 10#include "util.h"
12 11
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) { 12int wrap(int i, int max) {
20 return ((i % max) + max) % max; 13 return ((i % max) + max) % max;
21} 14}
@@ -86,6 +79,12 @@ enum movement_unit parse_movement_unit(const char *unit) {
86 79
87int parse_movement_amount(int argc, char **argv, 80int parse_movement_amount(int argc, char **argv,
88 struct movement_amount *amount) { 81 struct movement_amount *amount) {
82 if (!sway_assert(argc > 0, "Expected args in parse_movement_amount")) {
83 amount->amount = 0;
84 amount->unit = MOVEMENT_UNIT_INVALID;
85 return 0;
86 }
87
89 char *err; 88 char *err;
90 amount->amount = (int)strtol(argv[0], &err, 10); 89 amount->amount = (int)strtol(argv[0], &err, 10);
91 if (*err) { 90 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..0be1cd22 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,19 @@ 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; 292};
293
294/**
295 * An output config pre-matched to an output
296 */
297struct matched_output_config {
298 struct sway_output *output;
299 struct output_config *config;
272}; 300};
273 301
274/** 302/**
@@ -281,6 +309,12 @@ struct side_gaps {
281 int left; 309 int left;
282}; 310};
283 311
312enum smart_gaps_mode {
313 SMART_GAPS_OFF,
314 SMART_GAPS_ON,
315 SMART_GAPS_INVERSE_OUTER,
316};
317
284/** 318/**
285 * Stores configuration for a workspace, regardless of whether the workspace 319 * Stores configuration for a workspace, regardless of whether the workspace
286 * exists. 320 * exists.
@@ -292,6 +326,12 @@ struct workspace_config {
292 struct side_gaps gaps_outer; 326 struct side_gaps gaps_outer;
293}; 327};
294 328
329enum pango_markup_config {
330 PANGO_MARKUP_DISABLED = false,
331 PANGO_MARKUP_ENABLED = true,
332 PANGO_MARKUP_DEFAULT // The default is font dependent ("pango:" prefix)
333};
334
295struct bar_config { 335struct bar_config {
296 char *swaybar_command; 336 char *swaybar_command;
297 struct wl_client *client; 337 struct wl_client *client;
@@ -323,7 +363,7 @@ struct bar_config {
323 char *position; 363 char *position;
324 list_t *bindings; 364 list_t *bindings;
325 char *status_command; 365 char *status_command;
326 bool pango_markup; 366 enum pango_markup_config pango_markup;
327 char *font; 367 char *font;
328 int height; // -1 not defined 368 int height; // -1 not defined
329 bool workspace_buttons; 369 bool workspace_buttons;
@@ -410,14 +450,6 @@ enum sway_popup_during_fullscreen {
410 POPUP_LEAVE, 450 POPUP_LEAVE,
411}; 451};
412 452
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 { 453enum focus_follows_mouse_mode {
422 FOLLOWS_NO, 454 FOLLOWS_NO,
423 FOLLOWS_YES, 455 FOLLOWS_YES,
@@ -479,9 +511,10 @@ struct sway_config {
479 char *floating_scroll_right_cmd; 511 char *floating_scroll_right_cmd;
480 enum sway_container_layout default_orientation; 512 enum sway_container_layout default_orientation;
481 enum sway_container_layout default_layout; 513 enum sway_container_layout default_layout;
482 char *font; 514 char *font; // Used for IPC.
483 size_t font_height; 515 PangoFontDescription *font_description; // Used internally for rendering and validating.
484 size_t font_baseline; 516 int font_height;
517 int font_baseline;
485 bool pango_markup; 518 bool pango_markup;
486 int titlebar_border_thickness; 519 int titlebar_border_thickness;
487 int titlebar_h_padding; 520 int titlebar_h_padding;
@@ -508,11 +541,12 @@ struct sway_config {
508 bool auto_back_and_forth; 541 bool auto_back_and_forth;
509 bool show_marks; 542 bool show_marks;
510 enum alignment title_align; 543 enum alignment title_align;
544 bool primary_selection;
511 545
512 bool tiling_drag; 546 bool tiling_drag;
513 int tiling_drag_threshold; 547 int tiling_drag_threshold;
514 548
515 bool smart_gaps; 549 enum smart_gaps_mode smart_gaps;
516 int gaps_inner; 550 int gaps_inner;
517 struct side_gaps gaps_outer; 551 struct side_gaps gaps_outer;
518 552
@@ -535,12 +569,15 @@ struct sway_config {
535 struct { 569 struct {
536 struct border_colors focused; 570 struct border_colors focused;
537 struct border_colors focused_inactive; 571 struct border_colors focused_inactive;
572 struct border_colors focused_tab_title;
538 struct border_colors unfocused; 573 struct border_colors unfocused;
539 struct border_colors urgent; 574 struct border_colors urgent;
540 struct border_colors placeholder; 575 struct border_colors placeholder;
541 float background[4]; 576 float background[4];
542 } border_colors; 577 } border_colors;
543 578
579 bool has_focused_tab_title;
580
544 // floating view 581 // floating view
545 int32_t floating_maximum_width; 582 int32_t floating_maximum_width;
546 int32_t floating_maximum_height; 583 int32_t floating_maximum_height;
@@ -559,7 +596,7 @@ struct sway_config {
559 struct sway_node *node; 596 struct sway_node *node;
560 struct sway_container *container; 597 struct sway_container *container;
561 struct sway_workspace *workspace; 598 struct sway_workspace *workspace;
562 bool using_criteria; 599 bool node_overridden; // True if the node is selected by means other than focus
563 struct { 600 struct {
564 int argc; 601 int argc;
565 char **argv; 602 char **argv;
@@ -598,7 +635,7 @@ void run_deferred_bindings(void);
598/** 635/**
599 * Adds a warning entry to the swaynag instance used for errors. 636 * Adds a warning entry to the swaynag instance used for errors.
600 */ 637 */
601void config_add_swaynag_warning(char *fmt, ...); 638void config_add_swaynag_warning(char *fmt, ...) _SWAY_ATTRIB_PRINTF(1, 2);
602 639
603/** 640/**
604 * Free config struct 641 * Free config struct
@@ -651,20 +688,22 @@ const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filt
651 688
652struct output_config *new_output_config(const char *name); 689struct output_config *new_output_config(const char *name);
653 690
654void merge_output_config(struct output_config *dst, struct output_config *src); 691bool apply_output_configs(struct matched_output_config *configs,
692 size_t configs_len, bool test_only);
655 693
656bool apply_output_config(struct output_config *oc, struct sway_output *output); 694void apply_all_output_configs(void);
657 695
658bool test_output_config(struct output_config *oc, struct sway_output *output); 696/**
659 697 * store_output_config stores a new output config. An output may be matched by
660struct output_config *store_output_config(struct output_config *oc); 698 * three different config types, in order of precedence: Identifier, name and
699 * wildcard. When storing a config type of lower precedence, assume that the
700 * user wants the config to take immediate effect by superseding (clearing) the
701 * same values from higher presedence configuration.
702 */
703void store_output_config(struct output_config *oc);
661 704
662struct output_config *find_output_config(struct sway_output *output); 705struct output_config *find_output_config(struct sway_output *output);
663 706
664void apply_output_config_to_outputs(struct output_config *oc);
665
666void reset_outputs(void);
667
668void free_output_config(struct output_config *oc); 707void free_output_config(struct output_config *oc);
669 708
670bool spawn_swaybg(void); 709bool spawn_swaybg(void);
@@ -675,6 +714,8 @@ void free_sway_binding(struct sway_binding *sb);
675 714
676void free_switch_binding(struct sway_switch_binding *binding); 715void free_switch_binding(struct sway_switch_binding *binding);
677 716
717void free_gesture_binding(struct sway_gesture_binding *binding);
718
678void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding); 719void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
679 720
680void load_swaybar(struct bar_config *bar); 721void load_swaybar(struct bar_config *bar);
@@ -690,14 +731,13 @@ void free_bar_binding(struct bar_binding *binding);
690void free_workspace_config(struct workspace_config *wsc); 731void free_workspace_config(struct workspace_config *wsc);
691 732
692/** 733/**
693 * Updates the value of config->font_height based on the max title height 734 * 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 735 * font as reported by pango.
695 * recalculate their heights before reporting.
696 * 736 *
697 * If the height has changed, all containers will be rearranged to take on the 737 * If the height has changed, all containers will be rearranged to take on the
698 * new size. 738 * new size.
699 */ 739 */
700void config_update_font_height(bool recalculate); 740void config_update_font_height(void);
701 741
702/** 742/**
703 * Convert bindsym into bindcode using the first configured layout. 743 * 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..412068a9
--- /dev/null
+++ b/include/sway/desktop/launcher.h
@@ -0,0 +1,40 @@
1#ifndef _SWAY_LAUNCHER_H
2#define _SWAY_LAUNCHER_H
3
4#include <stdlib.h>
5#include <wayland-server-core.h>
6#include "sway/input/seat.h"
7
8struct launcher_ctx {
9 pid_t pid;
10 char *fallback_name;
11 struct wlr_xdg_activation_token_v1 *token;
12 struct wl_listener token_destroy;
13 struct sway_seat *seat;
14 struct wl_listener seat_destroy;
15
16 bool activated;
17 bool had_focused_surface;
18
19 struct sway_node *node;
20 struct wl_listener node_destroy;
21
22 struct wl_list link; // sway_server::pending_launcher_ctxs
23};
24
25struct launcher_ctx *launcher_ctx_find_pid(pid_t pid);
26
27struct sway_workspace *launcher_ctx_get_workspace(struct launcher_ctx *ctx);
28
29void launcher_ctx_consume(struct launcher_ctx *ctx);
30
31void launcher_ctx_destroy(struct launcher_ctx *ctx);
32
33struct launcher_ctx *launcher_ctx_create_internal(void);
34
35struct launcher_ctx *launcher_ctx_create(
36 struct wlr_xdg_activation_token_v1 *token, struct sway_node *node);
37
38const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx);
39
40#endif
diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h
index 175489c5..dd7edb7a 100644
--- a/include/sway/desktop/transaction.h
+++ b/include/sway/desktop/transaction.h
@@ -1,6 +1,8 @@
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>
5#include <wlr/types/wlr_scene.h>
4 6
5/** 7/**
6 * Transactions enable us to perform atomic layout updates. 8 * Transactions enable us to perform atomic layout updates.
@@ -28,12 +30,21 @@ struct sway_view;
28 */ 30 */
29void transaction_commit_dirty(void); 31void transaction_commit_dirty(void);
30 32
33/*
34 * Same as transaction_commit_dirty, but signalling that this is a
35 * client-initiated change has already taken effect.
36 */
37void transaction_commit_dirty_client(void);
38
31/** 39/**
32 * Notify the transaction system that a view is ready for the new layout. 40 * Notify the transaction system that a view is ready for the new layout.
33 * 41 *
34 * When all views in the transaction are ready, the layout will be applied. 42 * When all views in the transaction are ready, the layout will be applied.
43 *
44 * A success boolean is returned denoting that this part of the transaction is
45 * ready.
35 */ 46 */
36void transaction_notify_view_ready_by_serial(struct sway_view *view, 47bool transaction_notify_view_ready_by_serial(struct sway_view *view,
37 uint32_t serial); 48 uint32_t serial);
38 49
39/** 50/**
@@ -41,14 +52,13 @@ void transaction_notify_view_ready_by_serial(struct sway_view *view,
41 * identifying the instruction by geometry rather than by serial. 52 * identifying the instruction by geometry rather than by serial.
42 * 53 *
43 * This is used by xwayland views, as they don't have serials. 54 * This is used by xwayland views, as they don't have serials.
55 *
56 * A success boolean is returned denoting that this part of the transaction is
57 * ready.
44 */ 58 */
45void transaction_notify_view_ready_by_geometry(struct sway_view *view, 59bool transaction_notify_view_ready_by_geometry(struct sway_view *view,
46 double x, double y, int width, int height); 60 double x, double y, int width, int height);
47 61
48/** 62void arrange_popups(struct wlr_scene_tree *popups);
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 63
54#endif 64#endif
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 6a38190b..527d0350 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 wl_pointer_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..45c75199 100644
--- a/include/sway/input/input-manager.h
+++ b/include/sway/input/input-manager.h
@@ -1,10 +1,10 @@
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>
7#include <wlr/types/wlr_transient_seat_v1.h>
8#include "sway/server.h" 8#include "sway/server.h"
9#include "sway/config.h" 9#include "sway/config.h"
10#include "list.h" 10#include "list.h"
@@ -21,10 +21,11 @@ struct sway_input_manager {
21 struct wl_list devices; 21 struct wl_list devices;
22 struct wl_list seats; 22 struct wl_list seats;
23 23
24 struct wlr_input_inhibit_manager *inhibit;
25 struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit; 24 struct wlr_keyboard_shortcuts_inhibit_manager_v1 *keyboard_shortcuts_inhibit;
26 struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard; 25 struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard;
27 struct wlr_virtual_pointer_manager_v1 *virtual_pointer; 26 struct wlr_virtual_pointer_manager_v1 *virtual_pointer;
27 struct wlr_pointer_gestures_v1 *pointer_gestures;
28 struct wlr_transient_seat_manager_v1 *transient_seat_manager;
28 29
29 struct wl_listener new_input; 30 struct wl_listener new_input;
30 struct wl_listener inhibit_activate; 31 struct wl_listener inhibit_activate;
@@ -32,6 +33,7 @@ struct sway_input_manager {
32 struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor; 33 struct wl_listener keyboard_shortcuts_inhibit_new_inhibitor;
33 struct wl_listener virtual_keyboard_new; 34 struct wl_listener virtual_keyboard_new;
34 struct wl_listener virtual_pointer_new; 35 struct wl_listener virtual_pointer_new;
36 struct wl_listener transient_seat_create;
35}; 37};
36 38
37struct sway_input_manager *input_manager_create(struct sway_server *server); 39struct sway_input_manager *input_manager_create(struct sway_server *server);
@@ -44,7 +46,7 @@ void input_manager_configure_xcursor(void);
44 46
45void input_manager_apply_input_config(struct input_config *input_config); 47void input_manager_apply_input_config(struct input_config *input_config);
46 48
47void input_manager_configure_all_inputs(void); 49void input_manager_configure_all_input_mappings(void);
48 50
49void input_manager_reset_input(struct sway_input_device *input_device); 51void input_manager_reset_input(struct sway_input_device *input_device);
50 52
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..428f9679 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"
@@ -15,19 +17,41 @@ struct sway_seat;
15struct sway_seatop_impl { 17struct sway_seatop_impl {
16 void (*button)(struct sway_seat *seat, uint32_t time_msec, 18 void (*button)(struct sway_seat *seat, uint32_t time_msec,
17 struct wlr_input_device *device, uint32_t button, 19 struct wlr_input_device *device, uint32_t button,
18 enum wlr_button_state state); 20 enum wl_pointer_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;
@@ -106,6 +124,7 @@ struct sway_seat {
106 struct wl_listener start_drag; 124 struct wl_listener start_drag;
107 struct wl_listener request_set_selection; 125 struct wl_listener request_set_selection;
108 struct wl_listener request_set_primary_selection; 126 struct wl_listener request_set_primary_selection;
127 struct wl_listener destroy;
109 128
110 struct wl_list devices; // sway_seat_device::link 129 struct wl_list devices; // sway_seat_device::link
111 struct wl_list keyboard_groups; // sway_keyboard_group::link 130 struct wl_list keyboard_groups; // sway_keyboard_group::link
@@ -141,6 +160,9 @@ void seat_add_device(struct sway_seat *seat,
141void seat_configure_device(struct sway_seat *seat, 160void seat_configure_device(struct sway_seat *seat,
142 struct sway_input_device *device); 161 struct sway_input_device *device);
143 162
163void seat_configure_device_mapping(struct sway_seat *seat,
164 struct sway_input_device *input_device);
165
144void seat_reset_device(struct sway_seat *seat, 166void seat_reset_device(struct sway_seat *seat,
145 struct sway_input_device *input_device); 167 struct sway_input_device *input_device);
146 168
@@ -171,8 +193,7 @@ void seat_set_focus_surface(struct sway_seat *seat,
171void seat_set_focus_layer(struct sway_seat *seat, 193void seat_set_focus_layer(struct sway_seat *seat,
172 struct wlr_layer_surface_v1 *layer); 194 struct wlr_layer_surface_v1 *layer);
173 195
174void seat_set_exclusive_client(struct sway_seat *seat, 196void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client);
175 struct wl_client *client);
176 197
177struct sway_node *seat_get_focus(struct sway_seat *seat); 198struct sway_node *seat_get_focus(struct sway_seat *seat);
178 199
@@ -231,7 +252,7 @@ void seat_idle_notify_activity(struct sway_seat *seat,
231 252
232bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); 253bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
233 254
234void drag_icon_update_position(struct sway_drag_icon *icon); 255void drag_icons_update_position(struct sway_seat *seat);
235 256
236enum wlr_edges find_resize_edge(struct sway_container *cont, 257enum wlr_edges find_resize_edge(struct sway_container *cont,
237 struct wlr_surface *surface, struct sway_cursor *cursor); 258 struct wlr_surface *surface, struct sway_cursor *cursor);
@@ -239,7 +260,13 @@ enum wlr_edges find_resize_edge(struct sway_container *cont,
239void seatop_begin_default(struct sway_seat *seat); 260void seatop_begin_default(struct sway_seat *seat);
240 261
241void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, 262void seatop_begin_down(struct sway_seat *seat, struct sway_container *con,
242 uint32_t time_msec, int sx, int sy); 263 double sx, double sy);
264
265void seatop_begin_down_on_surface(struct sway_seat *seat,
266 struct wlr_surface *surface, double sx, double sy);
267
268void seatop_begin_touch_down(struct sway_seat *seat, struct wlr_surface *surface,
269 struct wlr_touch_down_event *event, double sx, double sy, double lx, double ly);
243 270
244void seatop_begin_move_floating(struct sway_seat *seat, 271void seatop_begin_move_floating(struct sway_seat *seat,
245 struct sway_container *con); 272 struct sway_container *con);
@@ -260,18 +287,18 @@ struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat,
260 struct sway_workspace *workspace); 287 struct sway_workspace *workspace);
261 288
262void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, 289void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
263 uint32_t button, enum wlr_button_state state); 290 uint32_t button, enum wl_pointer_button_state state);
264 291
265void seat_consider_warp_to_focus(struct sway_seat *seat); 292void seat_consider_warp_to_focus(struct sway_seat *seat);
266 293
267void seatop_button(struct sway_seat *seat, uint32_t time_msec, 294void seatop_button(struct sway_seat *seat, uint32_t time_msec,
268 struct wlr_input_device *device, uint32_t button, 295 struct wlr_input_device *device, uint32_t button,
269 enum wlr_button_state state); 296 enum wl_pointer_button_state state);
270 297
271void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec); 298void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec);
272 299
273void seatop_pointer_axis(struct sway_seat *seat, 300void seatop_pointer_axis(struct sway_seat *seat,
274 struct wlr_event_pointer_axis *event); 301 struct wlr_pointer_axis_event *event);
275 302
276void seatop_tablet_tool_tip(struct sway_seat *seat, 303void seatop_tablet_tool_tip(struct sway_seat *seat,
277 struct sway_tablet_tool *tool, uint32_t time_msec, 304 struct sway_tablet_tool *tool, uint32_t time_msec,
@@ -280,6 +307,37 @@ void seatop_tablet_tool_tip(struct sway_seat *seat,
280void seatop_tablet_tool_motion(struct sway_seat *seat, 307void seatop_tablet_tool_motion(struct sway_seat *seat,
281 struct sway_tablet_tool *tool, uint32_t time_msec); 308 struct sway_tablet_tool *tool, uint32_t time_msec);
282 309
310void seatop_hold_begin(struct sway_seat *seat,
311 struct wlr_pointer_hold_begin_event *event);
312void seatop_hold_end(struct sway_seat *seat,
313 struct wlr_pointer_hold_end_event *event);
314
315void seatop_pinch_begin(struct sway_seat *seat,
316 struct wlr_pointer_pinch_begin_event *event);
317void seatop_pinch_update(struct sway_seat *seat,
318 struct wlr_pointer_pinch_update_event *event);
319void seatop_pinch_end(struct sway_seat *seat,
320 struct wlr_pointer_pinch_end_event *event);
321
322void seatop_swipe_begin(struct sway_seat *seat,
323 struct wlr_pointer_swipe_begin_event *event);
324void seatop_swipe_update(struct sway_seat *seat,
325 struct wlr_pointer_swipe_update_event *event);
326void seatop_swipe_end(struct sway_seat *seat,
327 struct wlr_pointer_swipe_end_event *event);
328
329void seatop_touch_motion(struct sway_seat *seat,
330 struct wlr_touch_motion_event *event, double lx, double ly);
331
332void seatop_touch_up(struct sway_seat *seat,
333 struct wlr_touch_up_event *event);
334
335void seatop_touch_down(struct sway_seat *seat,
336 struct wlr_touch_down_event *event, double lx, double ly);
337
338void seatop_touch_cancel(struct sway_seat *seat,
339 struct wlr_touch_cancel_event *event);
340
283void seatop_rebase(struct sway_seat *seat, uint32_t time_msec); 341void seatop_rebase(struct sway_seat *seat, uint32_t time_msec);
284 342
285/** 343/**
@@ -294,13 +352,6 @@ void seatop_end(struct sway_seat *seat);
294 */ 352 */
295void seatop_unref(struct sway_seat *seat, struct sway_container *con); 353void seatop_unref(struct sway_seat *seat, struct sway_container *con);
296 354
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); 355bool seatop_allows_set_cursor(struct sway_seat *seat);
305 356
306/** 357/**
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..1993f928 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.
@@ -22,15 +21,21 @@ struct sway_input_method_relay {
22 struct sway_seat *seat; 21 struct sway_seat *seat;
23 22
24 struct wl_list text_inputs; // sway_text_input::link 23 struct wl_list text_inputs; // sway_text_input::link
24 struct wl_list input_popups; // sway_input_popup::link
25 struct wlr_input_method_v2 *input_method; // doesn't have to be present 25 struct wlr_input_method_v2 *input_method; // doesn't have to be present
26 26
27 struct wl_listener text_input_new; 27 struct wl_listener text_input_new;
28 28
29 struct wl_listener input_method_new; 29 struct wl_listener input_method_new;
30 struct wl_listener input_method_commit; 30 struct wl_listener input_method_commit;
31 struct wl_listener input_method_new_popup_surface;
32 struct wl_listener input_method_grab_keyboard;
31 struct wl_listener input_method_destroy; 33 struct wl_listener input_method_destroy;
34
35 struct wl_listener input_method_keyboard_grab_destroy;
32}; 36};
33 37
38
34struct sway_text_input { 39struct sway_text_input {
35 struct sway_input_method_relay *relay; 40 struct sway_input_method_relay *relay;
36 41
diff --git a/include/sway/input/text_input_popup.h b/include/sway/input/text_input_popup.h
new file mode 100644
index 00000000..e5f6ab8b
--- /dev/null
+++ b/include/sway/input/text_input_popup.h
@@ -0,0 +1,20 @@
1#ifndef _SWAY_INPUT_TEXT_INPUT_POPUP_H
2#define _SWAY_INPUT_TEXT_INPUT_POPUP_H
3
4#include "sway/tree/view.h"
5
6struct sway_input_popup {
7 struct sway_input_method_relay *relay;
8
9 struct wlr_scene_tree *scene_tree;
10 struct sway_popup_desc desc;
11 struct wlr_input_popup_surface_v2 *popup_surface;
12
13 struct wl_list link;
14
15 struct wl_listener popup_destroy;
16 struct wl_listener popup_surface_commit;
17
18 struct wl_listener focused_surface_unmap;
19};
20#endif
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..fd6384e0 100644
--- a/include/sway/layers.h
+++ b/include/sway/layers.h
@@ -1,59 +1,44 @@
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#include "sway/tree/view.h"
8enum layer_parent {
9 LAYER_PARENT_LAYER,
10 LAYER_PARENT_POPUP,
11};
12 7
13struct sway_layer_surface { 8struct 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; 9 struct wl_listener map;
19 struct wl_listener unmap; 10 struct wl_listener unmap;
20 struct wl_listener surface_commit; 11 struct wl_listener surface_commit;
21 struct wl_listener output_destroy; 12 struct wl_listener output_destroy;
13 struct wl_listener node_destroy;
22 struct wl_listener new_popup; 14 struct wl_listener new_popup;
23 struct wl_listener new_subsurface;
24 15
25 struct wlr_box geo; 16 bool mapped;
26 enum zwlr_layer_shell_v1_layer layer; 17
18 struct wlr_scene_tree *popups;
19 struct sway_popup_desc desc;
20
21 struct sway_output *output;
22 struct wlr_scene_layer_surface_v1 *scene;
23 struct wlr_scene_tree *tree;
24 struct wlr_layer_surface_v1 *layer_surface;
27}; 25};
28 26
29struct sway_layer_popup { 27struct sway_layer_popup {
30 struct wlr_xdg_popup *wlr_popup; 28 struct wlr_xdg_popup *wlr_popup;
31 enum layer_parent parent_type; 29 struct wlr_scene_tree *scene;
32 union { 30 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 31
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; 32 struct wl_listener destroy;
33 struct wl_listener new_popup;
50 struct wl_listener commit; 34 struct wl_listener commit;
51}; 35};
52 36
53struct sway_output; 37struct sway_output;
54void arrange_layers(struct sway_output *output);
55 38
56struct sway_layer_surface *layer_from_wlr_layer_surface_v1( 39struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface(
57 struct wlr_layer_surface_v1 *layer_surface); 40 struct wlr_surface *surface);
41
42void arrange_layers(struct sway_output *output);
58 43
59#endif 44#endif
diff --git a/include/sway/output.h b/include/sway/output.h
index 96986700..d546d488 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 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..c71851f6 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -2,39 +2,43 @@
2#define _SWAY_SERVER_H 2#define _SWAY_SERVER_H
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>
6#include <wlr/backend/session.h>
7#include <wlr/render/wlr_renderer.h>
8#include <wlr/types/wlr_compositor.h>
9#include <wlr/types/wlr_data_device.h>
10#include <wlr/types/wlr_input_method_v2.h>
11#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
12#include <wlr/types/wlr_layer_shell_v1.h>
13#include <wlr/types/wlr_output_management_v1.h>
14#include <wlr/types/wlr_output_power_management_v1.h>
15#include <wlr/types/wlr_presentation_time.h>
16#include <wlr/types/wlr_relative_pointer_v1.h>
17#include <wlr/types/wlr_server_decoration.h>
18#include <wlr/types/wlr_text_input_v3.h>
19#include <wlr/types/wlr_xdg_shell.h>
20#include "config.h" 5#include "config.h"
21#include "list.h" 6#include "list.h"
7#include "sway/desktop/idle_inhibit_v1.h"
22#if HAVE_XWAYLAND 8#if HAVE_XWAYLAND
23#include "sway/xwayland.h" 9#include "sway/xwayland.h"
24#endif 10#endif
25 11
12struct sway_transaction;
13
14struct sway_session_lock {
15 struct wlr_session_lock_v1 *lock;
16 struct wlr_surface *focused;
17 bool abandoned;
18
19 struct wl_list outputs; // struct sway_session_lock_output
20
21 // invalid if the session is abandoned
22 struct wl_listener new_surface;
23 struct wl_listener unlock;
24 struct wl_listener destroy;
25};
26
26struct sway_server { 27struct sway_server {
27 struct wl_display *wl_display; 28 struct wl_display *wl_display;
28 struct wl_event_loop *wl_event_loop; 29 struct wl_event_loop *wl_event_loop;
29 const char *socket; 30 const char *socket;
30 31
31 struct wlr_backend *backend; 32 struct wlr_backend *backend;
32 struct wlr_backend *noop_backend; 33 struct wlr_session *session;
33 // secondary headless backend used for creating virtual outputs on-the-fly 34 // secondary headless backend used for creating virtual outputs on-the-fly
34 struct wlr_backend *headless_backend; 35 struct wlr_backend *headless_backend;
36 struct wlr_renderer *renderer;
37 struct wlr_allocator *allocator;
35 38
36 struct wlr_compositor *compositor; 39 struct wlr_compositor *compositor;
37 struct wl_listener compositor_new_surface; 40
41 struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1;
38 42
39 struct wlr_data_device_manager *data_device_manager; 43 struct wlr_data_device_manager *data_device_manager;
40 44
@@ -42,15 +46,16 @@ struct sway_server {
42 46
43 struct wl_listener new_output; 47 struct wl_listener new_output;
44 struct wl_listener output_layout_change; 48 struct wl_listener output_layout_change;
49 struct wl_listener renderer_lost;
45 50
46 struct wlr_idle *idle; 51 struct wlr_idle_notifier_v1 *idle_notifier_v1;
47 struct sway_idle_inhibit_manager_v1 *idle_inhibit_manager_v1; 52 struct sway_idle_inhibit_manager_v1 idle_inhibit_manager_v1;
48 53
49 struct wlr_layer_shell_v1 *layer_shell; 54 struct wlr_layer_shell_v1 *layer_shell;
50 struct wl_listener layer_shell_surface; 55 struct wl_listener layer_shell_surface;
51 56
52 struct wlr_xdg_shell *xdg_shell; 57 struct wlr_xdg_shell *xdg_shell;
53 struct wl_listener xdg_shell_surface; 58 struct wl_listener xdg_shell_toplevel;
54 59
55 struct wlr_tablet_manager_v2 *tablet_v2; 60 struct wlr_tablet_manager_v2 *tablet_v2;
56 61
@@ -70,7 +75,8 @@ struct sway_server {
70 struct wl_listener xdg_decoration; 75 struct wl_listener xdg_decoration;
71 struct wl_list xdg_decorations; // sway_xdg_decoration::link 76 struct wl_list xdg_decorations; // sway_xdg_decoration::link
72 77
73 struct wlr_presentation *presentation; 78 struct wlr_drm_lease_v1_manager *drm_lease_manager;
79 struct wl_listener drm_lease_request;
74 80
75 struct wlr_pointer_constraints_v1 *pointer_constraints; 81 struct wlr_pointer_constraints_v1 *pointer_constraints;
76 struct wl_listener pointer_constraint; 82 struct wl_listener pointer_constraint;
@@ -79,14 +85,53 @@ struct sway_server {
79 struct wl_listener output_manager_apply; 85 struct wl_listener output_manager_apply;
80 struct wl_listener output_manager_test; 86 struct wl_listener output_manager_test;
81 87
88 struct wlr_gamma_control_manager_v1 *gamma_control_manager_v1;
89 struct wl_listener gamma_control_set_gamma;
90
91 struct {
92 struct sway_session_lock *lock;
93 struct wlr_session_lock_manager_v1 *manager;
94
95 struct wl_listener new_lock;
96 struct wl_listener manager_destroy;
97 } session_lock;
98
82 struct wlr_output_power_manager_v1 *output_power_manager_v1; 99 struct wlr_output_power_manager_v1 *output_power_manager_v1;
83 struct wl_listener output_power_manager_set_mode; 100 struct wl_listener output_power_manager_set_mode;
84 struct wlr_input_method_manager_v2 *input_method; 101 struct wlr_input_method_manager_v2 *input_method;
85 struct wlr_text_input_manager_v3 *text_input; 102 struct wlr_text_input_manager_v3 *text_input;
103 struct wlr_ext_foreign_toplevel_list_v1 *foreign_toplevel_list;
86 struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager; 104 struct wlr_foreign_toplevel_manager_v1 *foreign_toplevel_manager;
105 struct wlr_content_type_manager_v1 *content_type_manager_v1;
106 struct wlr_data_control_manager_v1 *data_control_manager_v1;
107 struct wlr_screencopy_manager_v1 *screencopy_manager_v1;
108 struct wlr_export_dmabuf_manager_v1 *export_dmabuf_manager_v1;
109 struct wlr_security_context_manager_v1 *security_context_manager_v1;
110
111 struct wlr_xdg_activation_v1 *xdg_activation_v1;
112 struct wl_listener xdg_activation_v1_request_activate;
113 struct wl_listener xdg_activation_v1_new_token;
87 114
115 struct wl_listener request_set_cursor_shape;
116
117 struct wl_list pending_launcher_ctxs; // launcher_ctx::link
118
119 // The timeout for transactions, after which a transaction is applied
120 // regardless of readiness.
88 size_t txn_timeout_ms; 121 size_t txn_timeout_ms;
89 list_t *transactions; 122
123 // Stores a transaction after it has been committed, but is waiting for
124 // views to ack the new dimensions before being applied. A queued
125 // transaction is frozen and must not have new instructions added to it.
126 struct sway_transaction *queued_transaction;
127
128 // Stores a pending transaction that will be committed once the existing
129 // queued transaction is applied and freed. The pending transaction can be
130 // updated with new instructions as needed.
131 struct sway_transaction *pending_transaction;
132
133 // Stores the nodes that have been marked as "dirty" and will be put into
134 // the pending transaction.
90 list_t *dirty_nodes; 135 list_t *dirty_nodes;
91}; 136};
92 137
@@ -96,34 +141,41 @@ struct sway_debug {
96 bool noatomic; // Ignore atomic layout updates 141 bool noatomic; // Ignore atomic layout updates
97 bool txn_timings; // Log verbose messages about transactions 142 bool txn_timings; // Log verbose messages about transactions
98 bool txn_wait; // Always wait for the timeout before applying 143 bool txn_wait; // Always wait for the timeout before applying
99 144 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}; 145};
106 146
107extern struct sway_debug debug; 147extern struct sway_debug debug;
108 148
109/* Prepares an unprivileged server_init by performing all privileged operations in advance */ 149extern bool allow_unsupported_gpu;
110bool server_privileged_prepare(struct sway_server *server); 150
111bool server_init(struct sway_server *server); 151bool server_init(struct sway_server *server);
112void server_fini(struct sway_server *server); 152void server_fini(struct sway_server *server);
113bool server_start(struct sway_server *server); 153bool server_start(struct sway_server *server);
114void server_run(struct sway_server *server); 154void server_run(struct sway_server *server);
115 155
116void handle_compositor_new_surface(struct wl_listener *listener, void *data); 156void restore_nofile_limit(void);
157
117void handle_new_output(struct wl_listener *listener, void *data); 158void handle_new_output(struct wl_listener *listener, void *data);
118 159
119void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data); 160void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data);
120void handle_layer_shell_surface(struct wl_listener *listener, void *data); 161void handle_layer_shell_surface(struct wl_listener *listener, void *data);
121void handle_xdg_shell_surface(struct wl_listener *listener, void *data); 162void sway_session_lock_init(void);
163void sway_session_lock_add_output(struct sway_session_lock *lock,
164 struct sway_output *output);
165bool sway_session_lock_has_surface(struct sway_session_lock *lock,
166 struct wlr_surface *surface);
167void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data);
122#if HAVE_XWAYLAND 168#if HAVE_XWAYLAND
123void handle_xwayland_surface(struct wl_listener *listener, void *data); 169void handle_xwayland_surface(struct wl_listener *listener, void *data);
124#endif 170#endif
125void handle_server_decoration(struct wl_listener *listener, void *data); 171void handle_server_decoration(struct wl_listener *listener, void *data);
126void handle_xdg_decoration(struct wl_listener *listener, void *data); 172void handle_xdg_decoration(struct wl_listener *listener, void *data);
127void handle_pointer_constraint(struct wl_listener *listener, void *data); 173void handle_pointer_constraint(struct wl_listener *listener, void *data);
174void xdg_activation_v1_handle_request_activate(struct wl_listener *listener,
175 void *data);
176void xdg_activation_v1_handle_new_token(struct wl_listener *listener,
177 void *data);
178
179void set_rr_scheduling(void);
128 180
129#endif 181#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..7faacdcc 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,15 +80,11 @@ 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 87 struct wlr_ext_foreign_toplevel_handle_v1 *ext_foreign_toplevel;
97 // when a transaction is applied.
98 struct wlr_box saved_geometry;
99 88
100 struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel; 89 struct wlr_foreign_toplevel_handle_v1 *foreign_toplevel;
101 struct wl_listener foreign_activate_request; 90 struct wl_listener foreign_activate_request;
@@ -108,19 +97,16 @@ struct sway_view {
108 list_t *executed_criteria; // struct criteria * 97 list_t *executed_criteria; // struct criteria *
109 98
110 union { 99 union {
111 struct wlr_xdg_surface *wlr_xdg_surface; 100 struct wlr_xdg_toplevel *wlr_xdg_toplevel;
112#if HAVE_XWAYLAND 101#if HAVE_XWAYLAND
113 struct wlr_xwayland_surface *wlr_xwayland_surface; 102 struct wlr_xwayland_surface *wlr_xwayland_surface;
114#endif 103#endif
115 struct wlr_wl_shell_surface *wlr_wl_shell_surface;
116 }; 104 };
117 105
118 struct { 106 struct {
119 struct wl_signal unmap; 107 struct wl_signal unmap;
120 } events; 108 } events;
121 109
122 struct wl_listener surface_new_subsurface;
123
124 int max_render_time; // In milliseconds 110 int max_render_time; // In milliseconds
125 111
126 enum seat_config_shortcuts_inhibit shortcuts_inhibit; 112 enum seat_config_shortcuts_inhibit shortcuts_inhibit;
@@ -145,6 +131,8 @@ struct sway_xdg_shell_view {
145struct sway_xwayland_view { 131struct sway_xwayland_view {
146 struct sway_view view; 132 struct sway_view view;
147 133
134 struct wlr_scene_tree *surface_tree;
135
148 struct wl_listener commit; 136 struct wl_listener commit;
149 struct wl_listener request_move; 137 struct wl_listener request_move;
150 struct wl_listener request_resize; 138 struct wl_listener request_resize;
@@ -156,71 +144,55 @@ struct sway_xwayland_view {
156 struct wl_listener set_title; 144 struct wl_listener set_title;
157 struct wl_listener set_class; 145 struct wl_listener set_class;
158 struct wl_listener set_role; 146 struct wl_listener set_role;
147 struct wl_listener set_startup_id;
159 struct wl_listener set_window_type; 148 struct wl_listener set_window_type;
160 struct wl_listener set_hints; 149 struct wl_listener set_hints;
161 struct wl_listener set_decorations; 150 struct wl_listener set_decorations;
151 struct wl_listener associate;
152 struct wl_listener dissociate;
162 struct wl_listener map; 153 struct wl_listener map;
163 struct wl_listener unmap; 154 struct wl_listener unmap;
164 struct wl_listener destroy; 155 struct wl_listener destroy;
165 struct wl_listener override_redirect; 156 struct wl_listener override_redirect;
157
158 struct wl_listener surface_tree_destroy;
166}; 159};
167 160
168struct sway_xwayland_unmanaged { 161struct sway_xwayland_unmanaged {
169 struct wlr_xwayland_surface *wlr_xwayland_surface; 162 struct wlr_xwayland_surface *wlr_xwayland_surface;
170 struct wl_list link;
171 163
172 int lx, ly; 164 struct wlr_scene_surface *surface_scene;
173 165
166 struct wl_listener request_activate;
174 struct wl_listener request_configure; 167 struct wl_listener request_configure;
175 struct wl_listener request_fullscreen; 168 struct wl_listener request_fullscreen;
176 struct wl_listener commit;
177 struct wl_listener set_geometry; 169 struct wl_listener set_geometry;
170 struct wl_listener associate;
171 struct wl_listener dissociate;
178 struct wl_listener map; 172 struct wl_listener map;
179 struct wl_listener unmap; 173 struct wl_listener unmap;
180 struct wl_listener destroy; 174 struct wl_listener destroy;
181 struct wl_listener override_redirect; 175 struct wl_listener override_redirect;
182}; 176};
183#endif 177#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 178
179struct sway_popup_desc {
180 struct wlr_scene_node *relative;
198 struct sway_view *view; 181 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}; 182};
217 183
218struct sway_xdg_popup { 184struct sway_xdg_popup {
219 struct sway_view_child child; 185 struct sway_view *view;
186
187 struct wlr_scene_tree *scene_tree;
188 struct wlr_scene_tree *xdg_surface_tree;
189 struct wlr_xdg_popup *wlr_xdg_popup;
220 190
221 struct wlr_xdg_surface *wlr_xdg_surface; 191 struct sway_popup_desc desc;
222 192
193 struct wl_listener surface_commit;
223 struct wl_listener new_popup; 194 struct wl_listener new_popup;
195 struct wl_listener reposition;
224 struct wl_listener destroy; 196 struct wl_listener destroy;
225}; 197};
226 198
@@ -269,7 +241,12 @@ void view_set_activated(struct sway_view *view, bool activated);
269/** 241/**
270 * Called when the view requests to be focused. 242 * Called when the view requests to be focused.
271 */ 243 */
272void view_request_activate(struct sway_view *view); 244void view_request_activate(struct sway_view *view, struct sway_seat *seat);
245
246/*
247 * Called when the view requests urgent state
248 */
249void view_request_urgent(struct sway_view *view);
273 250
274/** 251/**
275 * If possible, instructs the client to change their decoration mode. 252 * If possible, instructs the client to change their decoration mode.
@@ -288,42 +265,31 @@ void view_close(struct sway_view *view);
288 265
289void view_close_popups(struct sway_view *view); 266void view_close_popups(struct sway_view *view);
290 267
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 268// view implementation
306 269
307void view_init(struct sway_view *view, enum sway_view_type type, 270bool view_init(struct sway_view *view, enum sway_view_type type,
308 const struct sway_view_impl *impl); 271 const struct sway_view_impl *impl);
309 272
310void view_destroy(struct sway_view *view); 273void view_destroy(struct sway_view *view);
311 274
312void view_begin_destroy(struct sway_view *view); 275void view_begin_destroy(struct sway_view *view);
313 276
277/**
278 * Map a view, ie. make it visible in the tree.
279 *
280 * `fullscreen` should be set to true (and optionally `fullscreen_output`
281 * should be populated) if the view should be made fullscreen immediately.
282 *
283 * `decoration` should be set to true if the client prefers CSD. The client's
284 * preference may be ignored.
285 */
314void view_map(struct sway_view *view, struct wlr_surface *wlr_surface, 286void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
315 bool fullscreen, struct wlr_output *fullscreen_output, bool decoration); 287 bool fullscreen, struct wlr_output *fullscreen_output, bool decoration);
316 288
317void view_unmap(struct sway_view *view); 289void view_unmap(struct sway_view *view);
318 290
319void view_update_size(struct sway_view *view, int width, int height); 291void view_update_size(struct sway_view *view);
320 292void 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 293
328struct sway_view *view_from_wlr_xdg_surface( 294struct sway_view *view_from_wlr_xdg_surface(
329 struct wlr_xdg_surface *xdg_surface); 295 struct wlr_xdg_surface *xdg_surface);
@@ -333,6 +299,8 @@ struct sway_view *view_from_wlr_xwayland_surface(
333#endif 299#endif
334struct sway_view *view_from_wlr_surface(struct wlr_surface *surface); 300struct sway_view *view_from_wlr_surface(struct wlr_surface *surface);
335 301
302void view_update_app_id(struct sway_view *view);
303
336/** 304/**
337 * Re-read the view's title property and update any relevant title bars. 305 * Re-read the view's title property and update any relevant title bars.
338 * The force argument makes it recreate the title bars even if the title hasn't 306 * The force argument makes it recreate the title bars even if the title hasn't
@@ -362,4 +330,8 @@ void view_save_buffer(struct sway_view *view);
362 330
363bool view_is_transient_for(struct sway_view *child, struct sway_view *ancestor); 331bool view_is_transient_for(struct sway_view *child, struct sway_view *ancestor);
364 332
333void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx);
334
335void view_send_frame_done(struct sway_view *view);
336
365#endif 337#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..1043e4ba 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',
@@ -14,10 +14,12 @@ project(
14add_project_arguments( 14add_project_arguments(
15 [ 15 [
16 '-DWLR_USE_UNSTABLE', 16 '-DWLR_USE_UNSTABLE',
17 '-D_POSIX_C_SOURCE=200809L',
17 18
18 '-Wno-unused-parameter', 19 '-Wno-unused-parameter',
19 '-Wno-unused-result', 20 '-Wno-unused-result',
20 '-Wno-missing-braces', 21 '-Wno-missing-braces',
22 '-Wno-format-zero-length',
21 '-Wundef', 23 '-Wundef',
22 '-Wvla', 24 '-Wvla',
23 ], 25 ],
@@ -35,79 +37,65 @@ if is_freebsd
35 add_project_arguments('-D_C11_SOURCE', language: 'c') 37 add_project_arguments('-D_C11_SOURCE', language: 'c')
36endif 38endif
37 39
38jsonc = dependency('json-c', version: '>=0.13') 40# Execute the wlroots subproject, if any
39pcre = dependency('libpcre') 41wlroots_version = ['>=0.18.0', '<0.19.0']
40wayland_server = dependency('wayland-server') 42subproject(
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', 43 'wlroots',
64 default_options: ['examples=false'], 44 default_options: ['examples=false'],
65 required: false, 45 required: false,
66 version: wlroots_version, 46 version: wlroots_version,
67) 47)
48wlroots = dependency('wlroots', version: wlroots_version)
68wlroots_features = { 49wlroots_features = {
69 'xwayland': false, 50 'xwayland': false,
70 'systemd': false, 51 'libinput_backend': false,
71 'elogind': false, 52 'session': false,
72 'libseat': false,
73} 53}
74if wlroots_proj.found() 54foreach name, _ : wlroots_features
75 wlroots = wlroots_proj.get_variable('wlroots') 55 var_name = 'have_' + name.underscorify()
76 wlroots_conf = wlroots_proj.get_variable('conf_data') 56 have = wlroots.get_variable(pkgconfig: var_name, internal: var_name) == 'true'
77 foreach name, _ : wlroots_features 57 wlroots_features += { name: have }
78 has = wlroots_conf.get('WLR_HAS_' + name.to_upper()) == 1 58endforeach
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 59
89if get_option('xwayland').enabled() and not wlroots_features['xwayland'] 60if get_option('xwayland').enabled() and not wlroots_features['xwayland']
90 error('Cannot enable Xwayland in sway: wlroots has been built without Xwayland support') 61 error('Cannot enable Xwayland in sway: wlroots has been built without Xwayland support')
91endif 62endif
92have_xwayland = xcb.found() and wlroots_features['xwayland'] 63
64null_dep = dependency('', required: false)
65
66jsonc = dependency('json-c', version: '>=0.13')
67pcre2 = dependency('libpcre2-8')
68wayland_server = dependency('wayland-server', version: '>=1.21.0')
69wayland_client = dependency('wayland-client')
70wayland_cursor = dependency('wayland-cursor')
71wayland_protos = dependency('wayland-protocols', version: '>=1.24')
72xkbcommon = dependency('xkbcommon', version: '>=1.5.0')
73cairo = dependency('cairo')
74pango = dependency('pango')
75pangocairo = dependency('pangocairo')
76gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
77pixman = dependency('pixman-1')
78libevdev = dependency('libevdev')
79libinput = wlroots_features['libinput_backend'] ? dependency('libinput', version: '>=1.21.0') : null_dep
80xcb = dependency('xcb', required: get_option('xwayland'))
81drm = dependency('libdrm')
82libudev = wlroots_features['libinput_backend'] ? dependency('libudev') : null_dep
83math = cc.find_library('m')
84rt = cc.find_library('rt')
85xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland'))
86threads = dependency('threads') # for pthread_setschedparam
87
88have_xwayland = xcb.found() and xcb_icccm.found() and wlroots_features['xwayland']
93 89
94if get_option('sd-bus-provider') == 'auto' 90if get_option('sd-bus-provider') == 'auto'
95 if not get_option('tray').disabled() 91 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') 92 assert(get_option('auto_features').auto(), 'sd-bus-provider must not be set to auto since auto_features != auto')
97 endif 93 endif
98 sdbus = dependency('libsystemd', 94 sdbus = dependency(['libsystemd', 'libelogind'],
99 required: false, 95 required: false,
100 version: '>=239', 96 version: '>=239',
101 not_found_message: 'libsystemd not found, trying libelogind',
102 ) 97 )
103 if not sdbus.found() 98 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) 99 sdbus = dependency('basu', required: false)
112 endif 100 endif
113else 101else
@@ -128,11 +116,15 @@ conf_data.set10('HAVE_LIBSYSTEMD', sdbus.found() and sdbus.name() == 'libsystemd
128conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind') 116conf_data.set10('HAVE_LIBELOGIND', sdbus.found() and sdbus.name() == 'libelogind')
129conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu') 117conf_data.set10('HAVE_BASU', sdbus.found() and sdbus.name() == 'basu')
130conf_data.set10('HAVE_TRAY', have_tray) 118conf_data.set10('HAVE_TRAY', have_tray)
119conf_data.set10('HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM', cc.has_header_symbol(
120 'libinput.h',
121 'LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM',
122 dependencies: libinput,
123))
131 124
132scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) 125scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages'))
133if scdoc.found() 126if scdoc.found()
134 scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) 127 scdoc_prog = find_program(scdoc.get_variable(pkgconfig: 'scdoc'), native: true)
135 sh = find_program('sh', native: true)
136 mandir = get_option('mandir') 128 mandir = get_option('mandir')
137 man_files = [ 129 man_files = [
138 'sway/sway.1.scd', 130 'sway/sway.1.scd',
@@ -143,9 +135,15 @@ if scdoc.found()
143 'sway/sway-output.5.scd', 135 'sway/sway-output.5.scd',
144 'swaybar/swaybar-protocol.7.scd', 136 'swaybar/swaybar-protocol.7.scd',
145 'swaymsg/swaymsg.1.scd', 137 'swaymsg/swaymsg.1.scd',
146 'swaynag/swaynag.1.scd',
147 'swaynag/swaynag.5.scd',
148 ] 138 ]
139
140 if get_option('swaynag')
141 man_files += [
142 'swaynag/swaynag.1.scd',
143 'swaynag/swaynag.5.scd',
144 ]
145 endif
146
149 foreach filename : man_files 147 foreach filename : man_files
150 topic = filename.split('.')[-3].split('/')[-1] 148 topic = filename.split('.')[-3].split('/')[-1]
151 section = filename.split('.')[-2] 149 section = filename.split('.')[-2]
@@ -155,10 +153,10 @@ if scdoc.found()
155 output, 153 output,
156 input: filename, 154 input: filename,
157 output: output, 155 output: output,
158 command: [ 156 command: scdoc_prog,
159 sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)
160 ],
161 install: true, 157 install: true,
158 feed: true,
159 capture: true,
162 install_dir: '@0@/man@1@'.format(mandir, section) 160 install_dir: '@0@/man@1@'.format(mandir, section)
163 ) 161 )
164 endforeach 162 endforeach
@@ -169,8 +167,8 @@ add_project_arguments('-DSYSCONFDIR="/@0@"'.format(join_paths(prefix, sysconfdir
169version = '"@0@"'.format(meson.project_version()) 167version = '"@0@"'.format(meson.project_version())
170git = find_program('git', native: true, required: false) 168git = find_program('git', native: true, required: false)
171if git.found() 169if git.found()
172 git_commit = run_command([git, 'rev-parse', '--short', 'HEAD']) 170 git_commit = run_command([git, 'rev-parse', '--short', 'HEAD'], check: false)
173 git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD']) 171 git_branch = run_command([git, 'rev-parse', '--abbrev-ref', 'HEAD'], check: false)
174 if git_commit.returncode() == 0 and git_branch.returncode() == 0 172 if git_commit.returncode() == 0 and git_branch.returncode() == 0
175 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format( 173 version = '"@0@-@1@ (" __DATE__ ", branch \'@2@\')"'.format(
176 meson.project_version(), 174 meson.project_version(),
@@ -183,7 +181,7 @@ add_project_arguments('-DSWAY_VERSION=@0@'.format(version), language: 'c')
183 181
184# Compute the relative path used by compiler invocations. 182# Compute the relative path used by compiler invocations.
185source_root = meson.current_source_dir().split('/') 183source_root = meson.current_source_dir().split('/')
186build_root = meson.build_root().split('/') 184build_root = meson.global_build_root().split('/')
187relative_dir_parts = [] 185relative_dir_parts = []
188i = 0 186i = 0
189in_prefix = true 187in_prefix = true
@@ -227,9 +225,15 @@ subdir('common')
227subdir('sway') 225subdir('sway')
228subdir('swaymsg') 226subdir('swaymsg')
229 227
230subdir('client') 228if get_option('swaybar') or get_option('swaynag')
231subdir('swaybar') 229 subdir('client')
232subdir('swaynag') 230endif
231if get_option('swaybar')
232 subdir('swaybar')
233endif
234if get_option('swaynag')
235 subdir('swaynag')
236endif
233 237
234config = configuration_data() 238config = configuration_data()
235config.set('datadir', join_paths(prefix, datadir)) 239config.set('datadir', join_paths(prefix, datadir))
@@ -264,61 +268,11 @@ if get_option('default-wallpaper')
264 install_data(wallpaper_files, install_dir: wallpaper_install_dir) 268 install_data(wallpaper_files, install_dir: wallpaper_install_dir)
265endif 269endif
266 270
267if get_option('zsh-completions') 271subdir('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 272
313summary({ 273summary({
314 'xwayland': have_xwayland, 274 'xwayland': have_xwayland,
315 'gdk-pixbuf': gdk_pixbuf.found(), 275 'gdk-pixbuf': gdk_pixbuf.found(),
316 'sd-bus': sdbus.found(),
317 'tray': have_tray, 276 'tray': have_tray,
318 'man-pages': scdoc.found(), 277 'man-pages': scdoc.found(),
319}, bool_yn: true) 278}, 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/release.sh b/release.sh
new file mode 100755
index 00000000..62baf415
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,31 @@
1#!/bin/sh -eu
2
3prev=$(git describe --tags --abbrev=0)
4next=$(meson rewrite kwargs info project / 2>&1 >/dev/null | jq -r '.kwargs["project#/"].version')
5
6case "$next" in
7*-dev)
8 echo "This is a development version"
9 exit 1
10 ;;
11esac
12
13if [ "$prev" = "$next" ]; then
14 echo "Version not bumped in meson.build"
15 exit 1
16fi
17
18if ! git diff-index --quiet HEAD -- meson.build; then
19 echo "meson.build not committed"
20 exit 1
21fi
22
23shortlog="$(git shortlog --no-merges "$prev..")"
24(echo "sway $next"; echo ""; echo "$shortlog") | git tag "$next" -ase -F -
25
26prefix=sway-$next
27archive=$prefix.tar.gz
28git archive --prefix="$prefix/" -o "$archive" "$next"
29gpg --output "$archive".sig --detach-sig "$archive"
30
31gh release create "sway $next" -t "$next" -n "" -d "$archive" "$archive.sig"
diff --git a/sway/commands.c b/sway/commands.c
index fe1e98b5..8d003dfa 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809
2#include <ctype.h> 1#include <ctype.h>
3#include <stdarg.h> 2#include <stdarg.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -42,15 +41,17 @@ struct cmd_results *checkarg(int argc, const char *name, enum expected_args type
42} 41}
43 42
44/* Keep alphabetized */ 43/* Keep alphabetized */
45static struct cmd_handler handlers[] = { 44static const struct cmd_handler handlers[] = {
46 { "assign", cmd_assign }, 45 { "assign", cmd_assign },
47 { "bar", cmd_bar }, 46 { "bar", cmd_bar },
48 { "bindcode", cmd_bindcode }, 47 { "bindcode", cmd_bindcode },
48 { "bindgesture", cmd_bindgesture },
49 { "bindswitch", cmd_bindswitch }, 49 { "bindswitch", cmd_bindswitch },
50 { "bindsym", cmd_bindsym }, 50 { "bindsym", cmd_bindsym },
51 { "client.background", cmd_client_noop }, 51 { "client.background", cmd_client_noop },
52 { "client.focused", cmd_client_focused }, 52 { "client.focused", cmd_client_focused },
53 { "client.focused_inactive", cmd_client_focused_inactive }, 53 { "client.focused_inactive", cmd_client_focused_inactive },
54 { "client.focused_tab_title", cmd_client_focused_tab_title },
54 { "client.placeholder", cmd_client_noop }, 55 { "client.placeholder", cmd_client_noop },
55 { "client.unfocused", cmd_client_unfocused }, 56 { "client.unfocused", cmd_client_unfocused },
56 { "client.urgent", cmd_client_urgent }, 57 { "client.urgent", cmd_client_urgent },
@@ -91,6 +92,7 @@ static struct cmd_handler handlers[] = {
91 { "titlebar_border_thickness", cmd_titlebar_border_thickness }, 92 { "titlebar_border_thickness", cmd_titlebar_border_thickness },
92 { "titlebar_padding", cmd_titlebar_padding }, 93 { "titlebar_padding", cmd_titlebar_padding },
93 { "unbindcode", cmd_unbindcode }, 94 { "unbindcode", cmd_unbindcode },
95 { "unbindgesture", cmd_unbindgesture },
94 { "unbindswitch", cmd_unbindswitch }, 96 { "unbindswitch", cmd_unbindswitch },
95 { "unbindsym", cmd_unbindsym }, 97 { "unbindsym", cmd_unbindsym },
96 { "workspace", cmd_workspace }, 98 { "workspace", cmd_workspace },
@@ -98,9 +100,10 @@ static struct cmd_handler handlers[] = {
98}; 100};
99 101
100/* Config-time only commands. Keep alphabetized */ 102/* Config-time only commands. Keep alphabetized */
101static struct cmd_handler config_handlers[] = { 103static const struct cmd_handler config_handlers[] = {
102 { "default_orientation", cmd_default_orientation }, 104 { "default_orientation", cmd_default_orientation },
103 { "include", cmd_include }, 105 { "include", cmd_include },
106 { "primary_selection", cmd_primary_selection },
104 { "swaybg_command", cmd_swaybg_command }, 107 { "swaybg_command", cmd_swaybg_command },
105 { "swaynag_command", cmd_swaynag_command }, 108 { "swaynag_command", cmd_swaynag_command },
106 { "workspace_layout", cmd_workspace_layout }, 109 { "workspace_layout", cmd_workspace_layout },
@@ -108,7 +111,7 @@ static struct cmd_handler config_handlers[] = {
108}; 111};
109 112
110/* Runtime-only commands. Keep alphabetized */ 113/* Runtime-only commands. Keep alphabetized */
111static struct cmd_handler command_handlers[] = { 114static const struct cmd_handler command_handlers[] = {
112 { "border", cmd_border }, 115 { "border", cmd_border },
113 { "create_output", cmd_create_output }, 116 { "create_output", cmd_create_output },
114 { "exit", cmd_exit }, 117 { "exit", cmd_exit },
@@ -144,22 +147,22 @@ static int handler_compare(const void *_a, const void *_b) {
144 return strcasecmp(a->command, b->command); 147 return strcasecmp(a->command, b->command);
145} 148}
146 149
147struct cmd_handler *find_handler(char *line, struct cmd_handler *handlers, 150const struct cmd_handler *find_handler(const char *line,
148 size_t handlers_size) { 151 const struct cmd_handler *handlers, size_t handlers_size) {
149 if (!handlers || !handlers_size) { 152 if (!handlers || !handlers_size) {
150 return NULL; 153 return NULL;
151 } 154 }
152 struct cmd_handler query = { .command = line }; 155 const struct cmd_handler query = { .command = line };
153 return bsearch(&query, handlers, 156 return bsearch(&query, handlers,
154 handlers_size / sizeof(struct cmd_handler), 157 handlers_size / sizeof(struct cmd_handler),
155 sizeof(struct cmd_handler), handler_compare); 158 sizeof(struct cmd_handler), handler_compare);
156} 159}
157 160
158static struct cmd_handler *find_handler_ex(char *line, 161static const struct cmd_handler *find_handler_ex(char *line,
159 struct cmd_handler *config_handlers, size_t config_handlers_size, 162 const struct cmd_handler *config_handlers, size_t config_handlers_size,
160 struct cmd_handler *command_handlers, size_t command_handlers_size, 163 const struct cmd_handler *command_handlers, size_t command_handlers_size,
161 struct cmd_handler *handlers, size_t handlers_size) { 164 const struct cmd_handler *handlers, size_t handlers_size) {
162 struct cmd_handler *handler = NULL; 165 const struct cmd_handler *handler = NULL;
163 if (config->reading) { 166 if (config->reading) {
164 handler = find_handler(line, config_handlers, config_handlers_size); 167 handler = find_handler(line, config_handlers, config_handlers_size);
165 } else if (config->active) { 168 } else if (config->active) {
@@ -168,16 +171,17 @@ static struct cmd_handler *find_handler_ex(char *line,
168 return handler ? handler : find_handler(line, handlers, handlers_size); 171 return handler ? handler : find_handler(line, handlers, handlers_size);
169} 172}
170 173
171static struct cmd_handler *find_core_handler(char *line) { 174static const struct cmd_handler *find_core_handler(char *line) {
172 return find_handler_ex(line, config_handlers, sizeof(config_handlers), 175 return find_handler_ex(line, config_handlers, sizeof(config_handlers),
173 command_handlers, sizeof(command_handlers), 176 command_handlers, sizeof(command_handlers),
174 handlers, sizeof(handlers)); 177 handlers, sizeof(handlers));
175} 178}
176 179
177static void set_config_node(struct sway_node *node) { 180static void set_config_node(struct sway_node *node, bool node_overridden) {
178 config->handler_context.node = node; 181 config->handler_context.node = node;
179 config->handler_context.container = NULL; 182 config->handler_context.container = NULL;
180 config->handler_context.workspace = NULL; 183 config->handler_context.workspace = NULL;
184 config->handler_context.node_overridden = node_overridden;
181 185
182 if (node == NULL) { 186 if (node == NULL) {
183 return; 187 return;
@@ -186,7 +190,7 @@ static void set_config_node(struct sway_node *node) {
186 switch (node->type) { 190 switch (node->type) {
187 case N_CONTAINER: 191 case N_CONTAINER:
188 config->handler_context.container = node->sway_container; 192 config->handler_context.container = node->sway_container;
189 config->handler_context.workspace = node->sway_container->workspace; 193 config->handler_context.workspace = node->sway_container->pending.workspace;
190 break; 194 break;
191 case N_WORKSPACE: 195 case N_WORKSPACE:
192 config->handler_context.workspace = node->sway_workspace; 196 config->handler_context.workspace = node->sway_workspace;
@@ -202,6 +206,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
202 char *cmd; 206 char *cmd;
203 char matched_delim = ';'; 207 char matched_delim = ';';
204 list_t *containers = NULL; 208 list_t *containers = NULL;
209 bool using_criteria = false;
205 210
206 if (seat == NULL) { 211 if (seat == NULL) {
207 // passing a NULL seat means we just pick the default seat 212 // passing a NULL seat means we just pick the default seat
@@ -225,7 +230,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
225 for (; isspace(*head); ++head) {} 230 for (; isspace(*head); ++head) {}
226 // Extract criteria (valid for this command list only). 231 // Extract criteria (valid for this command list only).
227 if (matched_delim == ';') { 232 if (matched_delim == ';') {
228 config->handler_context.using_criteria = false; 233 using_criteria = false;
229 if (*head == '[') { 234 if (*head == '[') {
230 char *error = NULL; 235 char *error = NULL;
231 struct criteria *criteria = criteria_parse(head, &error); 236 struct criteria *criteria = criteria_parse(head, &error);
@@ -239,7 +244,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
239 containers = criteria_get_containers(criteria); 244 containers = criteria_get_containers(criteria);
240 head += strlen(criteria->raw); 245 head += strlen(criteria->raw);
241 criteria_destroy(criteria); 246 criteria_destroy(criteria);
242 config->handler_context.using_criteria = true; 247 using_criteria = true;
243 // Skip leading whitespace 248 // Skip leading whitespace
244 for (; isspace(*head); ++head) {} 249 for (; isspace(*head); ++head) {}
245 } 250 }
@@ -265,7 +270,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
265 } 270 }
266 } 271 }
267 } 272 }
268 struct cmd_handler *handler = find_core_handler(argv[0]); 273 const struct cmd_handler *handler = find_core_handler(argv[0]);
269 if (!handler) { 274 if (!handler) {
270 list_add(res_list, cmd_results_new(CMD_INVALID, 275 list_add(res_list, cmd_results_new(CMD_INVALID,
271 "Unknown/invalid command '%s'", argv[0])); 276 "Unknown/invalid command '%s'", argv[0]));
@@ -278,11 +283,14 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
278 argv[i] = do_var_replacement(argv[i]); 283 argv[i] = do_var_replacement(argv[i]);
279 } 284 }
280 285
281 if (!config->handler_context.using_criteria) { 286
282 // The container or workspace which this command will run on. 287 if (!using_criteria) {
283 struct sway_node *node = con ? &con->node : 288 if (con) {
284 seat_get_focus_inactive(seat, &root->node); 289 set_config_node(&con->node, true);
285 set_config_node(node); 290 } else {
291 set_config_node(seat_get_focus_inactive(seat, &root->node),
292 false);
293 }
286 struct cmd_results *res = handler->handle(argc-1, argv+1); 294 struct cmd_results *res = handler->handle(argc-1, argv+1);
287 list_add(res_list, res); 295 list_add(res_list, res);
288 if (res->status == CMD_INVALID) { 296 if (res->status == CMD_INVALID) {
@@ -296,7 +304,7 @@ list_t *execute_command(char *_exec, struct sway_seat *seat,
296 struct cmd_results *fail_res = NULL; 304 struct cmd_results *fail_res = NULL;
297 for (int i = 0; i < containers->length; ++i) { 305 for (int i = 0; i < containers->length; ++i) {
298 struct sway_container *container = containers->items[i]; 306 struct sway_container *container = containers->items[i];
299 set_config_node(&container->node); 307 set_config_node(&container->node, true);
300 struct cmd_results *res = handler->handle(argc-1, argv+1); 308 struct cmd_results *res = handler->handle(argc-1, argv+1);
301 if (res->status == CMD_SUCCESS) { 309 if (res->status == CMD_SUCCESS) {
302 free_cmd_results(res); 310 free_cmd_results(res);
@@ -370,12 +378,15 @@ struct cmd_results *config_command(char *exec, char **new_block) {
370 378
371 // Determine the command handler 379 // Determine the command handler
372 sway_log(SWAY_INFO, "Config command: %s", exec); 380 sway_log(SWAY_INFO, "Config command: %s", exec);
373 struct cmd_handler *handler = find_core_handler(argv[0]); 381 const struct cmd_handler *handler = find_core_handler(argv[0]);
374 if (!handler || !handler->handle) { 382 if (!handler || !handler->handle) {
375 const char *error = handler 383 if (handler) {
376 ? "Command '%s' is shimmed, but unimplemented" 384 results = cmd_results_new(CMD_INVALID,
377 : "Unknown/invalid command '%s'"; 385 "Command '%s' is shimmed, but unimplemented", argv[0]);
378 results = cmd_results_new(CMD_INVALID, error, argv[0]); 386 } else {
387 results = cmd_results_new(CMD_INVALID,
388 "Unknown/invalid command '%s'", argv[0]);
389 }
379 goto cleanup; 390 goto cleanup;
380 } 391 }
381 392
@@ -401,6 +412,7 @@ struct cmd_results *config_command(char *exec, char **new_block) {
401 && handler->handle != cmd_bindsym 412 && handler->handle != cmd_bindsym
402 && handler->handle != cmd_bindcode 413 && handler->handle != cmd_bindcode
403 && handler->handle != cmd_bindswitch 414 && handler->handle != cmd_bindswitch
415 && handler->handle != cmd_bindgesture
404 && handler->handle != cmd_set 416 && handler->handle != cmd_set
405 && handler->handle != cmd_for_window 417 && handler->handle != cmd_for_window
406 && (*argv[i] == '\"' || *argv[i] == '\'')) { 418 && (*argv[i] == '\"' || *argv[i] == '\'')) {
@@ -418,12 +430,12 @@ cleanup:
418} 430}
419 431
420struct cmd_results *config_subcommand(char **argv, int argc, 432struct cmd_results *config_subcommand(char **argv, int argc,
421 struct cmd_handler *handlers, size_t handlers_size) { 433 const struct cmd_handler *handlers, size_t handlers_size) {
422 char *command = join_args(argv, argc); 434 char *command = join_args(argv, argc);
423 sway_log(SWAY_DEBUG, "Subcommand: %s", command); 435 sway_log(SWAY_DEBUG, "Subcommand: %s", command);
424 free(command); 436 free(command);
425 437
426 struct cmd_handler *handler = find_handler(argv[0], handlers, 438 const struct cmd_handler *handler = find_handler(argv[0], handlers,
427 handlers_size); 439 handlers_size);
428 if (!handler) { 440 if (!handler) {
429 return cmd_results_new(CMD_INVALID, 441 return cmd_results_new(CMD_INVALID,
@@ -453,41 +465,13 @@ struct cmd_results *config_commands_command(char *exec) {
453 goto cleanup; 465 goto cleanup;
454 } 466 }
455 467
456 struct cmd_handler *handler = find_handler(cmd, NULL, 0); 468 const struct cmd_handler *handler = find_handler(cmd, NULL, 0);
457 if (!handler && strcmp(cmd, "*") != 0) { 469 if (!handler && strcmp(cmd, "*") != 0) {
458 results = cmd_results_new(CMD_INVALID, 470 results = cmd_results_new(CMD_INVALID,
459 "Unknown/invalid command '%s'", cmd); 471 "Unknown/invalid command '%s'", cmd);
460 goto cleanup; 472 goto cleanup;
461 } 473 }
462 474
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); 475 results = cmd_results_new(CMD_SUCCESS, NULL);
492 476
493cleanup: 477cleanup:
@@ -504,14 +488,10 @@ struct cmd_results *cmd_results_new(enum cmd_status status,
504 } 488 }
505 results->status = status; 489 results->status = status;
506 if (format) { 490 if (format) {
507 char *error = malloc(256);
508 va_list args; 491 va_list args;
509 va_start(args, format); 492 va_start(args, format);
510 if (error) { 493 results->error = vformat_str(format, args);
511 vsnprintf(error, 256, format, args);
512 }
513 va_end(args); 494 va_end(args);
514 results->error = error;
515 } else { 495 } else {
516 results->error = NULL; 496 results->error = NULL;
517 } 497 }
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
index 976bc3cc..bf95cf00 100644
--- a/sway/commands/assign.c
+++ b/sway/commands/assign.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 1#include <stdio.h>
3#include <string.h> 2#include <string.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
@@ -17,7 +16,7 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
17 char *err_str = NULL; 16 char *err_str = NULL;
18 struct criteria *criteria = criteria_parse(argv[0], &err_str); 17 struct criteria *criteria = criteria_parse(argv[0], &err_str);
19 if (!criteria) { 18 if (!criteria) {
20 error = cmd_results_new(CMD_INVALID, err_str); 19 error = cmd_results_new(CMD_INVALID, "%s", err_str);
21 free(err_str); 20 free(err_str);
22 return error; 21 return error;
23 } 22 }
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index d42b7fc2..635e895b 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809
2#include <stdio.h> 1#include <stdio.h>
3#include <string.h> 2#include <string.h>
4#include <strings.h> 3#include <strings.h>
@@ -8,7 +7,7 @@
8#include "log.h" 7#include "log.h"
9 8
10// Must be in alphabetical order for bsearch 9// Must be in alphabetical order for bsearch
11static struct cmd_handler bar_handlers[] = { 10static const struct cmd_handler bar_handlers[] = {
12 { "bindcode", bar_cmd_bindcode }, 11 { "bindcode", bar_cmd_bindcode },
13 { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, 12 { "binding_mode_indicator", bar_cmd_binding_mode_indicator },
14 { "bindsym", bar_cmd_bindsym }, 13 { "bindsym", bar_cmd_bindsym },
@@ -41,7 +40,7 @@ static struct cmd_handler bar_handlers[] = {
41}; 40};
42 41
43// Must be in alphabetical order for bsearch 42// Must be in alphabetical order for bsearch
44static struct cmd_handler bar_config_handlers[] = { 43static const struct cmd_handler bar_config_handlers[] = {
45 { "id", bar_cmd_id }, 44 { "id", bar_cmd_id },
46 { "swaybar_command", bar_cmd_swaybar_command }, 45 { "swaybar_command", bar_cmd_swaybar_command },
47}; 46};
@@ -73,12 +72,10 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
73 } 72 }
74 ++argv; --argc; 73 ++argv; --argc;
75 } else if (config->reading && !config->current_bar) { 74 } else if (config->reading && !config->current_bar) {
76 int len = snprintf(NULL, 0, "bar-%d", config->bars->length) + 1; 75 id = format_str("bar-%d", config->bars->length);
77 id = malloc(len * sizeof(char));
78 if (!id) { 76 if (!id) {
79 return cmd_results_new(CMD_FAILURE, "Unable to allocate bar id"); 77 return cmd_results_new(CMD_FAILURE, "Unable to allocate bar id");
80 } 78 }
81 snprintf(id, len, "bar-%d", config->bars->length);
82 } else if (!config->reading && strcmp(argv[0], "mode") != 0 && 79 } else if (!config->reading && strcmp(argv[0], "mode") != 0 &&
83 strcmp(argv[0], "hidden_state") != 0) { 80 strcmp(argv[0], "hidden_state") != 0) {
84 if (is_subcommand(argv[0])) { 81 if (is_subcommand(argv[0])) {
@@ -116,6 +113,7 @@ struct cmd_results *cmd_bar(int argc, char **argv) {
116 if (res && res->status != CMD_SUCCESS) { 113 if (res && res->status != CMD_SUCCESS) {
117 if (id) { 114 if (id) {
118 free_bar_config(config->current_bar); 115 free_bar_config(config->current_bar);
116 config->current_bar = NULL;
119 id = NULL; 117 id = NULL;
120 } 118 }
121 return res; 119 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..0c074679 100644
--- a/sway/commands/bar/font.c
+++ b/sway/commands/bar/font.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
@@ -11,7 +10,20 @@ struct cmd_results *bar_cmd_font(int argc, char **argv) {
11 } 10 }
12 char *font = join_args(argv, argc); 11 char *font = join_args(argv, argc);
13 free(config->current_bar->font); 12 free(config->current_bar->font);
14 config->current_bar->font = font; 13
14 if (strncmp(font, "pango:", 6) == 0) {
15 if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) {
16 config->current_bar->pango_markup = true;
17 }
18 config->current_bar->font = strdup(font + 6);
19 } else {
20 if (config->current_bar->pango_markup == PANGO_MARKUP_DEFAULT) {
21 config->current_bar->pango_markup = false;
22 }
23 config->current_bar->font = strdup(font);
24 }
25
26 free(font);
15 sway_log(SWAY_DEBUG, "Settings font '%s' for bar: %s", 27 sway_log(SWAY_DEBUG, "Settings font '%s' for bar: %s",
16 config->current_bar->font, config->current_bar->id); 28 config->current_bar->font, config->current_bar->id);
17 return cmd_results_new(CMD_SUCCESS, NULL); 29 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..7b38831e 100644
--- a/sway/commands/bar/hidden_state.c
+++ b/sway/commands/bar/hidden_state.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
@@ -54,7 +53,7 @@ struct cmd_results *bar_cmd_hidden_state(int argc, char **argv) {
54 } 53 }
55 54
56 const char *state = argv[0]; 55 const char *state = argv[0];
57 if (config->reading) { 56 if (config->current_bar) {
58 error = bar_set_hidden_state(config->current_bar, state); 57 error = bar_set_hidden_state(config->current_bar, state);
59 } else { 58 } else {
60 const char *id = argc == 2 ? argv[1] : NULL; 59 const char *id = argc == 2 ? argv[1] : NULL;
diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c
index 6ac07843..fee21709 100644
--- a/sway/commands/bar/icon_theme.c
+++ b/sway/commands/bar/icon_theme.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "config.h" 2#include "config.h"
4#include "sway/commands.h" 3#include "sway/commands.h"
diff --git a/sway/commands/bar/id.c b/sway/commands/bar/id.c
index a9a61743..46cf4ca9 100644
--- a/sway/commands/bar/id.c
+++ b/sway/commands/bar/id.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
diff --git a/sway/commands/bar/mode.c b/sway/commands/bar/mode.c
index 8b3fb275..d69e910b 100644
--- a/sway/commands/bar/mode.c
+++ b/sway/commands/bar/mode.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
@@ -58,7 +57,7 @@ struct cmd_results *bar_cmd_mode(int argc, char **argv) {
58 } 57 }
59 58
60 const char *mode = argv[0]; 59 const char *mode = argv[0];
61 if (config->reading) { 60 if (config->current_bar) {
62 error = bar_set_mode(config->current_bar, mode); 61 error = bar_set_mode(config->current_bar, mode);
63 } else { 62 } else {
64 const char *id = argc == 2 ? argv[1] : NULL; 63 const char *id = argc == 2 ? argv[1] : NULL;
diff --git a/sway/commands/bar/output.c b/sway/commands/bar/output.c
index cac1d056..51730176 100644
--- a/sway/commands/bar/output.c
+++ b/sway/commands/bar/output.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <string.h> 2#include <string.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
diff --git a/sway/commands/bar/position.c b/sway/commands/bar/position.c
index b207de0b..94f530ec 100644
--- a/sway/commands/bar/position.c
+++ b/sway/commands/bar/position.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
diff --git a/sway/commands/bar/separator_symbol.c b/sway/commands/bar/separator_symbol.c
index 6737d4d2..50e9a873 100644
--- a/sway/commands/bar/separator_symbol.c
+++ b/sway/commands/bar/separator_symbol.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
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/bar/tray_output.c b/sway/commands/bar/tray_output.c
index eb3b486e..679facf7 100644
--- a/sway/commands/bar/tray_output.c
+++ b/sway/commands/bar/tray_output.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "config.h" 2#include "config.h"
4#include "sway/commands.h" 3#include "sway/commands.h"
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index f6e58d99..268f2855 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <libevdev/libevdev.h> 1#include <libevdev/libevdev.h>
3#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
4#include <string.h> 3#include <string.h>
@@ -8,6 +7,7 @@
8#include <wlr/types/wlr_cursor.h> 7#include <wlr/types/wlr_cursor.h>
9#include "sway/commands.h" 8#include "sway/commands.h"
10#include "sway/config.h" 9#include "sway/config.h"
10#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 11#include "sway/input/cursor.h"
12#include "sway/input/keyboard.h" 12#include "sway/input/keyboard.h"
13#include "sway/ipc-server.h" 13#include "sway/ipc-server.h"
@@ -46,7 +46,7 @@ static bool binding_switch_compare(struct sway_switch_binding *binding_a,
46 if (binding_a->type != binding_b->type) { 46 if (binding_a->type != binding_b->type) {
47 return false; 47 return false;
48 } 48 }
49 if (binding_a->state != binding_b->state) { 49 if (binding_a->trigger != binding_b->trigger) {
50 return false; 50 return false;
51 } 51 }
52 if ((binding_a->flags & BINDING_LOCKED) != 52 if ((binding_a->flags & BINDING_LOCKED) !=
@@ -126,7 +126,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
126 if (!button) { 126 if (!button) {
127 if (message) { 127 if (message) {
128 struct cmd_results *error = 128 struct cmd_results *error =
129 cmd_results_new(CMD_INVALID, message); 129 cmd_results_new(CMD_INVALID, "%s", message);
130 free(message); 130 free(message);
131 return error; 131 return error;
132 } else { 132 } else {
@@ -142,7 +142,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
142 if (!button) { 142 if (!button) {
143 if (message) { 143 if (message) {
144 struct cmd_results *error = 144 struct cmd_results *error =
145 cmd_results_new(CMD_INVALID, message); 145 cmd_results_new(CMD_INVALID, "%s", message);
146 free(message); 146 free(message);
147 return error; 147 return error;
148 } else { 148 } else {
@@ -181,7 +181,7 @@ static struct cmd_results *identify_key(const char* name, bool first_key,
181 uint32_t button = get_mouse_bindsym(name, &message); 181 uint32_t button = get_mouse_bindsym(name, &message);
182 if (message) { 182 if (message) {
183 struct cmd_results *error = 183 struct cmd_results *error =
184 cmd_results_new(CMD_INVALID, message); 184 cmd_results_new(CMD_INVALID, "%s", message);
185 free(message); 185 free(message);
186 return error; 186 return error;
187 } else if (button) { 187 } else if (button) {
@@ -371,6 +371,7 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
371 strlen("--input-device=")) == 0) { 371 strlen("--input-device=")) == 0) {
372 free(binding->input); 372 free(binding->input);
373 binding->input = strdup(argv[0] + strlen("--input-device=")); 373 binding->input = strdup(argv[0] + strlen("--input-device="));
374 strip_quotes(binding->input);
374 } else if (strcmp("--no-warn", argv[0]) == 0) { 375 } else if (strcmp("--no-warn", argv[0]) == 0) {
375 warn = false; 376 warn = false;
376 } else if (strcmp("--no-repeat", argv[0]) == 0) { 377 } else if (strcmp("--no-repeat", argv[0]) == 0) {
@@ -537,7 +538,7 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv,
537 free_switch_binding(binding); 538 free_switch_binding(binding);
538 return cmd_results_new(CMD_FAILURE, 539 return cmd_results_new(CMD_FAILURE,
539 "Invalid %s command (expected binding with the form " 540 "Invalid %s command (expected binding with the form "
540 "<switch>:<state>)", bindtype, argc); 541 "<switch>:<state>)", bindtype);
541 } 542 }
542 if (strcmp(split->items[0], "tablet") == 0) { 543 if (strcmp(split->items[0], "tablet") == 0) {
543 binding->type = WLR_SWITCH_TYPE_TABLET_MODE; 544 binding->type = WLR_SWITCH_TYPE_TABLET_MODE;
@@ -547,20 +548,21 @@ struct cmd_results *cmd_bind_or_unbind_switch(int argc, char **argv,
547 free_switch_binding(binding); 548 free_switch_binding(binding);
548 return cmd_results_new(CMD_FAILURE, 549 return cmd_results_new(CMD_FAILURE,
549 "Invalid %s command (expected switch binding: " 550 "Invalid %s command (expected switch binding: "
550 "unknown switch %s)", bindtype, split->items[0]); 551 "unknown switch %s)", bindtype,
552 (const char *)split->items[0]);
551 } 553 }
552 if (strcmp(split->items[1], "on") == 0) { 554 if (strcmp(split->items[1], "on") == 0) {
553 binding->state = WLR_SWITCH_STATE_ON; 555 binding->trigger = SWAY_SWITCH_TRIGGER_ON;
554 } else if (strcmp(split->items[1], "off") == 0) { 556 } else if (strcmp(split->items[1], "off") == 0) {
555 binding->state = WLR_SWITCH_STATE_OFF; 557 binding->trigger = SWAY_SWITCH_TRIGGER_OFF;
556 } else if (strcmp(split->items[1], "toggle") == 0) { 558 } else if (strcmp(split->items[1], "toggle") == 0) {
557 binding->state = WLR_SWITCH_STATE_TOGGLE; 559 binding->trigger = SWAY_SWITCH_TRIGGER_TOGGLE;
558 } else { 560 } else {
559 free_switch_binding(binding); 561 free_switch_binding(binding);
560 return cmd_results_new(CMD_FAILURE, 562 return cmd_results_new(CMD_FAILURE,
561 "Invalid %s command " 563 "Invalid %s command "
562 "(expected switch state: unknown state %d)", 564 "(expected switch state: unknown state %s)",
563 bindtype, split->items[0]); 565 bindtype, (const char *)split->items[1]);
564 } 566 }
565 list_free_items_and_destroy(split); 567 list_free_items_and_destroy(split);
566 568
@@ -642,6 +644,8 @@ void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding)
642 if (success) { 644 if (success) {
643 ipc_event_binding(binding); 645 ipc_event_binding(binding);
644 } 646 }
647
648 transaction_commit_dirty();
645} 649}
646 650
647/** 651/**
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..8bc1048c 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <stdint.h> 2#include <stdint.h>
4#include <string.h> 3#include <string.h>
@@ -7,6 +6,8 @@
7#include <signal.h> 6#include <signal.h>
8#include "sway/commands.h" 7#include "sway/commands.h"
9#include "sway/config.h" 8#include "sway/config.h"
9#include "sway/server.h"
10#include "sway/desktop/launcher.h"
10#include "sway/tree/container.h" 11#include "sway/tree/container.h"
11#include "sway/tree/root.h" 12#include "sway/tree/root.h"
12#include "sway/tree/workspace.h" 13#include "sway/tree/workspace.h"
@@ -24,11 +25,22 @@ struct cmd_results *cmd_exec_validate(int argc, char **argv) {
24 return error; 25 return error;
25} 26}
26 27
28static void export_xdga_token(struct launcher_ctx *ctx) {
29 const char *token = launcher_ctx_get_token_name(ctx);
30 setenv("XDG_ACTIVATION_TOKEN", token, 1);
31}
32
33static void export_startup_id(struct launcher_ctx *ctx) {
34 const char *token = launcher_ctx_get_token_name(ctx);
35 setenv("DESKTOP_STARTUP_ID", token, 1);
36}
37
27struct cmd_results *cmd_exec_process(int argc, char **argv) { 38struct cmd_results *cmd_exec_process(int argc, char **argv) {
28 struct cmd_results *error = NULL; 39 struct cmd_results *error = NULL;
29 char *tmp = NULL; 40 char *cmd = NULL;
41 bool no_startup_id = false;
30 if (strcmp(argv[0], "--no-startup-id") == 0) { 42 if (strcmp(argv[0], "--no-startup-id") == 0) {
31 sway_log(SWAY_INFO, "exec switch '--no-startup-id' not supported, ignored."); 43 no_startup_id = true;
32 --argc; ++argv; 44 --argc; ++argv;
33 if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) { 45 if ((error = checkarg(argc, argv[-1], EXPECTED_AT_LEAST, 1))) {
34 return error; 46 return error;
@@ -36,17 +48,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
36 } 48 }
37 49
38 if (argc == 1 && (argv[0][0] == '\'' || argv[0][0] == '"')) { 50 if (argc == 1 && (argv[0][0] == '\'' || argv[0][0] == '"')) {
39 tmp = strdup(argv[0]); 51 cmd = strdup(argv[0]);
40 strip_quotes(tmp); 52 strip_quotes(cmd);
41 } else { 53 } else {
42 tmp = join_args(argv, argc); 54 cmd = join_args(argv, argc);
43 } 55 }
44 56
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); 57 sway_log(SWAY_DEBUG, "Executing %s", cmd);
51 58
52 int fd[2]; 59 int fd[2];
@@ -55,18 +62,28 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
55 } 62 }
56 63
57 pid_t pid, child; 64 pid_t pid, child;
65 struct launcher_ctx *ctx = launcher_ctx_create_internal();
58 // Fork process 66 // Fork process
59 if ((pid = fork()) == 0) { 67 if ((pid = fork()) == 0) {
60 // Fork child process again 68 // Fork child process again
69 restore_nofile_limit();
61 setsid(); 70 setsid();
62 sigset_t set; 71 sigset_t set;
63 sigemptyset(&set); 72 sigemptyset(&set);
64 sigprocmask(SIG_SETMASK, &set, NULL); 73 sigprocmask(SIG_SETMASK, &set, NULL);
74 signal(SIGPIPE, SIG_DFL);
65 close(fd[0]); 75 close(fd[0]);
66 if ((child = fork()) == 0) { 76 if ((child = fork()) == 0) {
67 close(fd[1]); 77 close(fd[1]);
68 execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL); 78 if (ctx) {
69 _exit(0); 79 export_xdga_token(ctx);
80 }
81 if (ctx && !no_startup_id) {
82 export_startup_id(ctx);
83 }
84 execlp("sh", "sh", "-c", cmd, (void *)NULL);
85 sway_log_errno(SWAY_ERROR, "execlp failed");
86 _exit(1);
70 } 87 }
71 ssize_t s = 0; 88 ssize_t s = 0;
72 while ((size_t)s < sizeof(pid_t)) { 89 while ((size_t)s < sizeof(pid_t)) {
@@ -75,10 +92,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
75 close(fd[1]); 92 close(fd[1]);
76 _exit(0); // Close child process 93 _exit(0); // Close child process
77 } else if (pid < 0) { 94 } else if (pid < 0) {
95 free(cmd);
78 close(fd[0]); 96 close(fd[0]);
79 close(fd[1]); 97 close(fd[1]);
80 return cmd_results_new(CMD_FAILURE, "fork() failed"); 98 return cmd_results_new(CMD_FAILURE, "fork() failed");
81 } 99 }
100 free(cmd);
82 close(fd[1]); // close write 101 close(fd[1]); // close write
83 ssize_t s = 0; 102 ssize_t s = 0;
84 while ((size_t)s < sizeof(pid_t)) { 103 while ((size_t)s < sizeof(pid_t)) {
@@ -89,8 +108,12 @@ struct cmd_results *cmd_exec_process(int argc, char **argv) {
89 waitpid(pid, NULL, 0); 108 waitpid(pid, NULL, 0);
90 if (child > 0) { 109 if (child > 0) {
91 sway_log(SWAY_DEBUG, "Child process created with pid %d", child); 110 sway_log(SWAY_DEBUG, "Child process created with pid %d", child);
92 root_record_workspace_pid(child); 111 if (ctx != NULL) {
112 sway_log(SWAY_DEBUG, "Recording workspace for process %d", child);
113 ctx->pid = child;
114 }
93 } else { 115 } else {
116 launcher_ctx_destroy(ctx);
94 return cmd_results_new(CMD_FAILURE, "Second fork() failed"); 117 return cmd_results_new(CMD_FAILURE, "Second fork() failed");
95 } 118 }
96 119
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..9920d03e 100644
--- a/sway/commands/font.c
+++ b/sway/commands/font.c
@@ -1,9 +1,9 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
5#include "log.h" 4#include "log.h"
6#include "stringop.h" 5#include "stringop.h"
6#include <pango/pangocairo.h>
7 7
8struct cmd_results *cmd_font(int argc, char **argv) { 8struct cmd_results *cmd_font(int argc, char **argv) {
9 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -16,12 +16,34 @@ struct cmd_results *cmd_font(int argc, char **argv) {
16 if (strncmp(font, "pango:", 6) == 0) { 16 if (strncmp(font, "pango:", 6) == 0) {
17 config->pango_markup = true; 17 config->pango_markup = true;
18 config->font = strdup(font + 6); 18 config->font = strdup(font + 6);
19 free(font);
19 } else { 20 } else {
20 config->pango_markup = false; 21 config->pango_markup = false;
21 config->font = strdup(font); 22 config->font = font;
22 } 23 }
23 24
24 free(font); 25 // Parse the font early so we can reject it if it's not valid for pango.
25 config_update_font_height(true); 26 // Also avoids re-parsing each time we render text.
27 PangoFontDescription *font_description = pango_font_description_from_string(config->font);
28
29 const char *family = pango_font_description_get_family(font_description);
30 if (family == NULL) {
31 pango_font_description_free(font_description);
32 return cmd_results_new(CMD_FAILURE, "Invalid font family.");
33 }
34
35 const gint size = pango_font_description_get_size(font_description);
36 if (size == 0) {
37 pango_font_description_free(font_description);
38 return cmd_results_new(CMD_FAILURE, "Invalid font size.");
39 }
40
41 if (config->font_description != NULL) {
42 pango_font_description_free(config->font_description);
43 }
44
45 config->font_description = font_description;
46 config_update_font_height();
47
26 return cmd_results_new(CMD_SUCCESS, NULL); 48 return cmd_results_new(CMD_SUCCESS, NULL);
27} 49}
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..90a20716
--- /dev/null
+++ b/sway/commands/gesture.c
@@ -0,0 +1,165 @@
1#include "sway/config.h"
2
3#include "gesture.h"
4#include "log.h"
5#include "stringop.h"
6#include "sway/commands.h"
7
8void free_gesture_binding(struct sway_gesture_binding *binding) {
9 if (!binding) {
10 return;
11 }
12 free(binding->input);
13 free(binding->command);
14 free(binding);
15}
16
17/**
18 * Returns true if the bindings have the same gesture type, direction, etc
19 */
20static bool binding_gesture_equal(struct sway_gesture_binding *binding_a,
21 struct sway_gesture_binding *binding_b) {
22 if (strcmp(binding_a->input, binding_b->input) != 0) {
23 return false;
24 }
25
26 if (!gesture_equal(&binding_a->gesture, &binding_b->gesture)) {
27 return false;
28 }
29
30 if ((binding_a->flags & BINDING_EXACT) !=
31 (binding_b->flags & BINDING_EXACT)) {
32 return false;
33 }
34 return true;
35}
36
37/**
38 * Add gesture binding to config
39 */
40static struct cmd_results *gesture_binding_add(
41 struct sway_gesture_binding *binding,
42 const char *gesturecombo, bool warn) {
43 list_t *mode_bindings = config->current_mode->gesture_bindings;
44 // overwrite the binding if it already exists
45 bool overwritten = false;
46 for (int i = 0; i < mode_bindings->length; ++i) {
47 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
48 if (binding_gesture_equal(binding, config_binding)) {
49 sway_log(SWAY_INFO, "Overwriting binding '%s' to `%s` from `%s`",
50 gesturecombo, binding->command, config_binding->command);
51 if (warn) {
52 config_add_swaynag_warning("Overwriting binding"
53 "'%s' to `%s` from `%s`",
54 gesturecombo, binding->command,
55 config_binding->command);
56 }
57 free_gesture_binding(config_binding);
58 mode_bindings->items[i] = binding;
59 overwritten = true;
60 }
61 }
62
63 if (!overwritten) {
64 list_add(mode_bindings, binding);
65 sway_log(SWAY_DEBUG, "bindgesture - Bound %s to command `%s`",
66 gesturecombo, binding->command);
67 }
68
69 return cmd_results_new(CMD_SUCCESS, NULL);
70}
71
72/**
73 * Remove gesture binding from config
74 */
75static struct cmd_results *gesture_binding_remove(
76 struct sway_gesture_binding *binding, const char *gesturecombo) {
77 list_t *mode_bindings = config->current_mode->gesture_bindings;
78 for (int i = 0; i < mode_bindings->length; ++i) {
79 struct sway_gesture_binding *config_binding = mode_bindings->items[i];
80 if (binding_gesture_equal(binding, config_binding)) {
81 free_gesture_binding(config_binding);
82 free_gesture_binding(binding);
83 list_del(mode_bindings, i);
84 sway_log(SWAY_DEBUG, "unbindgesture - Unbound %s gesture",
85 gesturecombo);
86 return cmd_results_new(CMD_SUCCESS, NULL);
87 }
88 }
89
90 free_gesture_binding(binding);
91 return cmd_results_new(CMD_FAILURE, "Could not find gesture binding `%s`",
92 gesturecombo);
93}
94
95/**
96 * Parse and execute bindgesture or unbindgesture command.
97 */
98static struct cmd_results *cmd_bind_or_unbind_gesture(int argc, char **argv, bool unbind) {
99 int minargs = 2;
100 char *bindtype = "bindgesture";
101 if (unbind) {
102 minargs--;
103 bindtype = "unbindgesture";
104 }
105
106 struct cmd_results *error = NULL;
107 if ((error = checkarg(argc, bindtype, EXPECTED_AT_LEAST, minargs))) {
108 return error;
109 }
110 struct sway_gesture_binding *binding = calloc(1, sizeof(struct sway_gesture_binding));
111 if (!binding) {
112 return cmd_results_new(CMD_FAILURE, "Unable to allocate binding");
113 }
114 binding->input = strdup("*");
115
116 bool warn = true;
117
118 // Handle flags
119 while (argc > 0) {
120 if (strcmp("--exact", argv[0]) == 0) {
121 binding->flags |= BINDING_EXACT;
122 } else if (strcmp("--no-warn", argv[0]) == 0) {
123 warn = false;
124 } else if (strncmp("--input-device=", argv[0],
125 strlen("--input-device=")) == 0) {
126 free(binding->input);
127 binding->input = strdup(argv[0] + strlen("--input-device="));
128 } else {
129 break;
130 }
131 argv++;
132 argc--;
133 }
134
135 if (argc < minargs) {
136 free(binding);
137 return cmd_results_new(CMD_FAILURE,
138 "Invalid %s command (expected at least %d "
139 "non-option arguments, got %d)", bindtype, minargs, argc);
140 }
141
142 char* errmsg = NULL;
143 if ((errmsg = gesture_parse(argv[0], &binding->gesture))) {
144 free(binding);
145 struct cmd_results *final = cmd_results_new(CMD_FAILURE,
146 "Invalid %s command (%s)",
147 bindtype, errmsg);
148 free(errmsg);
149 return final;
150 }
151
152 if (unbind) {
153 return gesture_binding_remove(binding, argv[0]);
154 }
155 binding->command = join_args(argv + 1, argc - 1);
156 return gesture_binding_add(binding, argv[0], warn);
157}
158
159struct cmd_results *cmd_bindgesture(int argc, char **argv) {
160 return cmd_bind_or_unbind_gesture(argc, argv, false);
161}
162
163struct cmd_results *cmd_unbindgesture(int argc, char **argv) {
164 return cmd_bind_or_unbind_gesture(argc, argv, true);
165}
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/calibration_matrix.c b/sway/commands/input/calibration_matrix.c
index 38749fbb..53fe2c35 100644
--- a/sway/commands/input/calibration_matrix.c
+++ b/sway/commands/input/calibration_matrix.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/config.h" 3#include "sway/config.h"
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..2f8f753d 100644
--- a/sway/commands/input/map_from_region.c
+++ b/sway/commands/input/map_from_region.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <string.h> 2#include <string.h>
4#include <strings.h> 3#include <strings.h>
@@ -11,11 +10,21 @@ static bool parse_coords(const char *str, double *x, double *y, bool *mm) {
11 *mm = false; 10 *mm = false;
12 11
13 char *end; 12 char *end;
14 *x = strtod(str, &end); 13
15 if (end[0] != 'x') { 14 // Check for "0x" prefix to avoid strtod treating the string as hex
16 return false; 15 if (str[0] == '0' && str[1] == 'x') {
16 if (strlen(str) < 3) {
17 return false;
18 }
19 *x = 0;
20 end = (char *)str + 2;
21 } else {
22 *x = strtod(str, &end);
23 if (end[0] != 'x') {
24 return false;
25 }
26 ++end;
17 } 27 }
18 ++end;
19 28
20 *y = strtod(end, &end); 29 *y = strtod(end, &end);
21 if (end[0] == 'm') { 30 if (end[0] == 'm') {
diff --git a/sway/commands/input/map_to_output.c b/sway/commands/input/map_to_output.c
index f60fb7d5..a7266baa 100644
--- a/sway/commands/input/map_to_output.c
+++ b/sway/commands/input/map_to_output.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include <strings.h> 2#include <strings.h>
4#include "sway/config.h" 3#include "sway/config.h"
diff --git a/sway/commands/input/map_to_region.c b/sway/commands/input/map_to_region.c
index e85495e5..9087c589 100644
--- a/sway/commands/input/map_to_region.c
+++ b/sway/commands/input/map_to_region.c
@@ -1,7 +1,5 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <string.h> 2#include <string.h>
4#include <wlr/types/wlr_box.h>
5#include "sway/commands.h" 3#include "sway/commands.h"
6#include "sway/config.h" 4#include "sway/config.h"
7 5
@@ -50,5 +48,5 @@ struct cmd_results *input_cmd_map_to_region(int argc, char **argv) {
50error: 48error:
51 free(ic->mapped_to_region); 49 free(ic->mapped_to_region);
52 ic->mapped_to_region = NULL; 50 ic->mapped_to_region = NULL;
53 return cmd_results_new(CMD_FAILURE, errstr); 51 return cmd_results_new(CMD_FAILURE, "%s", errstr);
54} 52}
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_file.c b/sway/commands/input/xkb_file.c
index 493f94fb..056f00e5 100644
--- a/sway/commands/input/xkb_file.c
+++ b/sway/commands/input/xkb_file.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <unistd.h> 1#include <unistd.h>
3#include <errno.h> 2#include <errno.h>
4#include "sway/config.h" 3#include "sway/config.h"
diff --git a/sway/commands/input/xkb_layout.c b/sway/commands/input/xkb_layout.c
index 22626517..1d01886c 100644
--- a/sway/commands/input/xkb_layout.c
+++ b/sway/commands/input/xkb_layout.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
diff --git a/sway/commands/input/xkb_model.c b/sway/commands/input/xkb_model.c
index f4a33de3..a9144a8a 100644
--- a/sway/commands/input/xkb_model.c
+++ b/sway/commands/input/xkb_model.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
diff --git a/sway/commands/input/xkb_numlock.c b/sway/commands/input/xkb_numlock.c
index 87d3e60c..bbe848fe 100644
--- a/sway/commands/input/xkb_numlock.c
+++ b/sway/commands/input/xkb_numlock.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "util.h" 3#include "util.h"
diff --git a/sway/commands/input/xkb_options.c b/sway/commands/input/xkb_options.c
index d609293f..7ca20777 100644
--- a/sway/commands/input/xkb_options.c
+++ b/sway/commands/input/xkb_options.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
diff --git a/sway/commands/input/xkb_rules.c b/sway/commands/input/xkb_rules.c
index 3b59622c..8fbd26fb 100644
--- a/sway/commands/input/xkb_rules.c
+++ b/sway/commands/input/xkb_rules.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
diff --git a/sway/commands/input/xkb_switch_layout.c b/sway/commands/input/xkb_switch_layout.c
index d6548a68..ecac8e6c 100644
--- a/sway/commands/input/xkb_switch_layout.c
+++ b/sway/commands/input/xkb_switch_layout.c
@@ -1,10 +1,15 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
2#include <wlr/interfaces/wlr_keyboard.h>
3#include "sway/config.h" 3#include "sway/config.h"
4#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/input/input-manager.h" 5#include "sway/input/input-manager.h"
6#include "log.h" 6#include "log.h"
7 7
8struct xkb_switch_layout_action {
9 struct wlr_keyboard *keyboard;
10 xkb_layout_index_t layout;
11};
12
8static void switch_layout(struct wlr_keyboard *kbd, xkb_layout_index_t idx) { 13static 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); 14 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap);
10 if (idx >= num_layouts) { 15 if (idx >= num_layouts) {
@@ -28,10 +33,10 @@ static xkb_layout_index_t get_current_layout_index(struct wlr_keyboard *kbd) {
28 return layout_idx; 33 return layout_idx;
29} 34}
30 35
31static void switch_layout_relative(struct wlr_keyboard *kbd, int dir) { 36static 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); 37 xkb_layout_index_t num_layouts = xkb_keymap_num_layouts(kbd->keymap);
33 xkb_layout_index_t idx = get_current_layout_index(kbd); 38 xkb_layout_index_t idx = get_current_layout_index(kbd);
34 switch_layout(kbd, (idx + num_layouts + dir) % num_layouts); 39 return (idx + num_layouts + dir) % num_layouts;
35} 40}
36 41
37struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) { 42struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
@@ -66,6 +71,18 @@ struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
66 relative = 0; 71 relative = 0;
67 } 72 }
68 73
74 struct xkb_switch_layout_action *actions = calloc(
75 wl_list_length(&server.input->devices),
76 sizeof(struct xkb_switch_layout_action));
77 size_t actions_len = 0;
78
79 if (!actions) {
80 return cmd_results_new(CMD_FAILURE, "Unable to allocate actions");
81 }
82
83 /* Calculate new indexes first because switching a layout in one
84 keyboard may result in a change on other keyboards as well because
85 of keyboard groups. */
69 struct sway_input_device *dev; 86 struct sway_input_device *dev;
70 wl_list_for_each(dev, &server.input->devices, link) { 87 wl_list_for_each(dev, &server.input->devices, link) {
71 if (strcmp(ic->identifier, "*") != 0 && 88 if (strcmp(ic->identifier, "*") != 0 &&
@@ -76,12 +93,22 @@ struct cmd_results *input_cmd_xkb_switch_layout(int argc, char **argv) {
76 if (dev->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { 93 if (dev->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) {
77 continue; 94 continue;
78 } 95 }
96
97 struct xkb_switch_layout_action *action =
98 &actions[actions_len++];
99
100 action->keyboard = wlr_keyboard_from_input_device(dev->wlr_device);
79 if (relative) { 101 if (relative) {
80 switch_layout_relative(dev->wlr_device->keyboard, relative); 102 action->layout = get_layout_relative(action->keyboard, relative);
81 } else { 103 } else {
82 switch_layout(dev->wlr_device->keyboard, layout); 104 action->layout = layout;
83 } 105 }
84 } 106 }
85 107
108 for (size_t i = 0; i < actions_len; i++) {
109 switch_layout(actions[i].keyboard, actions[i].layout);
110 }
111 free(actions);
112
86 return cmd_results_new(CMD_SUCCESS, NULL); 113 return cmd_results_new(CMD_SUCCESS, NULL);
87} 114}
diff --git a/sway/commands/input/xkb_variant.c b/sway/commands/input/xkb_variant.c
index d0e21d77..2d14ea9c 100644
--- a/sway/commands/input/xkb_variant.c
+++ b/sway/commands/input/xkb_variant.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/config.h" 1#include "sway/config.h"
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "log.h" 3#include "log.h"
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..2bfc86b3 100644
--- a/sway/commands/mark.c
+++ b/sway/commands/mark.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -59,7 +58,7 @@ struct cmd_results *cmd_mark(int argc, char **argv) {
59 } 58 }
60 59
61 free(mark); 60 free(mark);
62 container_update_marks_textures(container); 61 container_update_marks(container);
63 if (container->view) { 62 if (container->view) {
64 view_execute_criteria(container->view); 63 view_execute_criteria(container->view);
65 } 64 }
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index a5871dab..b3216967 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <string.h> 2#include <string.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
@@ -9,12 +8,14 @@
9#include "stringop.h" 8#include "stringop.h"
10 9
11// Must be in order for the bsearch 10// Must be in order for the bsearch
12static struct cmd_handler mode_handlers[] = { 11static const struct cmd_handler mode_handlers[] = {
13 { "bindcode", cmd_bindcode }, 12 { "bindcode", cmd_bindcode },
13 { "bindgesture", cmd_bindgesture },
14 { "bindswitch", cmd_bindswitch }, 14 { "bindswitch", cmd_bindswitch },
15 { "bindsym", cmd_bindsym }, 15 { "bindsym", cmd_bindsym },
16 { "set", cmd_set }, 16 { "set", cmd_set },
17 { "unbindcode", cmd_unbindcode }, 17 { "unbindcode", cmd_unbindcode },
18 { "unbindgesture", cmd_unbindgesture },
18 { "unbindswitch", cmd_unbindswitch }, 19 { "unbindswitch", cmd_unbindswitch },
19 { "unbindsym", cmd_unbindsym }, 20 { "unbindsym", cmd_unbindsym },
20}; 21};
@@ -59,6 +60,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
59 mode->keycode_bindings = create_list(); 60 mode->keycode_bindings = create_list();
60 mode->mouse_bindings = create_list(); 61 mode->mouse_bindings = create_list();
61 mode->switch_bindings = create_list(); 62 mode->switch_bindings = create_list();
63 mode->gesture_bindings = create_list();
62 mode->pango = pango; 64 mode->pango = pango;
63 list_add(config->modes, mode); 65 list_add(config->modes, mode);
64 } 66 }
diff --git a/sway/commands/move.c b/sway/commands/move.c
index f8f89f18..8addf26e 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <math.h> 2#include <math.h>
4#include <stdbool.h> 3#include <stdbool.h>
@@ -113,8 +112,8 @@ static void container_move_to_container_from_direction(
113 struct sway_container *container, struct sway_container *destination, 112 struct sway_container *container, struct sway_container *destination,
114 enum wlr_direction move_dir) { 113 enum wlr_direction move_dir) {
115 if (destination->view) { 114 if (destination->view) {
116 if (destination->parent == container->parent && 115 if (destination->pending.parent == container->pending.parent &&
117 destination->workspace == container->workspace) { 116 destination->pending.workspace == container->pending.workspace) {
118 sway_log(SWAY_DEBUG, "Swapping siblings"); 117 sway_log(SWAY_DEBUG, "Swapping siblings");
119 list_t *siblings = container_get_siblings(container); 118 list_t *siblings = container_get_siblings(container);
120 int container_index = list_find(siblings, container); 119 int container_index = list_find(siblings, container);
@@ -126,28 +125,28 @@ static void container_move_to_container_from_direction(
126 int offset = 125 int offset =
127 move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP; 126 move_dir == WLR_DIRECTION_LEFT || move_dir == WLR_DIRECTION_UP;
128 int index = container_sibling_index(destination) + offset; 127 int index = container_sibling_index(destination) + offset;
129 if (destination->parent) { 128 if (destination->pending.parent) {
130 container_insert_child(destination->parent, container, index); 129 container_insert_child(destination->pending.parent, container, index);
131 } else { 130 } else {
132 workspace_insert_tiling(destination->workspace, 131 workspace_insert_tiling(destination->pending.workspace,
133 container, index); 132 container, index);
134 } 133 }
135 container->width = container->height = 0; 134 container->pending.width = container->pending.height = 0;
136 container->width_fraction = container->height_fraction = 0; 135 container->width_fraction = container->height_fraction = 0;
137 workspace_squash(destination->workspace); 136 workspace_squash(destination->pending.workspace);
138 } 137 }
139 return; 138 return;
140 } 139 }
141 140
142 if (is_parallel(destination->layout, move_dir)) { 141 if (is_parallel(destination->pending.layout, move_dir)) {
143 sway_log(SWAY_DEBUG, "Reparenting container (parallel)"); 142 sway_log(SWAY_DEBUG, "Reparenting container (parallel)");
144 int index = 143 int index =
145 move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ? 144 move_dir == WLR_DIRECTION_RIGHT || move_dir == WLR_DIRECTION_DOWN ?
146 0 : destination->children->length; 145 0 : destination->pending.children->length;
147 container_insert_child(destination, container, index); 146 container_insert_child(destination, container, index);
148 container->width = container->height = 0; 147 container->pending.width = container->pending.height = 0;
149 container->width_fraction = container->height_fraction = 0; 148 container->width_fraction = container->height_fraction = 0;
150 workspace_squash(destination->workspace); 149 workspace_squash(destination->pending.workspace);
151 return; 150 return;
152 } 151 }
153 152
@@ -168,7 +167,7 @@ static void container_move_to_container_from_direction(
168static void container_move_to_workspace_from_direction( 167static void container_move_to_workspace_from_direction(
169 struct sway_container *container, struct sway_workspace *workspace, 168 struct sway_container *container, struct sway_workspace *workspace,
170 enum wlr_direction move_dir) { 169 enum wlr_direction move_dir) {
171 container->width = container->height = 0; 170 container->pending.width = container->pending.height = 0;
172 container->width_fraction = container->height_fraction = 0; 171 container->width_fraction = container->height_fraction = 0;
173 172
174 if (is_parallel(workspace->layout, move_dir)) { 173 if (is_parallel(workspace->layout, move_dir)) {
@@ -188,8 +187,8 @@ static void container_move_to_workspace_from_direction(
188 workspace_add_tiling(workspace, container); 187 workspace_add_tiling(workspace, container);
189 return; 188 return;
190 } 189 }
191 while (focus_inactive->parent) { 190 while (focus_inactive->pending.parent) {
192 focus_inactive = focus_inactive->parent; 191 focus_inactive = focus_inactive->pending.parent;
193 } 192 }
194 container_move_to_container_from_direction(container, focus_inactive, 193 container_move_to_container_from_direction(container, focus_inactive,
195 move_dir); 194 move_dir);
@@ -197,25 +196,33 @@ static void container_move_to_workspace_from_direction(
197 196
198static void container_move_to_workspace(struct sway_container *container, 197static void container_move_to_workspace(struct sway_container *container,
199 struct sway_workspace *workspace) { 198 struct sway_workspace *workspace) {
200 if (container->workspace == workspace) { 199 if (container->pending.workspace == workspace) {
201 return; 200 return;
202 } 201 }
203 struct sway_workspace *old_workspace = container->workspace; 202 struct sway_workspace *old_workspace = container->pending.workspace;
204 if (container_is_floating(container)) { 203 if (container_is_floating(container)) {
205 struct sway_output *old_output = container->workspace->output; 204 struct sway_output *old_output = container->pending.workspace->output;
206 container_detach(container); 205 container_detach(container);
207 workspace_add_floating(workspace, container); 206 workspace_add_floating(workspace, container);
208 container_handle_fullscreen_reparent(container); 207 container_handle_fullscreen_reparent(container);
209 // If changing output, center it within the workspace 208 // If changing output, adjust the coordinates of the window.
210 if (old_output != workspace->output && !container->fullscreen_mode) { 209 if (old_output != workspace->output && !container->pending.fullscreen_mode) {
211 container_floating_move_to_center(container); 210 struct wlr_box workspace_box, old_workspace_box;
211 workspace_get_box(workspace, &workspace_box);
212 workspace_get_box(old_workspace, &old_workspace_box);
213 floating_fix_coordinates(container, &old_workspace_box, &workspace_box);
214 if (container->scratchpad && workspace->output) {
215 struct wlr_box output_box;
216 output_get_box(workspace->output, &output_box);
217 container->transform = workspace_box;
218 }
212 } 219 }
213 } else { 220 } else {
214 container_detach(container); 221 container_detach(container);
215 if (workspace_is_empty(workspace) && container->children) { 222 if (workspace_is_empty(workspace) && container->pending.children) {
216 workspace_unwrap_children(workspace, container); 223 workspace_unwrap_children(workspace, container);
217 } else { 224 } else {
218 container->width = container->height = 0; 225 container->pending.width = container->pending.height = 0;
219 container->width_fraction = container->height_fraction = 0; 226 container->width_fraction = container->height_fraction = 0;
220 workspace_add_tiling(workspace, container); 227 workspace_add_tiling(workspace, container);
221 } 228 }
@@ -237,13 +244,13 @@ static void container_move_to_container(struct sway_container *container,
237 return; 244 return;
238 } 245 }
239 if (container_is_floating(container)) { 246 if (container_is_floating(container)) {
240 container_move_to_workspace(container, destination->workspace); 247 container_move_to_workspace(container, destination->pending.workspace);
241 return; 248 return;
242 } 249 }
243 struct sway_workspace *old_workspace = container->workspace; 250 struct sway_workspace *old_workspace = container->pending.workspace;
244 251
245 container_detach(container); 252 container_detach(container);
246 container->width = container->height = 0; 253 container->pending.width = container->pending.height = 0;
247 container->width_fraction = container->height_fraction = 0; 254 container->width_fraction = container->height_fraction = 0;
248 255
249 if (destination->view) { 256 if (destination->view) {
@@ -256,12 +263,12 @@ static void container_move_to_container(struct sway_container *container,
256 ipc_event_window(container, "move"); 263 ipc_event_window(container, "move");
257 } 264 }
258 265
259 if (destination->workspace) { 266 if (destination->pending.workspace) {
260 workspace_focus_fullscreen(destination->workspace); 267 workspace_focus_fullscreen(destination->pending.workspace);
261 workspace_detect_urgent(destination->workspace); 268 workspace_detect_urgent(destination->pending.workspace);
262 } 269 }
263 270
264 if (old_workspace && old_workspace != destination->workspace) { 271 if (old_workspace && old_workspace != destination->pending.workspace) {
265 workspace_detect_urgent(old_workspace); 272 workspace_detect_urgent(old_workspace);
266 } 273 }
267} 274}
@@ -275,7 +282,7 @@ static bool container_move_to_next_output(struct sway_container *container,
275 if (!sway_assert(ws, "Expected output to have a workspace")) { 282 if (!sway_assert(ws, "Expected output to have a workspace")) {
276 return false; 283 return false;
277 } 284 }
278 switch (container->fullscreen_mode) { 285 switch (container->pending.fullscreen_mode) {
279 case FULLSCREEN_NONE: 286 case FULLSCREEN_NONE:
280 container_move_to_workspace_from_direction(container, ws, move_dir); 287 container_move_to_workspace_from_direction(container, ws, move_dir);
281 return true; 288 return true;
@@ -293,12 +300,12 @@ static bool container_move_to_next_output(struct sway_container *container,
293static bool container_move_in_direction(struct sway_container *container, 300static bool container_move_in_direction(struct sway_container *container,
294 enum wlr_direction move_dir) { 301 enum wlr_direction move_dir) {
295 // If moving a fullscreen view, only consider outputs 302 // If moving a fullscreen view, only consider outputs
296 switch (container->fullscreen_mode) { 303 switch (container->pending.fullscreen_mode) {
297 case FULLSCREEN_NONE: 304 case FULLSCREEN_NONE:
298 break; 305 break;
299 case FULLSCREEN_WORKSPACE: 306 case FULLSCREEN_WORKSPACE:
300 return container_move_to_next_output(container, 307 return container_move_to_next_output(container,
301 container->workspace->output, move_dir); 308 container->pending.workspace->output, move_dir);
302 case FULLSCREEN_GLOBAL: 309 case FULLSCREEN_GLOBAL:
303 return false; 310 return false;
304 } 311 }
@@ -317,26 +324,26 @@ static bool container_move_in_direction(struct sway_container *container,
317 while (!ancestor) { 324 while (!ancestor) {
318 // Don't allow containers to move out of their 325 // Don't allow containers to move out of their
319 // fullscreen or floating parent 326 // fullscreen or floating parent
320 if (current->fullscreen_mode || container_is_floating(current)) { 327 if (current->pending.fullscreen_mode || container_is_floating(current)) {
321 return false; 328 return false;
322 } 329 }
323 330
324 enum sway_container_layout parent_layout = container_parent_layout(current); 331 enum sway_container_layout parent_layout = container_parent_layout(current);
325 if (!is_parallel(parent_layout, move_dir)) { 332 if (!is_parallel(parent_layout, move_dir)) {
326 if (!current->parent) { 333 if (!current->pending.parent) {
327 // No parallel parent, so we reorient the workspace 334 // No parallel parent, so we reorient the workspace
328 current = workspace_wrap_children(current->workspace); 335 current = workspace_wrap_children(current->pending.workspace);
329 current->workspace->layout = 336 current->pending.workspace->layout =
330 move_dir == WLR_DIRECTION_LEFT || 337 move_dir == WLR_DIRECTION_LEFT ||
331 move_dir == WLR_DIRECTION_RIGHT ? 338 move_dir == WLR_DIRECTION_RIGHT ?
332 L_HORIZ : L_VERT; 339 L_HORIZ : L_VERT;
333 container->height = container->width = 0; 340 container->pending.height = container->pending.width = 0;
334 container->height_fraction = container->width_fraction = 0; 341 container->height_fraction = container->width_fraction = 0;
335 workspace_update_representation(current->workspace); 342 workspace_update_representation(current->pending.workspace);
336 wrapped = true; 343 wrapped = true;
337 } else { 344 } else {
338 // Keep looking for a parallel parent 345 // Keep looking for a parallel parent
339 current = current->parent; 346 current = current->pending.parent;
340 } 347 }
341 continue; 348 continue;
342 } 349 }
@@ -356,14 +363,14 @@ static bool container_move_in_direction(struct sway_container *container,
356 container_move_to_container_from_direction(container, 363 container_move_to_container_from_direction(container,
357 target, move_dir); 364 target, move_dir);
358 return true; 365 return true;
359 } else if (!container->parent) { 366 } else if (!container->pending.parent) {
360 // Container is at workspace level so we move it to the 367 // Container is at workspace level so we move it to the
361 // next workspace if possible 368 // next workspace if possible
362 return container_move_to_next_output(container, 369 return container_move_to_next_output(container,
363 current->workspace->output, move_dir); 370 current->pending.workspace->output, move_dir);
364 } else { 371 } else {
365 // Container has escaped its immediate parallel parent 372 // Container has escaped its immediate parallel parent
366 current = current->parent; 373 current = current->pending.parent;
367 continue; 374 continue;
368 } 375 }
369 } 376 }
@@ -377,31 +384,31 @@ static bool container_move_in_direction(struct sway_container *container,
377 container_move_to_container_from_direction(container, 384 container_move_to_container_from_direction(container,
378 target, move_dir); 385 target, move_dir);
379 return true; 386 return true;
380 } else if (!wrapped && !container->parent->parent && 387 } else if (!wrapped && !container->pending.parent->pending.parent &&
381 container->parent->children->length == 1) { 388 container->pending.parent->pending.children->length == 1) {
382 // Treat singleton children as if they are at workspace level like i3 389 // Treat singleton children as if they are at workspace level like i3
383 // https://github.com/i3/i3/blob/1d9160f2d247dbaa83fb62f02fd7041dec767fc2/src/move.c#L367 390 // https://github.com/i3/i3/blob/1d9160f2d247dbaa83fb62f02fd7041dec767fc2/src/move.c#L367
384 return container_move_to_next_output(container, 391 return container_move_to_next_output(container,
385 ancestor->workspace->output, move_dir); 392 ancestor->pending.workspace->output, move_dir);
386 } else { 393 } else {
387 // Container will be promoted 394 // Container will be promoted
388 struct sway_container *old_parent = container->parent; 395 struct sway_container *old_parent = container->pending.parent;
389 if (ancestor->parent) { 396 if (ancestor->pending.parent) {
390 // Container will move in with its parent 397 // Container will move in with its parent
391 container_insert_child(ancestor->parent, container, 398 container_insert_child(ancestor->pending.parent, container,
392 index + (offs < 0 ? 0 : 1)); 399 index + (offs < 0 ? 0 : 1));
393 } else { 400 } else {
394 // Container will move to workspace level, 401 // Container will move to workspace level,
395 // may be re-split by workspace_layout 402 // may be re-split by workspace_layout
396 workspace_insert_tiling(ancestor->workspace, container, 403 workspace_insert_tiling(ancestor->pending.workspace, container,
397 index + (offs < 0 ? 0 : 1)); 404 index + (offs < 0 ? 0 : 1));
398 } 405 }
399 ancestor->height = ancestor->width = 0; 406 ancestor->pending.height = ancestor->pending.width = 0;
400 ancestor->height_fraction = ancestor->width_fraction = 0; 407 ancestor->height_fraction = ancestor->width_fraction = 0;
401 if (old_parent) { 408 if (old_parent) {
402 container_reap_empty(old_parent); 409 container_reap_empty(old_parent);
403 } 410 }
404 workspace_squash(container->workspace); 411 workspace_squash(container->pending.workspace);
405 return true; 412 return true;
406 } 413 }
407} 414}
@@ -427,14 +434,14 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
427 container = workspace_wrap_children(workspace); 434 container = workspace_wrap_children(workspace);
428 } 435 }
429 436
430 if (container->fullscreen_mode == FULLSCREEN_GLOBAL) { 437 if (container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
431 return cmd_results_new(CMD_FAILURE, 438 return cmd_results_new(CMD_FAILURE,
432 "Can't move fullscreen global container"); 439 "Can't move fullscreen global container");
433 } 440 }
434 441
435 struct sway_seat *seat = config->handler_context.seat; 442 struct sway_seat *seat = config->handler_context.seat;
436 struct sway_container *old_parent = container->parent; 443 struct sway_container *old_parent = container->pending.parent;
437 struct sway_workspace *old_ws = container->workspace; 444 struct sway_workspace *old_ws = container->pending.workspace;
438 struct sway_output *old_output = old_ws ? old_ws->output : NULL; 445 struct sway_output *old_output = old_ws ? old_ws->output : NULL;
439 struct sway_node *destination = NULL; 446 struct sway_node *destination = NULL;
440 447
@@ -462,7 +469,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
462 if (strcasecmp(argv[1], "number") == 0) { 469 if (strcasecmp(argv[1], "number") == 0) {
463 // move [window|container] [to] "workspace number x" 470 // move [window|container] [to] "workspace number x"
464 if (argc < 3) { 471 if (argc < 3) {
465 return cmd_results_new(CMD_INVALID, expected_syntax); 472 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
466 } 473 }
467 if (!isdigit(argv[2][0])) { 474 if (!isdigit(argv[2][0])) {
468 return cmd_results_new(CMD_INVALID, 475 return cmd_results_new(CMD_INVALID,
@@ -508,7 +515,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
508 destination = dst ? &dst->node : &ws->node; 515 destination = dst ? &dst->node : &ws->node;
509 } else if (strcasecmp(argv[0], "output") == 0) { 516 } else if (strcasecmp(argv[0], "output") == 0) {
510 struct sway_output *new_output = output_in_direction(argv[1], 517 struct sway_output *new_output = output_in_direction(argv[1],
511 old_output, container->x, container->y); 518 old_output, container->pending.x, container->pending.y);
512 if (!new_output) { 519 if (!new_output) {
513 return cmd_results_new(CMD_FAILURE, 520 return cmd_results_new(CMD_FAILURE,
514 "Can't find output with name/direction '%s'", argv[1]); 521 "Can't find output with name/direction '%s'", argv[1]);
@@ -522,7 +529,7 @@ static struct cmd_results *cmd_move_container(bool no_auto_back_and_forth,
522 } 529 }
523 destination = &dest_con->node; 530 destination = &dest_con->node;
524 } else { 531 } else {
525 return cmd_results_new(CMD_INVALID, expected_syntax); 532 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
526 } 533 }
527 534
528 if (destination->type == N_CONTAINER && 535 if (destination->type == N_CONTAINER &&
@@ -686,6 +693,9 @@ static struct cmd_results *cmd_move_workspace(int argc, char **argv) {
686 arrange_output(old_output); 693 arrange_output(old_output);
687 arrange_output(new_output); 694 arrange_output(new_output);
688 695
696 struct sway_seat *seat = config->handler_context.seat;
697 seat_consider_warp_to_focus(seat);
698
689 return cmd_results_new(CMD_SUCCESS, NULL); 699 return cmd_results_new(CMD_SUCCESS, NULL);
690} 700}
691 701
@@ -706,12 +716,12 @@ static struct cmd_results *cmd_move_in_direction(
706 "Cannot move workspaces in a direction"); 716 "Cannot move workspaces in a direction");
707 } 717 }
708 if (container_is_floating(container)) { 718 if (container_is_floating(container)) {
709 if (container->fullscreen_mode) { 719 if (container->pending.fullscreen_mode) {
710 return cmd_results_new(CMD_FAILURE, 720 return cmd_results_new(CMD_FAILURE,
711 "Cannot move fullscreen floating container"); 721 "Cannot move fullscreen floating container");
712 } 722 }
713 double lx = container->x; 723 double lx = container->pending.x;
714 double ly = container->y; 724 double ly = container->pending.y;
715 switch (direction) { 725 switch (direction) {
716 case WLR_DIRECTION_LEFT: 726 case WLR_DIRECTION_LEFT:
717 lx -= move_amt; 727 lx -= move_amt;
@@ -729,8 +739,8 @@ static struct cmd_results *cmd_move_in_direction(
729 container_floating_move_to(container, lx, ly); 739 container_floating_move_to(container, lx, ly);
730 return cmd_results_new(CMD_SUCCESS, NULL); 740 return cmd_results_new(CMD_SUCCESS, NULL);
731 } 741 }
732 struct sway_workspace *old_ws = container->workspace; 742 struct sway_workspace *old_ws = container->pending.workspace;
733 struct sway_container *old_parent = container->parent; 743 struct sway_container *old_parent = container->pending.parent;
734 744
735 if (!container_move_in_direction(container, direction)) { 745 if (!container_move_in_direction(container, direction)) {
736 // Container didn't move 746 // Container didn't move
@@ -744,7 +754,7 @@ static struct cmd_results *cmd_move_in_direction(
744 workspace_consider_destroy(old_ws); 754 workspace_consider_destroy(old_ws);
745 } 755 }
746 756
747 struct sway_workspace *new_ws = container->workspace; 757 struct sway_workspace *new_ws = container->pending.workspace;
748 758
749 if (root->fullscreen_global) { 759 if (root->fullscreen_global) {
750 arrange_root(); 760 arrange_root();
@@ -759,15 +769,6 @@ static struct cmd_results *cmd_move_in_direction(
759 ipc_event_window(container, "move"); 769 ipc_event_window(container, "move");
760 } 770 }
761 771
762 // Hack to re-focus container
763 seat_set_raw_focus(config->handler_context.seat, &new_ws->node);
764 seat_set_focus_container(config->handler_context.seat, container);
765
766 if (old_ws != new_ws) {
767 ipc_event_workspace(old_ws, new_ws, "focus");
768 workspace_detect_urgent(old_ws);
769 workspace_detect_urgent(new_ws);
770 }
771 container_end_mouse_operation(container); 772 container_end_mouse_operation(container);
772 773
773 return cmd_results_new(CMD_SUCCESS, NULL); 774 return cmd_results_new(CMD_SUCCESS, NULL);
@@ -781,22 +782,22 @@ static struct cmd_results *cmd_move_to_position_pointer(
781 } 782 }
782 struct wlr_cursor *cursor = seat->cursor->cursor; 783 struct wlr_cursor *cursor = seat->cursor->cursor;
783 /* Determine where to put the window. */ 784 /* Determine where to put the window. */
784 double lx = cursor->x - container->width / 2; 785 double lx = cursor->x - container->pending.width / 2;
785 double ly = cursor->y - container->height / 2; 786 double ly = cursor->y - container->pending.height / 2;
786 787
787 /* Correct target coordinates to be in bounds (on screen). */ 788 /* Correct target coordinates to be in bounds (on screen). */
788 struct wlr_output *output = wlr_output_layout_output_at( 789 struct wlr_output *output = wlr_output_layout_output_at(
789 root->output_layout, cursor->x, cursor->y); 790 root->output_layout, cursor->x, cursor->y);
790 if (output) { 791 if (output) {
791 struct wlr_box *box = 792 struct wlr_box box;
792 wlr_output_layout_get_box(root->output_layout, output); 793 wlr_output_layout_get_box(root->output_layout, output, &box);
793 lx = fmax(lx, box->x); 794 lx = fmax(lx, box.x);
794 ly = fmax(ly, box->y); 795 ly = fmax(ly, box.y);
795 if (lx + container->width > box->x + box->width) { 796 if (lx + container->pending.width > box.x + box.width) {
796 lx = box->x + box->width - container->width; 797 lx = box.x + box.width - container->pending.width;
797 } 798 }
798 if (ly + container->height > box->y + box->height) { 799 if (ly + container->pending.height > box.y + box.height) {
799 ly = box->y + box->height - container->height; 800 ly = box.y + box.height - container->pending.height;
800 } 801 }
801 } 802 }
802 803
@@ -818,7 +819,7 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
818 } 819 }
819 820
820 if (!argc) { 821 if (!argc) {
821 return cmd_results_new(CMD_INVALID, expected_position_syntax); 822 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
822 } 823 }
823 824
824 bool absolute = false; 825 bool absolute = false;
@@ -828,41 +829,41 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
828 ++argv; 829 ++argv;
829 } 830 }
830 if (!argc) { 831 if (!argc) {
831 return cmd_results_new(CMD_INVALID, expected_position_syntax); 832 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
832 } 833 }
833 if (strcmp(argv[0], "position") == 0) { 834 if (strcmp(argv[0], "position") == 0) {
834 --argc; 835 --argc;
835 ++argv; 836 ++argv;
836 } 837 }
837 if (!argc) { 838 if (!argc) {
838 return cmd_results_new(CMD_INVALID, expected_position_syntax); 839 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
839 } 840 }
840 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 || 841 if (strcmp(argv[0], "cursor") == 0 || strcmp(argv[0], "mouse") == 0 ||
841 strcmp(argv[0], "pointer") == 0) { 842 strcmp(argv[0], "pointer") == 0) {
842 if (absolute) { 843 if (absolute) {
843 return cmd_results_new(CMD_INVALID, expected_position_syntax); 844 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
844 } 845 }
845 return cmd_move_to_position_pointer(container); 846 return cmd_move_to_position_pointer(container);
846 } else if (strcmp(argv[0], "center") == 0) { 847 } else if (strcmp(argv[0], "center") == 0) {
847 double lx, ly; 848 double lx, ly;
848 if (absolute) { 849 if (absolute) {
849 lx = root->x + (root->width - container->width) / 2; 850 lx = root->x + (root->width - container->pending.width) / 2;
850 ly = root->y + (root->height - container->height) / 2; 851 ly = root->y + (root->height - container->pending.height) / 2;
851 } else { 852 } else {
852 struct sway_workspace *ws = container->workspace; 853 struct sway_workspace *ws = container->pending.workspace;
853 if (!ws) { 854 if (!ws) {
854 struct sway_seat *seat = config->handler_context.seat; 855 struct sway_seat *seat = config->handler_context.seat;
855 ws = seat_get_focused_workspace(seat); 856 ws = seat_get_focused_workspace(seat);
856 } 857 }
857 lx = ws->x + (ws->width - container->width) / 2; 858 lx = ws->x + (ws->width - container->pending.width) / 2;
858 ly = ws->y + (ws->height - container->height) / 2; 859 ly = ws->y + (ws->height - container->pending.height) / 2;
859 } 860 }
860 container_floating_move_to(container, lx, ly); 861 container_floating_move_to(container, lx, ly);
861 return cmd_results_new(CMD_SUCCESS, NULL); 862 return cmd_results_new(CMD_SUCCESS, NULL);
862 } 863 }
863 864
864 if (argc < 2) { 865 if (argc < 2) {
865 return cmd_results_new(CMD_FAILURE, expected_position_syntax); 866 return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax);
866 } 867 }
867 868
868 struct movement_amount lx = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; 869 struct movement_amount lx = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID };
@@ -874,19 +875,23 @@ static struct cmd_results *cmd_move_to_position(int argc, char **argv) {
874 return cmd_results_new(CMD_INVALID, "Invalid x position specified"); 875 return cmd_results_new(CMD_INVALID, "Invalid x position specified");
875 } 876 }
876 877
878 if (argc < 1) {
879 return cmd_results_new(CMD_FAILURE, "%s", expected_position_syntax);
880 }
881
877 struct movement_amount ly = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID }; 882 struct movement_amount ly = { .amount = 0, .unit = MOVEMENT_UNIT_INVALID };
878 // Y direction 883 // Y direction
879 num_consumed_args = parse_movement_amount(argc, argv, &ly); 884 num_consumed_args = parse_movement_amount(argc, argv, &ly);
880 argc -= num_consumed_args; 885 argc -= num_consumed_args;
881 argv += num_consumed_args; 886 argv += num_consumed_args;
882 if (argc > 0) { 887 if (argc > 0) {
883 return cmd_results_new(CMD_INVALID, expected_position_syntax); 888 return cmd_results_new(CMD_INVALID, "%s", expected_position_syntax);
884 } 889 }
885 if (ly.unit == MOVEMENT_UNIT_INVALID) { 890 if (ly.unit == MOVEMENT_UNIT_INVALID) {
886 return cmd_results_new(CMD_INVALID, "Invalid y position specified"); 891 return cmd_results_new(CMD_INVALID, "Invalid y position specified");
887 } 892 }
888 893
889 struct sway_workspace *ws = container->workspace; 894 struct sway_workspace *ws = container->pending.workspace;
890 if (!ws) { 895 if (!ws) {
891 struct sway_seat *seat = config->handler_context.seat; 896 struct sway_seat *seat = config->handler_context.seat;
892 ws = seat_get_focused_workspace(seat); 897 ws = seat_get_focused_workspace(seat);
@@ -960,14 +965,14 @@ static struct cmd_results *cmd_move_to_scratchpad(void) {
960 // If the container is in a floating split container, 965 // If the container is in a floating split container,
961 // operate on the split container instead of the child. 966 // operate on the split container instead of the child.
962 if (container_is_floating_or_child(con)) { 967 if (container_is_floating_or_child(con)) {
963 while (con->parent) { 968 while (con->pending.parent) {
964 con = con->parent; 969 con = con->pending.parent;
965 } 970 }
966 } 971 }
967 972
968 if (!con->scratchpad) { 973 if (!con->scratchpad) {
969 root_scratchpad_add_container(con, NULL); 974 root_scratchpad_add_container(con, NULL);
970 } else if (con->workspace) { 975 } else if (con->pending.workspace) {
971 root_scratchpad_hide(con); 976 root_scratchpad_hide(con);
972 } 977 }
973 return cmd_results_new(CMD_SUCCESS, NULL); 978 return cmd_results_new(CMD_SUCCESS, NULL);
@@ -1026,13 +1031,13 @@ struct cmd_results *cmd_move(int argc, char **argv) {
1026 } 1031 }
1027 1032
1028 if (!argc) { 1033 if (!argc) {
1029 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1034 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1030 } 1035 }
1031 1036
1032 // Only `move [window|container] [to] workspace` supports 1037 // Only `move [window|container] [to] workspace` supports
1033 // `--no-auto-back-and-forth` so treat others as invalid syntax 1038 // `--no-auto-back-and-forth` so treat others as invalid syntax
1034 if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) { 1039 if (no_auto_back_and_forth && strcasecmp(argv[0], "workspace") != 0) {
1035 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1040 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1036 } 1041 }
1037 1042
1038 if (strcasecmp(argv[0], "workspace") == 0 || 1043 if (strcasecmp(argv[0], "workspace") == 0 ||
@@ -1046,5 +1051,5 @@ struct cmd_results *cmd_move(int argc, char **argv) {
1046 strcasecmp(argv[1], "position") == 0)) { 1051 strcasecmp(argv[1], "position") == 0)) {
1047 return cmd_move_to_position(argc, argv); 1052 return cmd_move_to_position(argc, argv);
1048 } 1053 }
1049 return cmd_results_new(CMD_INVALID, expected_full_syntax); 1054 return cmd_results_new(CMD_INVALID, "%s", expected_full_syntax);
1050} 1055}
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..5e5d31b3 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 }
@@ -99,15 +103,18 @@ struct cmd_results *cmd_output(int argc, char **argv) {
99 103
100 bool background = output->background; 104 bool background = output->background;
101 105
102 output = store_output_config(output); 106 store_output_config(output);
103 107
104 // If reloading, the output configs will be applied after reading the 108 // If reloading, the output configs will be applied after reading the
105 // entire config and before the deferred commands so that an auto generated 109 // entire config and before the deferred commands so that an auto generated
106 // workspace name is not given to re-enabled outputs. 110 // workspace name is not given to re-enabled outputs.
107 if (!config->reloading && !config->validating) { 111 if (!config->reloading && !config->validating) {
108 apply_output_config_to_outputs(output); 112 apply_all_output_configs();
109 if (background) { 113 if (background) {
110 spawn_swaybg(); 114 if (!spawn_swaybg()) {
115 return cmd_results_new(CMD_FAILURE,
116 "Failed to apply background configuration");
117 }
111 } 118 }
112 } 119 }
113 120
diff --git a/sway/commands/output/background.c b/sway/commands/output/background.c
index 68ee9fe1..55bd7671 100644
--- a/sway/commands/output/background.c
+++ b/sway/commands/output/background.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <libgen.h> 1#include <libgen.h>
3#include <stdio.h> 2#include <stdio.h>
4#include <string.h> 3#include <string.h>
@@ -102,19 +101,19 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
102 } 101 }
103 102
104 char *conf_path = dirname(conf); 103 char *conf_path = dirname(conf);
105 char *rel_path = src; 104 char *real_src = malloc(strlen(conf_path) + strlen(src) + 2);
106 src = malloc(strlen(conf_path) + strlen(src) + 2); 105 if (!real_src) {
107 if (!src) { 106 free(src);
108 free(rel_path);
109 free(conf); 107 free(conf);
110 sway_log(SWAY_ERROR, "Unable to allocate memory"); 108 sway_log(SWAY_ERROR, "Unable to allocate memory");
111 return cmd_results_new(CMD_FAILURE, 109 return cmd_results_new(CMD_FAILURE,
112 "Unable to allocate resources"); 110 "Unable to allocate resources");
113 } 111 }
114 112
115 sprintf(src, "%s/%s", conf_path, rel_path); 113 snprintf(real_src, strlen(conf_path) + strlen(src) + 2, "%s/%s", conf_path, src);
116 free(rel_path); 114 free(src);
117 free(conf); 115 free(conf);
116 src = real_src;
118 } 117 }
119 118
120 bool can_access = access(src, F_OK) != -1; 119 bool can_access = access(src, F_OK) != -1;
@@ -123,7 +122,10 @@ struct cmd_results *output_cmd_background(int argc, char **argv) {
123 src); 122 src);
124 config_add_swaynag_warning("Unable to access background file '%s'", 123 config_add_swaynag_warning("Unable to access background file '%s'",
125 src); 124 src);
125 struct cmd_results *result = cmd_results_new(CMD_FAILURE,
126 "unable to access background file '%s'", src);
126 free(src); 127 free(src);
128 return result;
127 } else { 129 } else {
128 output->background = src; 130 output->background = src;
129 output->background_option = strdup(mode); 131 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/toggle.c b/sway/commands/output/toggle.c
index 6342d526..c6b72845 100644
--- a/sway/commands/output/toggle.c
+++ b/sway/commands/output/toggle.c
@@ -29,7 +29,7 @@ struct cmd_results *output_cmd_toggle(int argc, char **argv) {
29 config->handler_context.output_config->enabled = 1; 29 config->handler_context.output_config->enabled = 1;
30 } 30 }
31 31
32 free(oc); 32 free_output_config(oc);
33 config->handler_context.leftovers.argc = argc; 33 config->handler_context.leftovers.argc = argc;
34 config->handler_context.leftovers.argv = argv; 34 config->handler_context.leftovers.argv = argv;
35 return NULL; 35 return NULL;
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..9e2689c2
--- /dev/null
+++ b/sway/commands/primary_selection.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 "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 // config->primary_selection is reset to the previous value on reload in
16 // load_main_config()
17 if (config->reloading && config->primary_selection != primary_selection) {
18 return cmd_results_new(CMD_FAILURE,
19 "primary_selection can only be enabled/disabled at launch");
20 }
21
22 config->primary_selection = primary_selection;
23
24 return cmd_results_new(CMD_SUCCESS, NULL);
25}
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index 3c994d54..6c0aac26 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -9,9 +8,8 @@
9#include "list.h" 8#include "list.h"
10#include "log.h" 9#include "log.h"
11 10
12static void rebuild_textures_iterator(struct sway_container *con, void *data) { 11static void title_bar_update_iterator(struct sway_container *con, void *data) {
13 container_update_marks_textures(con); 12 container_update_title_bar(con);
14 container_update_title_textures(con);
15} 13}
16 14
17static void do_reload(void *data) { 15static void do_reload(void *data) {
@@ -48,8 +46,7 @@ static void do_reload(void *data) {
48 } 46 }
49 list_free_items_and_destroy(bar_ids); 47 list_free_items_and_destroy(bar_ids);
50 48
51 config_update_font_height(true); 49 root_for_each_container(title_bar_update_iterator, NULL);
52 root_for_each_container(rebuild_textures_iterator, NULL);
53 50
54 arrange_root(); 51 arrange_root();
55} 52}
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..47d18546 100644
--- a/sway/commands/seat/attach.c
+++ b/sway/commands/seat/attach.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -12,7 +11,7 @@ struct cmd_results *seat_cmd_attach(int argc, char **argv) {
12 if (!config->handler_context.seat_config) { 11 if (!config->handler_context.seat_config) {
13 return cmd_results_new(CMD_FAILURE, "No seat defined"); 12 return cmd_results_new(CMD_FAILURE, "No seat defined");
14 } 13 }
15 if (config->reading) { 14 if (!config->active) {
16 return cmd_results_new(CMD_DEFER, NULL); 15 return cmd_results_new(CMD_DEFER, NULL);
17 } 16 }
18 17
diff --git a/sway/commands/seat/cursor.c b/sway/commands/seat/cursor.c
index 749235eb..df7c379d 100644
--- a/sway/commands/seat/cursor.c
+++ b/sway/commands/seat/cursor.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <linux/input-event-codes.h> 1#include <linux/input-event-codes.h>
3 2
4#include <strings.h> 3#include <strings.h>
@@ -18,7 +17,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
18 int argc, char **argv) { 17 int argc, char **argv) {
19 if (strcasecmp(argv[0], "move") == 0) { 18 if (strcasecmp(argv[0], "move") == 0) {
20 if (argc < 3) { 19 if (argc < 3) {
21 return cmd_results_new(CMD_INVALID, expected_syntax); 20 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
22 } 21 }
23 int delta_x = strtol(argv[1], NULL, 10); 22 int delta_x = strtol(argv[1], NULL, 10);
24 int delta_y = strtol(argv[2], NULL, 10); 23 int delta_y = strtol(argv[2], NULL, 10);
@@ -27,7 +26,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
27 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 26 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
28 } else if (strcasecmp(argv[0], "set") == 0) { 27 } else if (strcasecmp(argv[0], "set") == 0) {
29 if (argc < 3) { 28 if (argc < 3) {
30 return cmd_results_new(CMD_INVALID, expected_syntax); 29 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
31 } 30 }
32 // map absolute coords (0..1,0..1) to root container coords 31 // map absolute coords (0..1,0..1) to root container coords
33 float x = strtof(argv[1], NULL) / root->width; 32 float x = strtof(argv[1], NULL) / root->width;
@@ -37,7 +36,7 @@ static struct cmd_results *handle_command(struct sway_cursor *cursor,
37 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat); 36 wlr_seat_pointer_notify_frame(cursor->seat->wlr_seat);
38 } else { 37 } else {
39 if (argc < 2) { 38 if (argc < 2) {
40 return cmd_results_new(CMD_INVALID, expected_syntax); 39 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
41 } 40 }
42 struct cmd_results *error = NULL; 41 struct cmd_results *error = NULL;
43 if ((error = press_or_release(cursor, argv[0], argv[1]))) { 42 if ((error = press_or_release(cursor, argv[0], argv[1]))) {
@@ -85,36 +84,36 @@ struct cmd_results *seat_cmd_cursor(int argc, char **argv) {
85 84
86static struct cmd_results *press_or_release(struct sway_cursor *cursor, 85static struct cmd_results *press_or_release(struct sway_cursor *cursor,
87 char *action, char *button_str) { 86 char *action, char *button_str) {
88 enum wlr_button_state state; 87 enum wl_pointer_button_state state;
89 uint32_t button; 88 uint32_t button;
90 if (strcasecmp(action, "press") == 0) { 89 if (strcasecmp(action, "press") == 0) {
91 state = WLR_BUTTON_PRESSED; 90 state = WL_POINTER_BUTTON_STATE_PRESSED;
92 } else if (strcasecmp(action, "release") == 0) { 91 } else if (strcasecmp(action, "release") == 0) {
93 state = WLR_BUTTON_RELEASED; 92 state = WL_POINTER_BUTTON_STATE_RELEASED;
94 } else { 93 } else {
95 return cmd_results_new(CMD_INVALID, expected_syntax); 94 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
96 } 95 }
97 96
98 char *message = NULL; 97 char *message = NULL;
99 button = get_mouse_button(button_str, &message); 98 button = get_mouse_button(button_str, &message);
100 if (message) { 99 if (message) {
101 struct cmd_results *error = 100 struct cmd_results *error =
102 cmd_results_new(CMD_INVALID, message); 101 cmd_results_new(CMD_INVALID, "%s", message);
103 free(message); 102 free(message);
104 return error; 103 return error;
105 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN 104 } else if (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN
106 || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) { 105 || button == SWAY_SCROLL_LEFT || button == SWAY_SCROLL_RIGHT) {
107 // Dispatch axis event 106 // Dispatch axis event
108 enum wlr_axis_orientation orientation = 107 enum wl_pointer_axis orientation =
109 (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN) 108 (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_DOWN)
110 ? WLR_AXIS_ORIENTATION_VERTICAL 109 ? WL_POINTER_AXIS_VERTICAL_SCROLL
111 : WLR_AXIS_ORIENTATION_HORIZONTAL; 110 : WL_POINTER_AXIS_HORIZONTAL_SCROLL;
112 double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT) 111 double delta = (button == SWAY_SCROLL_UP || button == SWAY_SCROLL_LEFT)
113 ? -1 : 1; 112 ? -1 : 1;
114 struct wlr_event_pointer_axis event = { 113 struct wlr_pointer_axis_event event = {
115 .device = NULL, 114 .pointer = NULL,
116 .time_msec = 0, 115 .time_msec = 0,
117 .source = WLR_AXIS_SOURCE_WHEEL, 116 .source = WL_POINTER_AXIS_SOURCE_WHEEL,
118 .orientation = orientation, 117 .orientation = orientation,
119 .delta = delta * 15, 118 .delta = delta * 15,
120 .delta_discrete = delta 119 .delta_discrete = delta
diff --git a/sway/commands/seat/hide_cursor.c b/sway/commands/seat/hide_cursor.c
index e09b82d9..f5177a47 100644
--- a/sway/commands/seat/hide_cursor.c
+++ b/sway/commands/seat/hide_cursor.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
diff --git a/sway/commands/seat/idle.c b/sway/commands/seat/idle.c
index 82428f2c..2974453e 100644
--- a/sway/commands/seat/idle.c
+++ b/sway/commands/seat/idle.c
@@ -1,8 +1,8 @@
1#define _POSIX_C_SOURCE 200809L
2#include <limits.h> 1#include <limits.h>
3#include <string.h> 2#include <string.h>
4#include <strings.h> 3#include <strings.h>
5#include <stdint.h> 4#include <stdint.h>
5#include "log.h"
6#include "sway/commands.h" 6#include "sway/commands.h"
7#include "sway/config.h" 7#include "sway/config.h"
8#include "sway/input/seat.h" 8#include "sway/input/seat.h"
@@ -69,5 +69,10 @@ struct cmd_results *seat_cmd_idle_wake(int argc, char **argv) {
69 return cmd_results_new(CMD_FAILURE, "Invalid idle source"); 69 return cmd_results_new(CMD_FAILURE, "Invalid idle source");
70 } 70 }
71 config->handler_context.seat_config->idle_wake_sources = sources; 71 config->handler_context.seat_config->idle_wake_sources = sources;
72 sway_log(SWAY_INFO, "Warning: seat idle_wake is deprecated");
73 if (config->reading) {
74 config_add_swaynag_warning("seat idle_wake is deprecated. "
75 "Only seat idle_inhibit is supported.");
76 }
72 return cmd_results_new(CMD_SUCCESS, NULL); 77 return cmd_results_new(CMD_SUCCESS, NULL);
73} 78}
diff --git a/sway/commands/seat/xcursor_theme.c b/sway/commands/seat/xcursor_theme.c
index 202f35b9..61322a57 100644
--- a/sway/commands/seat/xcursor_theme.c
+++ b/sway/commands/seat/xcursor_theme.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
diff --git a/sway/commands/set.c b/sway/commands/set.c
index c539e9fc..ba384c7c 100644
--- a/sway/commands/set.c
+++ b/sway/commands/set.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 1#include <stdio.h>
3#include <string.h> 2#include <string.h>
4#include <strings.h> 3#include <strings.h>
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
index 0d373b80..60cef9fa 100644
--- a/sway/commands/show_marks.c
+++ b/sway/commands/show_marks.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -10,8 +9,8 @@
10#include "stringop.h" 9#include "stringop.h"
11#include "util.h" 10#include "util.h"
12 11
13static void rebuild_marks_iterator(struct sway_container *con, void *data) { 12static void title_bar_update_iterator(struct sway_container *con, void *data) {
14 container_update_marks_textures(con); 13 container_update_marks(con);
15} 14}
16 15
17struct cmd_results *cmd_show_marks(int argc, char **argv) { 16struct cmd_results *cmd_show_marks(int argc, char **argv) {
@@ -23,12 +22,7 @@ struct cmd_results *cmd_show_marks(int argc, char **argv) {
23 config->show_marks = parse_boolean(argv[0], config->show_marks); 22 config->show_marks = parse_boolean(argv[0], config->show_marks);
24 23
25 if (config->show_marks) { 24 if (config->show_marks) {
26 root_for_each_container(rebuild_marks_iterator, NULL); 25 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 } 26 }
33 27
34 return cmd_results_new(CMD_SUCCESS, NULL); 28 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..e142eede 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -1,10 +1,10 @@
1#define _POSIX_C_SOURCE 200809L
2#include <strings.h> 1#include <strings.h>
3#include "config.h" 2#include "config.h"
4#include "log.h" 3#include "log.h"
5#include "sway/commands.h" 4#include "sway/commands.h"
6#include "sway/output.h" 5#include "sway/output.h"
7#include "sway/tree/arrange.h" 6#include "sway/tree/arrange.h"
7#include "sway/tree/container.h"
8#include "sway/tree/root.h" 8#include "sway/tree/root.h"
9#include "sway/tree/view.h" 9#include "sway/tree/view.h"
10#include "sway/tree/workspace.h" 10#include "sway/tree/workspace.h"
@@ -13,180 +13,6 @@
13static const char expected_syntax[] = 13static const char expected_syntax[] =
14 "Expected 'swap container with id|con_id|mark <arg>'"; 14 "Expected 'swap container with id|con_id|mark <arg>'";
15 15
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) { 16static bool test_con_id(struct sway_container *container, void *data) {
191 size_t *con_id = data; 17 size_t *con_id = data;
192 return container->node.id == *con_id; 18 return container->node.id == *con_id;
@@ -219,7 +45,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
219 } 45 }
220 46
221 if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) { 47 if (strcasecmp(argv[0], "container") || strcasecmp(argv[1], "with")) {
222 return cmd_results_new(CMD_INVALID, expected_syntax); 48 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
223 } 49 }
224 50
225 struct sway_container *current = config->handler_context.container; 51 struct sway_container *current = config->handler_context.container;
@@ -238,7 +64,7 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
238 other = root_find_container(test_mark, value); 64 other = root_find_container(test_mark, value);
239 } else { 65 } else {
240 free(value); 66 free(value);
241 return cmd_results_new(CMD_INVALID, expected_syntax); 67 return cmd_results_new(CMD_INVALID, "%s", expected_syntax);
242 } 68 }
243 69
244 if (!other) { 70 if (!other) {
@@ -247,6 +73,9 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
247 } else if (!current) { 73 } else if (!current) {
248 error = cmd_results_new(CMD_FAILURE, 74 error = cmd_results_new(CMD_FAILURE,
249 "Can only swap with containers and views"); 75 "Can only swap with containers and views");
76 } else if (current == other) {
77 error = cmd_results_new(CMD_FAILURE,
78 "Cannot swap a container with itself");
250 } else if (container_has_ancestor(current, other) 79 } else if (container_has_ancestor(current, other)
251 || container_has_ancestor(other, current)) { 80 || container_has_ancestor(other, current)) {
252 error = cmd_results_new(CMD_FAILURE, 81 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..0b2ea265 100644
--- a/sway/commands/title_format.c
+++ b/sway/commands/title_format.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -23,6 +22,5 @@ struct cmd_results *cmd_title_format(int argc, char **argv) {
23 } 22 }
24 view->title_format = format; 23 view->title_format = format;
25 view_update_title(view, true); 24 view_update_title(view, true);
26 config_update_font_height(true);
27 return cmd_results_new(CMD_SUCCESS, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL);
28} 26}
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..4aba5bae 100644
--- a/sway/commands/unmark.c
+++ b/sway/commands/unmark.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <string.h> 1#include <string.h>
3#include "sway/commands.h" 2#include "sway/commands.h"
4#include "sway/config.h" 3#include "sway/config.h"
@@ -8,9 +7,13 @@
8#include "log.h" 7#include "log.h"
9#include "stringop.h" 8#include "stringop.h"
10 9
11static void remove_all_marks_iterator(struct sway_container *con, void *data) { 10static void remove_mark(struct sway_container *con) {
12 container_clear_marks(con); 11 container_clear_marks(con);
13 container_update_marks_textures(con); 12 container_update_marks(con);
13}
14
15static void remove_all_marks_iterator(struct sway_container *con, void *data) {
16 remove_mark(con);
14} 17}
15 18
16// unmark Remove all marks from all views 19// unmark Remove all marks from all views
@@ -21,7 +24,7 @@ static void remove_all_marks_iterator(struct sway_container *con, void *data) {
21struct cmd_results *cmd_unmark(int argc, char **argv) { 24struct cmd_results *cmd_unmark(int argc, char **argv) {
22 // Determine the container 25 // Determine the container
23 struct sway_container *con = NULL; 26 struct sway_container *con = NULL;
24 if (config->handler_context.using_criteria) { 27 if (config->handler_context.node_overridden) {
25 con = config->handler_context.container; 28 con = config->handler_context.container;
26 } 29 }
27 30
@@ -38,8 +41,7 @@ struct cmd_results *cmd_unmark(int argc, char **argv) {
38 } 41 }
39 } else if (con && !mark) { 42 } else if (con && !mark) {
40 // Clear all marks from the given container 43 // Clear all marks from the given container
41 container_clear_marks(con); 44 remove_mark(con);
42 container_update_marks_textures(con);
43 } else if (!con && mark) { 45 } else if (!con && mark) {
44 // Remove mark from whichever container has it 46 // Remove mark from whichever container has it
45 container_find_and_unmark(mark); 47 container_find_and_unmark(mark);
diff --git a/sway/commands/workspace.c b/sway/commands/workspace.c
index 9ff1c97d..37a201b4 100644
--- a/sway/commands/workspace.c
+++ b/sway/commands/workspace.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <limits.h> 2#include <limits.h>
4#include <string.h> 3#include <string.h>
@@ -61,7 +60,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
61 const char expected[] = "Expected 'workspace <name> gaps " 60 const char expected[] = "Expected 'workspace <name> gaps "
62 "inner|outer|horizontal|vertical|top|right|bottom|left <px>'"; 61 "inner|outer|horizontal|vertical|top|right|bottom|left <px>'";
63 if (gaps_location == 0) { 62 if (gaps_location == 0) {
64 return cmd_results_new(CMD_INVALID, expected); 63 return cmd_results_new(CMD_INVALID, "%s", expected);
65 } 64 }
66 struct cmd_results *error = NULL; 65 struct cmd_results *error = NULL;
67 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO, 66 if ((error = checkarg(argc, "workspace", EXPECTED_EQUAL_TO,
@@ -79,7 +78,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
79 char *end; 78 char *end;
80 int amount = strtol(argv[gaps_location + 2], &end, 10); 79 int amount = strtol(argv[gaps_location + 2], &end, 10);
81 if (strlen(end)) { 80 if (strlen(end)) {
82 return cmd_results_new(CMD_FAILURE, expected); 81 return cmd_results_new(CMD_FAILURE, "%s", expected);
83 } 82 }
84 83
85 bool valid = false; 84 bool valid = false;
@@ -110,7 +109,7 @@ static struct cmd_results *cmd_workspace_gaps(int argc, char **argv,
110 } 109 }
111 } 110 }
112 if (!valid) { 111 if (!valid) {
113 return cmd_results_new(CMD_INVALID, expected); 112 return cmd_results_new(CMD_INVALID, "%s", expected);
114 } 113 }
115 114
116 // Prevent invalid gaps configurations. 115 // Prevent invalid gaps configurations.
@@ -178,25 +177,20 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
178 } 177 }
179 178
180 if (root->fullscreen_global) { 179 if (root->fullscreen_global) {
181 return cmd_results_new(CMD_FAILURE, "workspace", 180 return cmd_results_new(CMD_FAILURE,
182 "Can't switch workspaces while fullscreen global"); 181 "Can't switch workspaces while fullscreen global");
183 } 182 }
184 183
185 bool no_auto_back_and_forth = false; 184 bool auto_back_and_forth = true;
186 while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) { 185 while (strcasecmp(argv[0], "--no-auto-back-and-forth") == 0) {
187 no_auto_back_and_forth = true; 186 auto_back_and_forth = false;
188 if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) { 187 if ((error = checkarg(--argc, "workspace", EXPECTED_AT_LEAST, 1))) {
189 return error; 188 return error;
190 } 189 }
191 ++argv; 190 ++argv;
192 } 191 }
193 192
194 bool create = argc > 1 && strcasecmp(argv[1], "--create") == 0;
195 struct sway_seat *seat = config->handler_context.seat; 193 struct sway_seat *seat = config->handler_context.seat;
196 struct sway_workspace *current = seat_get_focused_workspace(seat);
197 if (!current) {
198 return cmd_results_new(CMD_FAILURE, "No workspace to switch from");
199 }
200 194
201 struct sway_workspace *ws = NULL; 195 struct sway_workspace *ws = NULL;
202 if (strcasecmp(argv[0], "number") == 0) { 196 if (strcasecmp(argv[0], "number") == 0) {
@@ -213,14 +207,15 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
213 ws = workspace_create(NULL, name); 207 ws = workspace_create(NULL, name);
214 free(name); 208 free(name);
215 } 209 }
210 if (ws && auto_back_and_forth) {
211 ws = workspace_auto_back_and_forth(ws);
212 }
216 } else if (strcasecmp(argv[0], "next") == 0 || 213 } else if (strcasecmp(argv[0], "next") == 0 ||
217 strcasecmp(argv[0], "prev") == 0 || 214 strcasecmp(argv[0], "prev") == 0 ||
215 strcasecmp(argv[0], "next_on_output") == 0 ||
216 strcasecmp(argv[0], "prev_on_output") == 0 ||
218 strcasecmp(argv[0], "current") == 0) { 217 strcasecmp(argv[0], "current") == 0) {
219 ws = workspace_by_name(argv[0]); 218 ws = workspace_by_name(argv[0]);
220 } else if (strcasecmp(argv[0], "next_on_output") == 0) {
221 ws = workspace_output_next(current, create);
222 } else if (strcasecmp(argv[0], "prev_on_output") == 0) {
223 ws = workspace_output_prev(current, create);
224 } else if (strcasecmp(argv[0], "back_and_forth") == 0) { 219 } else if (strcasecmp(argv[0], "back_and_forth") == 0) {
225 if (!seat->prev_workspace_name) { 220 if (!seat->prev_workspace_name) {
226 return cmd_results_new(CMD_INVALID, 221 return cmd_results_new(CMD_INVALID,
@@ -235,11 +230,14 @@ struct cmd_results *cmd_workspace(int argc, char **argv) {
235 ws = workspace_create(NULL, name); 230 ws = workspace_create(NULL, name);
236 } 231 }
237 free(name); 232 free(name);
233 if (ws && auto_back_and_forth) {
234 ws = workspace_auto_back_and_forth(ws);
235 }
238 } 236 }
239 if (!ws) { 237 if (!ws) {
240 return cmd_results_new(CMD_FAILURE, "No workspace to switch to"); 238 return cmd_results_new(CMD_FAILURE, "No workspace to switch to");
241 } 239 }
242 workspace_switch(ws, no_auto_back_and_forth); 240 workspace_switch(ws);
243 seat_consider_warp_to_focus(seat); 241 seat_consider_warp_to_focus(seat);
244 } 242 }
245 return cmd_results_new(CMD_SUCCESS, NULL); 243 return cmd_results_new(CMD_SUCCESS, NULL);
diff --git a/sway/commands/xwayland.c b/sway/commands/xwayland.c
index 6ca26923..584a8e3a 100644
--- a/sway/commands/xwayland.c
+++ b/sway/commands/xwayland.c
@@ -20,6 +20,8 @@ struct cmd_results *cmd_xwayland(int argc, char **argv) {
20 xwayland = XWAYLAND_MODE_DISABLED; 20 xwayland = XWAYLAND_MODE_DISABLED;
21 } 21 }
22 22
23 // config->xwayland is reset to the previous value on reload in
24 // load_main_config()
23 if (config->reloading && config->xwayland != xwayland) { 25 if (config->reloading && config->xwayland != xwayland) {
24 return cmd_results_new(CMD_FAILURE, 26 return cmd_results_new(CMD_FAILURE,
25 "xwayland can only be enabled/disabled at launch"); 27 "xwayland can only be enabled/disabled at launch");
diff --git a/sway/config.c b/sway/config.c
index 6e665434..f9131e0f 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -1,3 +1,4 @@
1#undef _POSIX_C_SOURCE
1#define _XOPEN_SOURCE 700 // for realpath 2#define _XOPEN_SOURCE 700 // for realpath
2#include <stdio.h> 3#include <stdio.h>
3#include <stdbool.h> 4#include <stdbool.h>
@@ -26,7 +27,7 @@
26#include "sway/tree/arrange.h" 27#include "sway/tree/arrange.h"
27#include "sway/tree/root.h" 28#include "sway/tree/root.h"
28#include "sway/tree/workspace.h" 29#include "sway/tree/workspace.h"
29#include "cairo.h" 30#include "cairo_util.h"
30#include "pango.h" 31#include "pango.h"
31#include "stringop.h" 32#include "stringop.h"
32#include "list.h" 33#include "list.h"
@@ -36,19 +37,26 @@
36struct sway_config *config = NULL; 37struct sway_config *config = NULL;
37 38
38static struct xkb_state *keysym_translation_state_create( 39static struct xkb_state *keysym_translation_state_create(
39 struct xkb_rule_names rules) { 40 struct xkb_rule_names rules, uint32_t context_flags) {
40 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 41 struct xkb_context *context = xkb_context_new(context_flags | XKB_CONTEXT_NO_SECURE_GETENV);
41 struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names( 42 struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_names(
42 context, 43 context,
43 &rules, 44 &rules,
44 XKB_KEYMAP_COMPILE_NO_FLAGS); 45 XKB_KEYMAP_COMPILE_NO_FLAGS);
45
46 xkb_context_unref(context); 46 xkb_context_unref(context);
47 if (xkb_keymap == NULL) {
48 sway_log(SWAY_ERROR, "Failed to compile keysym translation XKB keymap");
49 return NULL;
50 }
51
47 return xkb_state_new(xkb_keymap); 52 return xkb_state_new(xkb_keymap);
48} 53}
49 54
50static void keysym_translation_state_destroy( 55static void keysym_translation_state_destroy(
51 struct xkb_state *state) { 56 struct xkb_state *state) {
57 if (state == NULL) {
58 return;
59 }
52 xkb_keymap_unref(xkb_state_get_keymap(state)); 60 xkb_keymap_unref(xkb_state_get_keymap(state));
53 xkb_state_unref(state); 61 xkb_state_unref(state);
54} 62}
@@ -82,6 +90,12 @@ static void free_mode(struct sway_mode *mode) {
82 } 90 }
83 list_free(mode->switch_bindings); 91 list_free(mode->switch_bindings);
84 } 92 }
93 if (mode->gesture_bindings) {
94 for (int i = 0; i < mode->gesture_bindings->length; i++) {
95 free_gesture_binding(mode->gesture_bindings->items[i]);
96 }
97 list_free(mode->gesture_bindings);
98 }
85 free(mode); 99 free(mode);
86} 100}
87 101
@@ -222,6 +236,7 @@ static void config_defaults(struct sway_config *config) {
222 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; 236 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
223 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup; 237 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
224 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup; 238 if (!(config->current_mode->switch_bindings = create_list())) goto cleanup;
239 if (!(config->current_mode->gesture_bindings = create_list())) goto cleanup;
225 list_add(config->modes, config->current_mode); 240 list_add(config->modes, config->current_mode);
226 241
227 config->floating_mod = 0; 242 config->floating_mod = 0;
@@ -236,7 +251,7 @@ static void config_defaults(struct sway_config *config) {
236 config->default_layout = L_NONE; 251 config->default_layout = L_NONE;
237 config->default_orientation = L_NONE; 252 config->default_orientation = L_NONE;
238 if (!(config->font = strdup("monospace 10"))) goto cleanup; 253 if (!(config->font = strdup("monospace 10"))) goto cleanup;
239 config->font_height = 17; // height of monospace 10 254 config->font_description = pango_font_description_from_string(config->font);
240 config->urgent_timeout = 500; 255 config->urgent_timeout = 500;
241 config->focus_on_window_activation = FOWA_URGENT; 256 config->focus_on_window_activation = FOWA_URGENT;
242 config->popup_during_fullscreen = POPUP_SMART; 257 config->popup_during_fullscreen = POPUP_SMART;
@@ -266,8 +281,9 @@ static void config_defaults(struct sway_config *config) {
266 config->title_align = ALIGN_LEFT; 281 config->title_align = ALIGN_LEFT;
267 config->tiling_drag = true; 282 config->tiling_drag = true;
268 config->tiling_drag_threshold = 9; 283 config->tiling_drag_threshold = 9;
284 config->primary_selection = true;
269 285
270 config->smart_gaps = false; 286 config->smart_gaps = SMART_GAPS_OFF;
271 config->gaps_inner = 0; 287 config->gaps_inner = 0;
272 config->gaps_outer.top = 0; 288 config->gaps_outer.top = 0;
273 config->gaps_outer.right = 0; 289 config->gaps_outer.right = 0;
@@ -291,6 +307,8 @@ static void config_defaults(struct sway_config *config) {
291 config->hide_edge_borders_smart = ESMART_OFF; 307 config->hide_edge_borders_smart = ESMART_OFF;
292 config->hide_lone_tab = false; 308 config->hide_lone_tab = false;
293 309
310 config->has_focused_tab_title = false;
311
294 // border colors 312 // border colors
295 color_to_rgba(config->border_colors.focused.border, 0x4C7899FF); 313 color_to_rgba(config->border_colors.focused.border, 0x4C7899FF);
296 color_to_rgba(config->border_colors.focused.background, 0x285577FF); 314 color_to_rgba(config->border_colors.focused.background, 0x285577FF);
@@ -326,8 +344,14 @@ static void config_defaults(struct sway_config *config) {
326 344
327 // The keysym to keycode translation 345 // The keysym to keycode translation
328 struct xkb_rule_names rules = {0}; 346 struct xkb_rule_names rules = {0};
329 config->keysym_translation_state = 347 config->keysym_translation_state = keysym_translation_state_create(rules, 0);
330 keysym_translation_state_create(rules); 348 if (config->keysym_translation_state == NULL) {
349 config->keysym_translation_state = keysym_translation_state_create(rules,
350 XKB_CONTEXT_NO_ENVIRONMENT_NAMES);
351 }
352 if (config->keysym_translation_state == NULL) {
353 goto cleanup;
354 }
331 355
332 return; 356 return;
333cleanup: 357cleanup:
@@ -338,35 +362,53 @@ static bool file_exists(const char *path) {
338 return path && access(path, R_OK) != -1; 362 return path && access(path, R_OK) != -1;
339} 363}
340 364
365static char *config_path(const char *prefix, const char *config_folder) {
366 if (!prefix || !prefix[0] || !config_folder || !config_folder[0]) {
367 return NULL;
368 }
369 return format_str("%s/%s/config", prefix, config_folder);
370}
371
341static char *get_config_path(void) { 372static char *get_config_path(void) {
342 static const char *config_paths[] = { 373 char *path = NULL;
343 "$HOME/.sway/config", 374 const char *home = getenv("HOME");
344 "$XDG_CONFIG_HOME/sway/config", 375 char *config_home_fallback = NULL;
345 "$HOME/.i3/config", 376
346 "$XDG_CONFIG_HOME/i3/config", 377 const char *config_home = getenv("XDG_CONFIG_HOME");
347 SYSCONFDIR "/sway/config", 378 if ((config_home == NULL || config_home[0] == '\0') && home != NULL) {
348 SYSCONFDIR "/i3/config", 379 config_home_fallback = format_str("%s/.config", home);
380 config_home = config_home_fallback;
381 }
382
383 struct config_path {
384 const char *prefix;
385 const char *config_folder;
349 }; 386 };
350 387
351 char *config_home = getenv("XDG_CONFIG_HOME"); 388 struct config_path config_paths[] = {
352 if (!config_home || !*config_home) { 389 { .prefix = home, .config_folder = ".sway"},
353 config_paths[1] = "$HOME/.config/sway/config"; 390 { .prefix = config_home, .config_folder = "sway"},
354 config_paths[3] = "$HOME/.config/i3/config"; 391 { .prefix = home, .config_folder = ".i3"},
355 } 392 { .prefix = config_home, .config_folder = "i3"},
393 { .prefix = SYSCONFDIR, .config_folder = "sway"},
394 { .prefix = SYSCONFDIR, .config_folder = "i3"}
395 };
356 396
357 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) { 397 size_t num_config_paths = sizeof(config_paths)/sizeof(config_paths[0]);
358 wordexp_t p; 398 for (size_t i = 0; i < num_config_paths; i++) {
359 if (wordexp(config_paths[i], &p, WRDE_UNDEF) == 0) { 399 path = config_path(config_paths[i].prefix, config_paths[i].config_folder);
360 char *path = strdup(p.we_wordv[0]); 400 if (!path) {
361 wordfree(&p); 401 continue;
362 if (file_exists(path)) { 402 }
363 return path; 403 if (file_exists(path)) {
364 } 404 break;
365 free(path);
366 } 405 }
406 free(path);
407 path = NULL;
367 } 408 }
368 409
369 return NULL; 410 free(config_home_fallback);
411 return path;
370} 412}
371 413
372static bool load_config(const char *path, struct sway_config *config, 414static bool load_config(const char *path, struct sway_config *config,
@@ -438,6 +480,11 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
438 old_config->xwayland ? "enabled" : "disabled"); 480 old_config->xwayland ? "enabled" : "disabled");
439 config->xwayland = old_config->xwayland; 481 config->xwayland = old_config->xwayland;
440 482
483 // primary_selection can only be enabled/disabled at launch
484 sway_log(SWAY_DEBUG, "primary_selection will remain %s",
485 old_config->primary_selection ? "enabled" : "disabled");
486 config->primary_selection = old_config->primary_selection;
487
441 if (!config->validating) { 488 if (!config->validating) {
442 if (old_config->swaybg_client != NULL) { 489 if (old_config->swaybg_client != NULL) {
443 wl_client_destroy(old_config->swaybg_client); 490 wl_client_destroy(old_config->swaybg_client);
@@ -457,56 +504,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
457 504
458 config->reading = true; 505 config->reading = true;
459 506
460 // Read security configs 507 bool success = load_config(path, config, &config->swaynag_config_errors);
461 // TODO: Security
462 bool success = true;
463 /*
464 DIR *dir = opendir(SYSCONFDIR "/sway/security.d");
465 if (!dir) {
466 sway_log(SWAY_ERROR,
467 "%s does not exist, sway will have no security configuration"
468 " and will probably be broken", SYSCONFDIR "/sway/security.d");
469 } else {
470 list_t *secconfigs = create_list();
471 char *base = SYSCONFDIR "/sway/security.d/";
472 struct dirent *ent = readdir(dir);
473 struct stat s;
474 while (ent != NULL) {
475 char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1);
476 strcpy(_path, base);
477 strcat(_path, ent->d_name);
478 lstat(_path, &s);
479 if (S_ISREG(s.st_mode) && ent->d_name[0] != '.') {
480 list_add(secconfigs, _path);
481 }
482 else {
483 free(_path);
484 }
485 ent = readdir(dir);
486 }
487 closedir(dir);
488
489 list_qsort(secconfigs, qstrcmp);
490 for (int i = 0; i < secconfigs->length; ++i) {
491 char *_path = secconfigs->items[i];
492 if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 ||
493 (((s.st_mode & 0777) != 0644) &&
494 (s.st_mode & 0777) != 0444)) {
495 sway_log(SWAY_ERROR,
496 "Refusing to load %s - it must be owned by root "
497 "and mode 644 or 444", _path);
498 success = false;
499 } else {
500 success = success && load_config(_path, config);
501 }
502 }
503
504 list_free_items_and_destroy(secconfigs);
505 }
506 */
507
508 success = success && load_config(path, config,
509 &config->swaynag_config_errors);
510 508
511 if (validating) { 509 if (validating) {
512 free_config(config); 510 free_config(config);
@@ -514,6 +512,9 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
514 return success; 512 return success;
515 } 513 }
516 514
515 // Only really necessary if not explicitly `font` is set in the config.
516 config_update_font_height();
517
517 if (is_active && !validating) { 518 if (is_active && !validating) {
518 input_manager_verify_fallback_seat(); 519 input_manager_verify_fallback_seat();
519 520
@@ -531,7 +532,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
531 } 532 }
532 sway_switch_retrigger_bindings_for_all(); 533 sway_switch_retrigger_bindings_for_all();
533 534
534 reset_outputs(); 535 apply_all_output_configs();
535 spawn_swaybg(); 536 spawn_swaybg();
536 537
537 config->reloading = false; 538 config->reloading = false;
@@ -884,23 +885,18 @@ void config_add_swaynag_warning(char *fmt, ...) {
884 if (config->reading && !config->validating) { 885 if (config->reading && !config->validating) {
885 va_list args; 886 va_list args;
886 va_start(args, fmt); 887 va_start(args, fmt);
887 size_t length = vsnprintf(NULL, 0, fmt, args) + 1; 888 char *str = vformat_str(fmt, args);
888 va_end(args); 889 va_end(args);
889 890 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; 891 return;
894 } 892 }
895 893
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, 894 swaynag_log(config->swaynag_command, &config->swaynag_config_errors,
901 "Warning on line %i (%s) '%s': %s", 895 "Warning on line %i (%s) '%s': %s",
902 config->current_config_line_number, config->current_config_path, 896 config->current_config_line_number, config->current_config_path,
903 config->current_config_line, temp); 897 config->current_config_line, str);
898
899 free(str);
904 } 900 }
905} 901}
906 902
@@ -940,7 +936,7 @@ char *do_var_replacement(char *str) {
940 int offset = find - str; 936 int offset = find - str;
941 strncpy(newptr, str, offset); 937 strncpy(newptr, str, offset);
942 newptr += offset; 938 newptr += offset;
943 strncpy(newptr, var->value, vvlen); 939 memcpy(newptr, var->value, vvlen);
944 newptr += vvlen; 940 newptr += vvlen;
945 strcpy(newptr, find + vnlen); 941 strcpy(newptr, find + vnlen);
946 free(str); 942 free(str);
@@ -964,31 +960,11 @@ int workspace_output_cmp_workspace(const void *a, const void *b) {
964 return lenient_strcmp(wsa->workspace, wsb->workspace); 960 return lenient_strcmp(wsa->workspace, wsb->workspace);
965} 961}
966 962
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 963
985void config_update_font_height(bool recalculate) { 964void config_update_font_height(void) {
986 size_t prev_max_height = config->font_height; 965 int prev_max_height = config->font_height;
987 config->font_height = 0;
988 config->font_baseline = 0;
989 966
990 root_for_each_container(find_baseline_iterator, &recalculate); 967 get_text_metrics(config->font_description, &config->font_height, &config->font_baseline);
991 root_for_each_container(find_font_height_iterator, NULL);
992 968
993 if (config->font_height != prev_max_height) { 969 if (config->font_height != prev_max_height) {
994 arrange_root(); 970 arrange_root();
@@ -1022,8 +998,12 @@ void translate_keysyms(struct input_config *input_config) {
1022 998
1023 struct xkb_rule_names rules = {0}; 999 struct xkb_rule_names rules = {0};
1024 input_config_fill_rule_names(input_config, &rules); 1000 input_config_fill_rule_names(input_config, &rules);
1025 config->keysym_translation_state = 1001 config->keysym_translation_state = keysym_translation_state_create(rules, 0);
1026 keysym_translation_state_create(rules); 1002 if (config->keysym_translation_state == NULL) {
1003 sway_log(SWAY_ERROR, "Failed to create keysym translation XKB state "
1004 "for device '%s'", input_config->identifier);
1005 return;
1006 }
1027 1007
1028 for (int i = 0; i < config->modes->length; ++i) { 1008 for (int i = 0; i < config->modes->length; ++i) {
1029 struct sway_mode *mode = config->modes->items[i]; 1009 struct sway_mode *mode = config->modes->items[i];
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 767534a6..908b2865 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <signal.h> 1#include <signal.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdio.h> 3#include <stdio.h>
@@ -91,7 +90,7 @@ struct bar_config *default_bar_config(void) {
91 } 90 }
92 bar->outputs = NULL; 91 bar->outputs = NULL;
93 bar->position = strdup("bottom"); 92 bar->position = strdup("bottom");
94 bar->pango_markup = false; 93 bar->pango_markup = PANGO_MARKUP_DEFAULT;
95 bar->swaybar_command = NULL; 94 bar->swaybar_command = NULL;
96 bar->font = NULL; 95 bar->font = NULL;
97 bar->height = 0; 96 bar->height = 0;
@@ -217,6 +216,9 @@ static void invoke_swaybar(struct bar_config *bar) {
217 sigset_t set; 216 sigset_t set;
218 sigemptyset(&set); 217 sigemptyset(&set);
219 sigprocmask(SIG_SETMASK, &set, NULL); 218 sigprocmask(SIG_SETMASK, &set, NULL);
219 signal(SIGPIPE, SIG_DFL);
220
221 restore_nofile_limit();
220 222
221 pid = fork(); 223 pid = fork();
222 if (pid < 0) { 224 if (pid < 0) {
@@ -253,7 +255,6 @@ static void invoke_swaybar(struct bar_config *bar) {
253 } 255 }
254 256
255 sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id); 257 sway_log(SWAY_DEBUG, "Spawned swaybar %s", bar->id);
256 return;
257} 258}
258 259
259void load_swaybar(struct bar_config *bar) { 260void load_swaybar(struct bar_config *bar) {
diff --git a/sway/config/input.c b/sway/config/input.c
index a998e170..de3b21ed 100644
--- a/sway/config/input.c
+++ b/sway/config/input.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <limits.h> 2#include <limits.h>
4#include <float.h> 3#include <float.h>
@@ -25,14 +24,17 @@ struct input_config *new_input_config(const char* identifier) {
25 input->drag = INT_MIN; 24 input->drag = INT_MIN;
26 input->drag_lock = INT_MIN; 25 input->drag_lock = INT_MIN;
27 input->dwt = INT_MIN; 26 input->dwt = INT_MIN;
27 input->dwtp = INT_MIN;
28 input->send_events = INT_MIN; 28 input->send_events = INT_MIN;
29 input->click_method = INT_MIN; 29 input->click_method = INT_MIN;
30 input->middle_emulation = INT_MIN; 30 input->middle_emulation = INT_MIN;
31 input->natural_scroll = INT_MIN; 31 input->natural_scroll = INT_MIN;
32 input->accel_profile = INT_MIN; 32 input->accel_profile = INT_MIN;
33 input->rotation_angle = FLT_MIN;
33 input->pointer_accel = FLT_MIN; 34 input->pointer_accel = FLT_MIN;
34 input->scroll_factor = FLT_MIN; 35 input->scroll_factor = FLT_MIN;
35 input->scroll_button = INT_MIN; 36 input->scroll_button = INT_MIN;
37 input->scroll_button_lock = INT_MIN;
36 input->scroll_method = INT_MIN; 38 input->scroll_method = INT_MIN;
37 input->left_handed = INT_MIN; 39 input->left_handed = INT_MIN;
38 input->repeat_delay = INT_MIN; 40 input->repeat_delay = INT_MIN;
@@ -61,6 +63,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
61 if (src->dwt != INT_MIN) { 63 if (src->dwt != INT_MIN) {
62 dst->dwt = src->dwt; 64 dst->dwt = src->dwt;
63 } 65 }
66 if (src->dwtp != INT_MIN) {
67 dst->dwtp = src->dwtp;
68 }
64 if (src->left_handed != INT_MIN) { 69 if (src->left_handed != INT_MIN) {
65 dst->left_handed = src->left_handed; 70 dst->left_handed = src->left_handed;
66 } 71 }
@@ -70,6 +75,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
70 if (src->natural_scroll != INT_MIN) { 75 if (src->natural_scroll != INT_MIN) {
71 dst->natural_scroll = src->natural_scroll; 76 dst->natural_scroll = src->natural_scroll;
72 } 77 }
78 if (src->rotation_angle != FLT_MIN) {
79 dst->rotation_angle = src->rotation_angle;
80 }
73 if (src->pointer_accel != FLT_MIN) { 81 if (src->pointer_accel != FLT_MIN) {
74 dst->pointer_accel = src->pointer_accel; 82 dst->pointer_accel = src->pointer_accel;
75 } 83 }
@@ -88,6 +96,9 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
88 if (src->scroll_button != INT_MIN) { 96 if (src->scroll_button != INT_MIN) {
89 dst->scroll_button = src->scroll_button; 97 dst->scroll_button = src->scroll_button;
90 } 98 }
99 if (src->scroll_button_lock != INT_MIN) {
100 dst->scroll_button_lock = src->scroll_button_lock;
101 }
91 if (src->send_events != INT_MIN) { 102 if (src->send_events != INT_MIN) {
92 dst->send_events = src->send_events; 103 dst->send_events = src->send_events;
93 } 104 }
diff --git a/sway/config/output.c b/sway/config/output.c
index c9ec6745..54af5d8e 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -1,13 +1,15 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
2#include <drm_fourcc.h>
3#include <stdbool.h> 3#include <stdbool.h>
4#include <string.h> 4#include <string.h>
5#include <sys/socket.h> 5#include <sys/socket.h>
6#include <sys/wait.h> 6#include <sys/wait.h>
7#include <unistd.h> 7#include <unistd.h>
8#include <wlr/config.h>
8#include <wlr/types/wlr_cursor.h> 9#include <wlr/types/wlr_cursor.h>
9#include <wlr/types/wlr_output_layout.h> 10#include <wlr/types/wlr_output_layout.h>
10#include <wlr/types/wlr_output.h> 11#include <wlr/types/wlr_output.h>
12#include <wlr/types/wlr_output_swapchain_manager.h>
11#include "sway/config.h" 13#include "sway/config.h"
12#include "sway/input/cursor.h" 14#include "sway/input/cursor.h"
13#include "sway/output.h" 15#include "sway/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,10 +74,77 @@ 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
71void merge_output_config(struct output_config *dst, struct output_config *src) { 82// supersede_output_config clears all fields in dst that were set in src
83static void supersede_output_config(struct output_config *dst, struct output_config *src) {
84 if (src->enabled != -1) {
85 dst->enabled = -1;
86 }
87 if (src->width != -1) {
88 dst->width = -1;
89 }
90 if (src->height != -1) {
91 dst->height = -1;
92 }
93 if (src->x != -1) {
94 dst->x = -1;
95 }
96 if (src->y != -1) {
97 dst->y = -1;
98 }
99 if (src->scale != -1) {
100 dst->scale = -1;
101 }
102 if (src->scale_filter != SCALE_FILTER_DEFAULT) {
103 dst->scale_filter = SCALE_FILTER_DEFAULT;
104 }
105 if (src->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN) {
106 dst->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
107 }
108 if (src->refresh_rate != -1) {
109 dst->refresh_rate = -1;
110 }
111 if (src->custom_mode != -1) {
112 dst->custom_mode = -1;
113 }
114 if (src->drm_mode.type != (uint32_t) -1) {
115 dst->drm_mode.type = -1;
116 }
117 if (src->transform != -1) {
118 dst->transform = -1;
119 }
120 if (src->max_render_time != -1) {
121 dst->max_render_time = -1;
122 }
123 if (src->adaptive_sync != -1) {
124 dst->adaptive_sync = -1;
125 }
126 if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
127 dst->render_bit_depth = RENDER_BIT_DEPTH_DEFAULT;
128 }
129 if (src->background) {
130 free(dst->background);
131 dst->background = NULL;
132 }
133 if (src->background_option) {
134 free(dst->background_option);
135 dst->background_option = NULL;
136 }
137 if (src->background_fallback) {
138 free(dst->background_fallback);
139 dst->background_fallback = NULL;
140 }
141 if (src->power != -1) {
142 dst->power = -1;
143 }
144}
145
146// merge_output_config sets all fields in dst that were set in src
147static void merge_output_config(struct output_config *dst, struct output_config *src) {
72 if (src->enabled != -1) { 148 if (src->enabled != -1) {
73 dst->enabled = src->enabled; 149 dst->enabled = src->enabled;
74 } 150 }
@@ -99,6 +175,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
99 if (src->custom_mode != -1) { 175 if (src->custom_mode != -1) {
100 dst->custom_mode = src->custom_mode; 176 dst->custom_mode = src->custom_mode;
101 } 177 }
178 if (src->drm_mode.type != (uint32_t) -1) {
179 memcpy(&dst->drm_mode, &src->drm_mode, sizeof(src->drm_mode));
180 }
102 if (src->transform != -1) { 181 if (src->transform != -1) {
103 dst->transform = src->transform; 182 dst->transform = src->transform;
104 } 183 }
@@ -108,6 +187,9 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
108 if (src->adaptive_sync != -1) { 187 if (src->adaptive_sync != -1) {
109 dst->adaptive_sync = src->adaptive_sync; 188 dst->adaptive_sync = src->adaptive_sync;
110 } 189 }
190 if (src->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
191 dst->render_bit_depth = src->render_bit_depth;
192 }
111 if (src->background) { 193 if (src->background) {
112 free(dst->background); 194 free(dst->background);
113 dst->background = strdup(src->background); 195 dst->background = strdup(src->background);
@@ -120,155 +202,124 @@ void merge_output_config(struct output_config *dst, struct output_config *src) {
120 free(dst->background_fallback); 202 free(dst->background_fallback);
121 dst->background_fallback = strdup(src->background_fallback); 203 dst->background_fallback = strdup(src->background_fallback);
122 } 204 }
123 if (src->dpms_state != 0) { 205 if (src->power != -1) {
124 dst->dpms_state = src->dpms_state; 206 dst->power = src->power;
125 } 207 }
126} 208}
127 209
128static void merge_wildcard_on_all(struct output_config *wildcard) { 210void store_output_config(struct output_config *oc) {
129 for (int i = 0; i < config->output_configs->length; i++) { 211 bool merged = false;
130 struct output_config *oc = config->output_configs->items[i]; 212 bool wildcard = strcmp(oc->name, "*") == 0;
131 if (strcmp(wildcard->name, oc->name) != 0) { 213 struct sway_output *output = wildcard ? NULL : output_by_name_or_id(oc->name);
132 sway_log(SWAY_DEBUG, "Merging output * config on %s", oc->name);
133 merge_output_config(oc, wildcard);
134 }
135 }
136}
137 214
138static void merge_id_on_name(struct output_config *oc) {
139 char *id_on_name = NULL;
140 char id[128]; 215 char id[128];
141 char *name = NULL; 216 if (output) {
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); 217 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 } 218 }
157 219
158 if (!id_on_name) { 220 for (int i = 0; i < config->output_configs->length; i++) {
159 return; 221 struct output_config *old = config->output_configs->items[i];
160 } 222
161 223 // If the old config matches the new config's name, regardless of
162 int i = list_seq_find(config->output_configs, output_name_cmp, id_on_name); 224 // whether it was name or identifier, merge on top of the existing
163 if (i >= 0) { 225 // config. If the new config is a wildcard, this also merges on top of
164 sway_log(SWAY_DEBUG, "Merging on top of existing id on name config"); 226 // old wildcard configs.
165 merge_output_config(config->output_configs->items[i], oc); 227 if (strcmp(old->name, oc->name) == 0) {
166 } else { 228 merge_output_config(old, oc);
167 // If both a name and identifier config, exist generate an id on name 229 merged = true;
168 int ni = list_seq_find(config->output_configs, output_name_cmp, name); 230 continue;
169 int ii = list_seq_find(config->output_configs, output_name_cmp, id);
170 if ((ni >= 0 && ii >= 0) || (ni >= 0 && strcmp(oc->name, id) == 0)
171 || (ii >= 0 && strcmp(oc->name, name) == 0)) {
172 struct output_config *ion_oc = new_output_config(id_on_name);
173 if (ni >= 0) {
174 merge_output_config(ion_oc, config->output_configs->items[ni]);
175 }
176 if (ii >= 0) {
177 merge_output_config(ion_oc, config->output_configs->items[ii]);
178 }
179 merge_output_config(ion_oc, oc);
180 list_add(config->output_configs, ion_oc);
181 sway_log(SWAY_DEBUG, "Generated id on name output config \"%s\""
182 " (enabled: %d) (%dx%d@%fHz position %d,%d scale %f "
183 "transform %d) (bg %s %s) (dpms %d) (max render time: %d)",
184 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,
186 ion_oc->transform, ion_oc->background,
187 ion_oc->background_option, ion_oc->dpms_state,
188 ion_oc->max_render_time);
189 } 231 }
190 }
191 free(id_on_name);
192}
193 232
194struct output_config *store_output_config(struct output_config *oc) { 233 // If the new config is a wildcard config we supersede all non-wildcard
195 bool wildcard = strcmp(oc->name, "*") == 0; 234 // configs. Old wildcard configs have already been handled above.
196 if (wildcard) { 235 if (wildcard) {
197 merge_wildcard_on_all(oc); 236 supersede_output_config(old, oc);
198 } else { 237 continue;
199 merge_id_on_name(oc); 238 }
200 }
201 239
202 int i = list_seq_find(config->output_configs, output_name_cmp, oc->name); 240 // If the new config matches an output's name, and the old config
203 if (i >= 0) { 241 // matches on that output's identifier, supersede it.
204 sway_log(SWAY_DEBUG, "Merging on top of existing output config"); 242 if (output && strcmp(old->name, id) == 0 &&
205 struct output_config *current = config->output_configs->items[i]; 243 strcmp(oc->name, output->wlr_output->name) == 0) {
206 merge_output_config(current, oc); 244 supersede_output_config(old, oc);
207 free_output_config(oc);
208 oc = current;
209 } else if (!wildcard) {
210 sway_log(SWAY_DEBUG, "Adding non-wildcard output config");
211 i = list_seq_find(config->output_configs, output_name_cmp, "*");
212 if (i >= 0) {
213 sway_log(SWAY_DEBUG, "Merging on top of output * config");
214 struct output_config *current = new_output_config(oc->name);
215 merge_output_config(current, config->output_configs->items[i]);
216 merge_output_config(current, oc);
217 free_output_config(oc);
218 oc = current;
219 } 245 }
220 list_add(config->output_configs, oc);
221 } else {
222 // New wildcard config. Just add it
223 sway_log(SWAY_DEBUG, "Adding output * config");
224 list_add(config->output_configs, oc);
225 } 246 }
226 247
227 sway_log(SWAY_DEBUG, "Config stored for output %s (enabled: %d) (%dx%d@%fHz " 248 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) " 249 "position %d,%d scale %f subpixel %s transform %d) (bg %s %s) (power %d) "
229 "(max render time: %d)", 250 "(max render time: %d)",
230 oc->name, oc->enabled, oc->width, oc->height, oc->refresh_rate, 251 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), 252 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, 253 oc->transform, oc->background, oc->background_option, oc->power,
233 oc->max_render_time); 254 oc->max_render_time);
234 255
235 return oc; 256 // If the configuration was not merged into an existing configuration, add
257 // it to the list. Otherwise we're done with it and can free it.
258 if (!merged) {
259 list_add(config->output_configs, oc);
260 } else {
261 free_output_config(oc);
262 }
236} 263}
237 264
238static void set_mode(struct wlr_output *output, int width, int height, 265static void set_mode(struct wlr_output *output, struct wlr_output_state *pending,
239 float refresh_rate, bool custom) { 266 int width, int height, float refresh_rate, bool custom) {
240 // Not all floating point integers can be represented exactly 267 // Not all floating point integers can be represented exactly
241 // as (int)(1000 * mHz / 1000.f) 268 // as (int)(1000 * mHz / 1000.f)
242 // round() the result to avoid any error 269 // round() the result to avoid any error
243 int mhz = (int)round(refresh_rate * 1000); 270 int mhz = (int)roundf(refresh_rate * 1000);
271 // If no target refresh rate is given, match highest available
272 mhz = mhz <= 0 ? INT_MAX : mhz;
244 273
245 if (wl_list_empty(&output->modes) || custom) { 274 if (wl_list_empty(&output->modes) || custom) {
246 sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name); 275 sway_log(SWAY_DEBUG, "Assigning custom mode to %s", output->name);
247 wlr_output_set_custom_mode(output, width, height, 276 wlr_output_state_set_custom_mode(pending, width, height,
248 refresh_rate > 0 ? mhz : 0); 277 refresh_rate > 0 ? mhz : 0);
249 return; 278 return;
250 } 279 }
251 280
252 struct wlr_output_mode *mode, *best = NULL; 281 struct wlr_output_mode *mode, *best = NULL;
282 int best_diff_mhz = INT_MAX;
253 wl_list_for_each(mode, &output->modes, link) { 283 wl_list_for_each(mode, &output->modes, link) {
254 if (mode->width == width && mode->height == height) { 284 if (mode->width == width && mode->height == height) {
255 if (mode->refresh == mhz) { 285 int diff_mhz = abs(mode->refresh - mhz);
256 best = mode; 286 if (diff_mhz < best_diff_mhz) {
257 break; 287 best_diff_mhz = diff_mhz;
258 }
259 if (best == NULL || mode->refresh > best->refresh) {
260 best = mode; 288 best = mode;
289 if (best_diff_mhz == 0) {
290 break;
291 }
261 } 292 }
262 } 293 }
263 } 294 }
264 if (!best) { 295 if (best) {
265 sway_log(SWAY_ERROR, "Configured mode for %s not available", output->name); 296 sway_log(SWAY_INFO, "Assigning configured mode (%dx%d@%.3fHz) to %s",
266 sway_log(SWAY_INFO, "Picking preferred mode instead"); 297 best->width, best->height, best->refresh / 1000.f, output->name);
267 best = wlr_output_preferred_mode(output);
268 } else { 298 } else {
269 sway_log(SWAY_DEBUG, "Assigning configured mode to %s", output->name); 299 best = wlr_output_preferred_mode(output);
300 sway_log(SWAY_INFO, "Configured mode (%dx%d@%.3fHz) not available, "
301 "applying preferred mode (%dx%d@%.3fHz)",
302 width, height, refresh_rate,
303 best->width, best->height, best->refresh / 1000.f);
304 }
305 wlr_output_state_set_mode(pending, best);
306}
307
308static void set_modeline(struct wlr_output *output,
309 struct wlr_output_state *pending, drmModeModeInfo *drm_mode) {
310#if WLR_HAS_DRM_BACKEND
311 if (!wlr_output_is_drm(output)) {
312 sway_log(SWAY_ERROR, "Modeline can only be set to DRM output");
313 return;
314 }
315 sway_log(SWAY_DEBUG, "Assigning custom modeline to %s", output->name);
316 struct wlr_output_mode *mode = wlr_drm_connector_add_mode(output, drm_mode);
317 if (mode) {
318 wlr_output_state_set_mode(pending, mode);
270 } 319 }
271 wlr_output_set_mode(output, best); 320#else
321 sway_log(SWAY_ERROR, "Modeline can only be set to DRM output");
322#endif
272} 323}
273 324
274/* Some manufacturers hardcode the aspect-ratio of the output in the physical 325/* Some manufacturers hardcode the aspect-ratio of the output in the physical
@@ -289,23 +340,24 @@ static bool phys_size_is_aspect_ratio(struct wlr_output *output) {
289// 1 inch = 25.4 mm 340// 1 inch = 25.4 mm
290#define MM_PER_INCH 25.4 341#define MM_PER_INCH 25.4
291 342
292static int compute_default_scale(struct wlr_output *output) { 343static int compute_default_scale(struct wlr_output *output,
344 struct wlr_output_state *pending) {
293 struct wlr_box box = { .width = output->width, .height = output->height }; 345 struct wlr_box box = { .width = output->width, .height = output->height };
294 if (output->pending.committed & WLR_OUTPUT_STATE_MODE) { 346 if (pending->committed & WLR_OUTPUT_STATE_MODE) {
295 switch (output->pending.mode_type) { 347 switch (pending->mode_type) {
296 case WLR_OUTPUT_STATE_MODE_FIXED: 348 case WLR_OUTPUT_STATE_MODE_FIXED:
297 box.width = output->pending.mode->width; 349 box.width = pending->mode->width;
298 box.height = output->pending.mode->height; 350 box.height = pending->mode->height;
299 break; 351 break;
300 case WLR_OUTPUT_STATE_MODE_CUSTOM: 352 case WLR_OUTPUT_STATE_MODE_CUSTOM:
301 box.width = output->pending.custom_mode.width; 353 box.width = pending->custom_mode.width;
302 box.height = output->pending.custom_mode.height; 354 box.height = pending->custom_mode.height;
303 break; 355 break;
304 } 356 }
305 } 357 }
306 enum wl_output_transform transform = output->transform; 358 enum wl_output_transform transform = output->transform;
307 if (output->pending.committed & WLR_OUTPUT_STATE_TRANSFORM) { 359 if (pending->committed & WLR_OUTPUT_STATE_TRANSFORM) {
308 transform = output->pending.transform; 360 transform = pending->transform;
309 } 361 }
310 wlr_box_transform(&box, &box, transform, box.width, box.height); 362 wlr_box_transform(&box, &box, transform, box.width, box.height);
311 363
@@ -334,42 +386,90 @@ static int compute_default_scale(struct wlr_output *output) {
334 return 2; 386 return 2;
335} 387}
336 388
389/* Lists of formats to try, in order, when a specific render bit depth has
390 * been asked for. The second to last format in each list should always
391 * be XRGB8888, as a reliable backup in case the others are not available;
392 * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */
393static const uint32_t *bit_depth_preferences[] = {
394 [RENDER_BIT_DEPTH_8] = (const uint32_t []){
395 DRM_FORMAT_XRGB8888,
396 DRM_FORMAT_INVALID,
397 },
398 [RENDER_BIT_DEPTH_10] = (const uint32_t []){
399 DRM_FORMAT_XRGB2101010,
400 DRM_FORMAT_XBGR2101010,
401 DRM_FORMAT_XRGB8888,
402 DRM_FORMAT_INVALID,
403 },
404};
405
337static void queue_output_config(struct output_config *oc, 406static void queue_output_config(struct output_config *oc,
338 struct sway_output *output) { 407 struct sway_output *output, struct wlr_output_state *pending) {
339 if (output == root->noop_output) { 408 if (output == root->fallback_output) {
340 return; 409 return;
341 } 410 }
342 411
343 struct wlr_output *wlr_output = output->wlr_output; 412 struct wlr_output *wlr_output = output->wlr_output;
344 413
345 if (oc && (!oc->enabled || oc->dpms_state == DPMS_OFF)) { 414 if (oc && (!oc->enabled || oc->power == 0)) {
346 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); 415 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name);
347 wlr_output_enable(wlr_output, false); 416 wlr_output_state_set_enabled(pending, false);
348 return; 417 return;
349 } 418 }
350 419
351 sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name); 420 sway_log(SWAY_DEBUG, "Turning on output %s", wlr_output->name);
352 wlr_output_enable(wlr_output, true); 421 wlr_output_state_set_enabled(pending, true);
353 422
354 if (oc && oc->width > 0 && oc->height > 0) { 423 if (oc && oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t) -1) {
424 sway_log(SWAY_DEBUG, "Set %s modeline",
425 wlr_output->name);
426 set_modeline(wlr_output, pending, &oc->drm_mode);
427 } else if (oc && oc->width > 0 && oc->height > 0) {
355 sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)", 428 sway_log(SWAY_DEBUG, "Set %s mode to %dx%d (%f Hz)",
356 wlr_output->name, oc->width, oc->height, oc->refresh_rate); 429 wlr_output->name, oc->width, oc->height, oc->refresh_rate);
357 set_mode(wlr_output, oc->width, oc->height, 430 set_mode(wlr_output, pending, oc->width, oc->height,
358 oc->refresh_rate, oc->custom_mode == 1); 431 oc->refresh_rate, oc->custom_mode == 1);
359 } else if (!wl_list_empty(&wlr_output->modes)) { 432 } else if (!wl_list_empty(&wlr_output->modes)) {
360 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); 433 sway_log(SWAY_DEBUG, "Set preferred mode");
361 wlr_output_set_mode(wlr_output, mode); 434 struct wlr_output_mode *preferred_mode =
435 wlr_output_preferred_mode(wlr_output);
436 wlr_output_state_set_mode(pending, preferred_mode);
437
438 if (!wlr_output_test_state(wlr_output, pending)) {
439 sway_log(SWAY_DEBUG, "Preferred mode rejected, "
440 "falling back to another mode");
441 struct wlr_output_mode *mode;
442 wl_list_for_each(mode, &wlr_output->modes, link) {
443 if (mode == preferred_mode) {
444 continue;
445 }
446
447 wlr_output_state_set_mode(pending, mode);
448 if (wlr_output_test_state(wlr_output, pending)) {
449 break;
450 }
451 }
452 }
362 } 453 }
363 454
364 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { 455 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) {
365 sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name, 456 sway_log(SWAY_DEBUG, "Set %s subpixel to %s", oc->name,
366 sway_wl_output_subpixel_to_string(oc->subpixel)); 457 sway_wl_output_subpixel_to_string(oc->subpixel));
367 wlr_output_set_subpixel(wlr_output, oc->subpixel); 458 wlr_output_state_set_subpixel(pending, oc->subpixel);
368 } 459 }
369 460
461 enum wl_output_transform tr = WL_OUTPUT_TRANSFORM_NORMAL;
370 if (oc && oc->transform >= 0) { 462 if (oc && oc->transform >= 0) {
371 sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, oc->transform); 463 tr = oc->transform;
372 wlr_output_set_transform(wlr_output, oc->transform); 464#if WLR_HAS_DRM_BACKEND
465 } else if (wlr_output_is_drm(wlr_output)) {
466 tr = wlr_drm_connector_get_panel_orientation(wlr_output);
467 sway_log(SWAY_DEBUG, "Auto-detected output transform: %d", tr);
468#endif
469 }
470 if (wlr_output->transform != tr) {
471 sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, tr);
472 wlr_output_state_set_transform(pending, tr);
373 } 473 }
374 474
375 // Apply the scale last before the commit, because the scale auto-detection 475 // Apply the scale last before the commit, because the scale auto-detection
@@ -377,50 +477,58 @@ static void queue_output_config(struct output_config *oc,
377 float scale; 477 float scale;
378 if (oc && oc->scale > 0) { 478 if (oc && oc->scale > 0) {
379 scale = oc->scale; 479 scale = oc->scale;
480
481 // The factional-scale-v1 protocol uses increments of 120ths to send
482 // the scale factor to the client. Adjust the scale so that we use the
483 // same value as the clients'.
484 float adjusted_scale = round(scale * 120) / 120;
485 if (scale != adjusted_scale) {
486 sway_log(SWAY_INFO, "Adjusting output scale from %f to %f",
487 scale, adjusted_scale);
488 scale = adjusted_scale;
489 }
380 } else { 490 } else {
381 scale = compute_default_scale(wlr_output); 491 scale = compute_default_scale(wlr_output, pending);
382 sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale); 492 sway_log(SWAY_DEBUG, "Auto-detected output scale: %f", scale);
383 } 493 }
384 if (scale != wlr_output->scale) { 494 if (scale != wlr_output->scale) {
385 sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale); 495 sway_log(SWAY_DEBUG, "Set %s scale to %f", wlr_output->name, scale);
386 wlr_output_set_scale(wlr_output, scale); 496 wlr_output_state_set_scale(pending, scale);
387 } 497 }
388 498
389 if (oc && oc->adaptive_sync != -1) { 499 if (oc && oc->adaptive_sync != -1) {
390 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, 500 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name,
391 oc->adaptive_sync); 501 oc->adaptive_sync);
392 wlr_output_enable_adaptive_sync(wlr_output, oc->adaptive_sync == 1); 502 wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1);
393 } 503 if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) {
394} 504 sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring");
395 505 wlr_output_state_set_adaptive_sync_enabled(pending, false);
396bool apply_output_config(struct output_config *oc, struct sway_output *output) { 506 }
397 if (output == root->noop_output) {
398 return false;
399 } 507 }
400 508
401 struct wlr_output *wlr_output = output->wlr_output; 509 if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
402 510 const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth];
403 // Flag to prevent the output mode event handler from calling us 511 assert(fmts);
404 output->enabling = (!oc || oc->enabled);
405 512
406 queue_output_config(oc, output); 513 for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) {
514 wlr_output_state_set_render_format(pending, fmts[i]);
515 if (wlr_output_test_state(wlr_output, pending)) {
516 break;
517 }
407 518
408 if (!oc || oc->dpms_state != DPMS_OFF) { 519 sway_log(SWAY_DEBUG, "Preferred output format 0x%08x "
409 output->current_mode = wlr_output->pending.mode; 520 "failed to work, falling back to next in "
521 "list, 0x%08x", fmts[i], fmts[i + 1]);
522 }
410 } 523 }
524}
411 525
412 sway_log(SWAY_DEBUG, "Committing output %s", wlr_output->name); 526static bool finalize_output_config(struct output_config *oc, struct sway_output *output) {
413 if (!wlr_output_commit(wlr_output)) { 527 if (output == root->fallback_output) {
414 // 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
416 // the mode we asked for.
417 sway_log(SWAY_ERROR, "Failed to commit output %s", wlr_output->name);
418 output->enabling = false;
419 return false; 528 return false;
420 } 529 }
421 530
422 output->enabling = false; 531 struct wlr_output *wlr_output = output->wlr_output;
423
424 if (oc && !oc->enabled) { 532 if (oc && !oc->enabled) {
425 sway_log(SWAY_DEBUG, "Disabling output %s", oc->name); 533 sway_log(SWAY_DEBUG, "Disabling output %s", oc->name);
426 if (output->enabled) { 534 if (output->enabled) {
@@ -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);
@@ -479,27 +584,13 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
479 output->max_render_time = oc->max_render_time; 584 output->max_render_time = oc->max_render_time;
480 } 585 }
481 586
482 // 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
484 // dependent on an output being present.
485 input_manager_configure_all_inputs();
486 return true; 587 return true;
487} 588}
488 589
489bool test_output_config(struct output_config *oc, struct sway_output *output) {
490 if (output == root->noop_output) {
491 return false;
492 }
493
494 queue_output_config(oc, output);
495 bool ok = wlr_output_test(output->wlr_output);
496 wlr_output_rollback(output->wlr_output);
497 return ok;
498}
499
500static void default_output_config(struct output_config *oc, 590static void default_output_config(struct output_config *oc,
501 struct wlr_output *wlr_output) { 591 struct wlr_output *wlr_output) {
502 oc->enabled = 1; 592 oc->enabled = 1;
593 oc->power = 1;
503 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output); 594 struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
504 if (mode != NULL) { 595 if (mode != NULL) {
505 oc->width = mode->width; 596 oc->width = mode->width;
@@ -512,147 +603,170 @@ static void default_output_config(struct output_config *oc,
512 struct sway_output *output = wlr_output->data; 603 struct sway_output *output = wlr_output->data;
513 oc->subpixel = output->detected_subpixel; 604 oc->subpixel = output->detected_subpixel;
514 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL; 605 oc->transform = WL_OUTPUT_TRANSFORM_NORMAL;
515 oc->dpms_state = DPMS_ON;
516 oc->max_render_time = 0; 606 oc->max_render_time = 0;
517} 607}
518 608
519static struct output_config *get_output_config(char *identifier, 609// find_output_config returns a merged output_config containing all stored
520 struct sway_output *sway_output) { 610// configuration that applies to the specified output.
611struct output_config *find_output_config(struct sway_output *sway_output) {
521 const char *name = sway_output->wlr_output->name; 612 const char *name = sway_output->wlr_output->name;
613 struct output_config *oc = NULL;
522 614
523 struct output_config *oc_id_on_name = NULL; 615 struct output_config *result = new_output_config(name);
524 struct output_config *oc_name = NULL; 616 if (config->reloading) {
525 struct output_config *oc_id = NULL; 617 default_output_config(result, sway_output->wlr_output);
618 }
526 619
527 size_t length = snprintf(NULL, 0, "%s on %s", identifier, name) + 1; 620 char id[128];
528 char *id_on_name = malloc(length); 621 output_get_identifier(id, sizeof(id), sway_output);
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);
531 if (i >= 0) {
532 oc_id_on_name = config->output_configs->items[i];
533 } else {
534 i = list_seq_find(config->output_configs, output_name_cmp, name);
535 if (i >= 0) {
536 oc_name = config->output_configs->items[i];
537 }
538 622
539 i = list_seq_find(config->output_configs, output_name_cmp, identifier); 623 int i;
540 if (i >= 0) { 624 bool match = false;
541 oc_id = config->output_configs->items[i]; 625 if ((i = list_seq_find(config->output_configs, output_name_cmp, "*")) >= 0) {
542 } 626 match = true;
627 oc = config->output_configs->items[i];
628 merge_output_config(result, oc);
543 } 629 }
544 630 if ((i = list_seq_find(config->output_configs, output_name_cmp, name)) >= 0) {
545 struct output_config *result = new_output_config("temp"); 631 match = true;
546 if (config->reloading) { 632 oc = config->output_configs->items[i];
547 default_output_config(result, sway_output->wlr_output); 633 merge_output_config(result, oc);
548 } 634 }
549 if (oc_id_on_name) { 635 if ((i = list_seq_find(config->output_configs, output_name_cmp, id)) >= 0) {
550 // Already have an identifier on name config, use that 636 match = true;
551 free(result->name); 637 oc = config->output_configs->items[i];
552 result->name = strdup(id_on_name); 638 merge_output_config(result, oc);
553 merge_output_config(result, oc_id_on_name); 639 }
554 } else if (oc_name && oc_id) { 640
555 // Generate a config named `<identifier> on <name>` which contains a 641 if (!match && !config->reloading) {
556 // merged copy of the identifier on name. This will make sure that both 642 // No name, identifier, or wildcard config. Since we are not
557 // identifier and name configs are respected, with identifier getting 643 // reloading with defaults, the output config will be empty, so
558 // priority 644 // just return NULL
559 struct output_config *temp = new_output_config(id_on_name); 645 free_output_config(result);
560 merge_output_config(temp, oc_name); 646 return NULL;
561 merge_output_config(temp, oc_id);
562 list_add(config->output_configs, temp);
563
564 free(result->name);
565 result->name = strdup(id_on_name);
566 merge_output_config(result, temp);
567
568 sway_log(SWAY_DEBUG, "Generated output config \"%s\" (enabled: %d)"
569 " (%dx%d@%fHz position %d,%d scale %f transform %d) (bg %s %s)"
570 " (dpms %d) (max render time: %d)", result->name, result->enabled,
571 result->width, result->height, result->refresh_rate,
572 result->x, result->y, result->scale, result->transform,
573 result->background, result->background_option, result->dpms_state,
574 result->max_render_time);
575 } else if (oc_name) {
576 // No identifier config, just return a copy of the name config
577 free(result->name);
578 result->name = strdup(name);
579 merge_output_config(result, oc_name);
580 } else if (oc_id) {
581 // No name config, just return a copy of the identifier config
582 free(result->name);
583 result->name = strdup(identifier);
584 merge_output_config(result, oc_id);
585 } else {
586 i = list_seq_find(config->output_configs, output_name_cmp, "*");
587 if (i >= 0) {
588 // No name or identifier config, but there is a wildcard config
589 free(result->name);
590 result->name = strdup("*");
591 merge_output_config(result, config->output_configs->items[i]);
592 } else if (!config->reloading) {
593 // No name, identifier, or wildcard config. Since we are not
594 // reloading with defaults, the output config will be empty, so
595 // just return NULL
596 free_output_config(result);
597 result = NULL;
598 }
599 } 647 }
600 648
601 free(id_on_name);
602 return result; 649 return result;
603} 650}
604 651
605struct output_config *find_output_config(struct sway_output *output) { 652bool apply_output_configs(struct matched_output_config *configs,
606 char id[128]; 653 size_t configs_len, bool test_only) {
607 output_get_identifier(id, sizeof(id), output); 654 struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states));
608 return get_output_config(id, output); 655 if (!states) {
609} 656 return false;
657 }
610 658
611void apply_output_config_to_outputs(struct output_config *oc) { 659 sway_log(SWAY_DEBUG, "Committing %zd outputs", configs_len);
612 // Try to find the output container and apply configuration now. If 660 for (size_t idx = 0; idx < configs_len; idx++) {
613 // this is during startup then there will be no container and config 661 struct matched_output_config *cfg = &configs[idx];
614 // will be applied during normal "new output" event from wlroots. 662 struct wlr_backend_output_state *backend_state = &states[idx];
615 bool wildcard = strcmp(oc->name, "*") == 0;
616 char id[128];
617 struct sway_output *sway_output, *tmp;
618 wl_list_for_each_safe(sway_output, tmp, &root->all_outputs, link) {
619 char *name = sway_output->wlr_output->name;
620 output_get_identifier(id, sizeof(id), sway_output);
621 if (wildcard || !strcmp(name, oc->name) || !strcmp(id, oc->name)) {
622 struct output_config *current = get_output_config(id, sway_output);
623 if (!current) {
624 // No stored output config matched, apply oc directly
625 sway_log(SWAY_DEBUG, "Applying oc directly");
626 current = new_output_config(oc->name);
627 merge_output_config(current, oc);
628 }
629 apply_output_config(current, sway_output);
630 free_output_config(current);
631 663
632 if (!wildcard) { 664 backend_state->output = cfg->output->wlr_output;
633 // Stop looking if the output config isn't applicable to all 665 wlr_output_state_init(&backend_state->base);
634 // outputs 666
635 break; 667 sway_log(SWAY_DEBUG, "Preparing config for %s",
636 } 668 cfg->output->wlr_output->name);
669 queue_output_config(cfg->config, cfg->output, &backend_state->base);
670 }
671
672 struct wlr_output_swapchain_manager swapchain_mgr;
673 wlr_output_swapchain_manager_init(&swapchain_mgr, server.backend);
674
675 bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len);
676 if (!ok) {
677 sway_log(SWAY_ERROR, "Swapchain prepare failed");
678 goto out;
679 }
680
681 if (test_only) {
682 // The swapchain manager already did a test for us
683 goto out;
684 }
685
686 for (size_t idx = 0; idx < configs_len; idx++) {
687 struct matched_output_config *cfg = &configs[idx];
688 struct wlr_backend_output_state *backend_state = &states[idx];
689
690 struct wlr_scene_output_state_options opts = {
691 .swapchain = wlr_output_swapchain_manager_get_swapchain(
692 &swapchain_mgr, backend_state->output),
693 };
694 struct wlr_scene_output *scene_output = cfg->output->scene_output;
695 struct wlr_output_state *state = &backend_state->base;
696 if (!wlr_scene_output_build_state(scene_output, state, &opts)) {
697 sway_log(SWAY_ERROR, "Building output state for '%s' failed",
698 backend_state->output->name);
699 goto out;
637 } 700 }
638 } 701 }
639 702
703 ok = wlr_backend_commit(server.backend, states, configs_len);
704 if (!ok) {
705 sway_log(SWAY_ERROR, "Backend commit failed");
706 goto out;
707 }
708
709 sway_log(SWAY_DEBUG, "Commit of %zd outputs succeeded", configs_len);
710
711 wlr_output_swapchain_manager_apply(&swapchain_mgr);
712
713 for (size_t idx = 0; idx < configs_len; idx++) {
714 struct matched_output_config *cfg = &configs[idx];
715 sway_log(SWAY_DEBUG, "Finalizing config for %s",
716 cfg->output->wlr_output->name);
717 finalize_output_config(cfg->config, cfg->output);
718 }
719
720out:
721 wlr_output_swapchain_manager_finish(&swapchain_mgr);
722 for (size_t idx = 0; idx < configs_len; idx++) {
723 struct wlr_backend_output_state *backend_state = &states[idx];
724 wlr_output_state_finish(&backend_state->base);
725 }
726 free(states);
727
728 // Reconfigure all devices, since input config may have been applied before
729 // this output came online, and some config items (like map_to_output) are
730 // dependent on an output being present.
731 input_manager_configure_all_input_mappings();
732 // Reconfigure the cursor images, since the scale may have changed.
733 input_manager_configure_xcursor();
734
640 struct sway_seat *seat; 735 struct sway_seat *seat;
641 wl_list_for_each(seat, &server.input->seats, link) { 736 wl_list_for_each(seat, &server.input->seats, link) {
642 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 737 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
643 cursor_rebase(seat->cursor); 738 cursor_rebase(seat->cursor);
644 } 739 }
740
741 return ok;
645} 742}
646 743
647void reset_outputs(void) { 744void apply_all_output_configs(void) {
648 struct output_config *oc = NULL; 745 size_t configs_len = wl_list_length(&root->all_outputs);
649 int i = list_seq_find(config->output_configs, output_name_cmp, "*"); 746 struct matched_output_config *configs = calloc(configs_len, sizeof(*configs));
650 if (i >= 0) { 747 if (!configs) {
651 oc = config->output_configs->items[i]; 748 return;
652 } else {
653 oc = store_output_config(new_output_config("*"));
654 } 749 }
655 apply_output_config_to_outputs(oc); 750
751 int config_idx = 0;
752 struct sway_output *sway_output;
753 wl_list_for_each(sway_output, &root->all_outputs, link) {
754 if (sway_output == root->fallback_output) {
755 configs_len--;
756 continue;
757 }
758
759 struct matched_output_config *config = &configs[config_idx++];
760 config->output = sway_output;
761 config->config = find_output_config(sway_output);
762 }
763
764 apply_output_configs(configs, configs_len, false);
765 for (size_t idx = 0; idx < configs_len; idx++) {
766 struct matched_output_config *cfg = &configs[idx];
767 free_output_config(cfg->config);
768 }
769 free(configs);
656} 770}
657 771
658void free_output_config(struct output_config *oc) { 772void free_output_config(struct output_config *oc) {
@@ -702,6 +816,8 @@ static bool _spawn_swaybg(char **command) {
702 sway_log_errno(SWAY_ERROR, "fork failed"); 816 sway_log_errno(SWAY_ERROR, "fork failed");
703 return false; 817 return false;
704 } else if (pid == 0) { 818 } else if (pid == 0) {
819 restore_nofile_limit();
820
705 pid = fork(); 821 pid = fork();
706 if (pid < 0) { 822 if (pid < 0) {
707 sway_log_errno(SWAY_ERROR, "fork failed"); 823 sway_log_errno(SWAY_ERROR, "fork failed");
@@ -717,7 +833,9 @@ static bool _spawn_swaybg(char **command) {
717 setenv("WAYLAND_SOCKET", wayland_socket_str, true); 833 setenv("WAYLAND_SOCKET", wayland_socket_str, true);
718 834
719 execvp(command[0], command); 835 execvp(command[0], command);
720 sway_log_errno(SWAY_ERROR, "execvp failed"); 836 sway_log_errno(SWAY_ERROR, "failed to execute '%s' "
837 "(background configuration probably not applied)",
838 command[0]);
721 _exit(EXIT_FAILURE); 839 _exit(EXIT_FAILURE);
722 } 840 }
723 _exit(EXIT_SUCCESS); 841 _exit(EXIT_SUCCESS);
@@ -727,12 +845,13 @@ static bool _spawn_swaybg(char **command) {
727 sway_log_errno(SWAY_ERROR, "close failed"); 845 sway_log_errno(SWAY_ERROR, "close failed");
728 return false; 846 return false;
729 } 847 }
730 if (waitpid(pid, NULL, 0) < 0) { 848 int fork_status = 0;
849 if (waitpid(pid, &fork_status, 0) < 0) {
731 sway_log_errno(SWAY_ERROR, "waitpid failed"); 850 sway_log_errno(SWAY_ERROR, "waitpid failed");
732 return false; 851 return false;
733 } 852 }
734 853
735 return true; 854 return WIFEXITED(fork_status) && WEXITSTATUS(fork_status) == EXIT_SUCCESS;
736} 855}
737 856
738bool spawn_swaybg(void) { 857bool spawn_swaybg(void) {
diff --git a/sway/config/seat.c b/sway/config/seat.c
index 84260aa3..f2326189 100644
--- a/sway/config/seat.c
+++ b/sway/config/seat.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <limits.h> 1#include <limits.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <string.h> 3#include <string.h>
@@ -99,7 +98,6 @@ static void seat_attachment_config_free(
99 struct seat_attachment_config *attachment) { 98 struct seat_attachment_config *attachment) {
100 free(attachment->identifier); 99 free(attachment->identifier);
101 free(attachment); 100 free(attachment);
102 return;
103} 101}
104 102
105static struct seat_attachment_config *seat_attachment_config_copy( 103static struct seat_attachment_config *seat_attachment_config_copy(
diff --git a/sway/criteria.c b/sway/criteria.c
index 409160c5..e16b4fa8 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -1,9 +1,9 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <stdio.h> 2#include <stdio.h>
4#include <stdbool.h> 3#include <stdbool.h>
5#include <strings.h> 4#include <strings.h>
6#include <pcre.h> 5#define PCRE2_CODE_UNIT_WIDTH 8
6#include <pcre2.h>
7#include "sway/criteria.h" 7#include "sway/criteria.h"
8#include "sway/tree/container.h" 8#include "sway/tree/container.h"
9#include "sway/config.h" 9#include "sway/config.h"
@@ -18,6 +18,7 @@
18bool criteria_is_empty(struct criteria *criteria) { 18bool criteria_is_empty(struct criteria *criteria) {
19 return !criteria->title 19 return !criteria->title
20 && !criteria->shell 20 && !criteria->shell
21 && !criteria->all
21 && !criteria->app_id 22 && !criteria->app_id
22 && !criteria->con_mark 23 && !criteria->con_mark
23 && !criteria->con_id 24 && !criteria->con_id
@@ -40,17 +41,19 @@ bool criteria_is_empty(struct criteria *criteria) {
40char *error = NULL; 41char *error = NULL;
41 42
42// Returns error string on failure or NULL otherwise. 43// Returns error string on failure or NULL otherwise.
43static bool generate_regex(pcre **regex, char *value) { 44static bool generate_regex(pcre2_code **regex, char *value) {
44 const char *reg_err; 45 int errorcode;
45 int offset; 46 PCRE2_SIZE offset;
46
47 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
48 47
48 *regex = pcre2_compile((PCRE2_SPTR)value, PCRE2_ZERO_TERMINATED, PCRE2_UTF | PCRE2_UCP, &errorcode, &offset, NULL);
49 if (!*regex) { 49 if (!*regex) {
50 PCRE2_UCHAR buffer[256];
51 pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
52
50 const char *fmt = "Regex compilation for '%s' failed: %s"; 53 const char *fmt = "Regex compilation for '%s' failed: %s";
51 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; 54 int len = strlen(fmt) + strlen(value) + strlen((char*) buffer) - 3;
52 error = malloc(len); 55 error = malloc(len);
53 snprintf(error, len, fmt, value, reg_err); 56 snprintf(error, len, fmt, value, buffer);
54 return false; 57 return false;
55 } 58 }
56 59
@@ -66,7 +69,7 @@ static bool pattern_create(struct pattern **pattern, char *value) {
66 if (strcmp(value, "__focused__") == 0) { 69 if (strcmp(value, "__focused__") == 0) {
67 (*pattern)->match_type = PATTERN_FOCUSED; 70 (*pattern)->match_type = PATTERN_FOCUSED;
68 } else { 71 } else {
69 (*pattern)->match_type = PATTERN_PCRE; 72 (*pattern)->match_type = PATTERN_PCRE2;
70 if (!generate_regex(&(*pattern)->regex, value)) { 73 if (!generate_regex(&(*pattern)->regex, value)) {
71 return false; 74 return false;
72 }; 75 };
@@ -77,7 +80,7 @@ static bool pattern_create(struct pattern **pattern, char *value) {
77static void pattern_destroy(struct pattern *pattern) { 80static void pattern_destroy(struct pattern *pattern) {
78 if (pattern) { 81 if (pattern) {
79 if (pattern->regex) { 82 if (pattern->regex) {
80 pcre_free(pattern->regex); 83 pcre2_code_free(pattern->regex);
81 } 84 }
82 free(pattern); 85 free(pattern);
83 } 86 }
@@ -93,14 +96,18 @@ void criteria_destroy(struct criteria *criteria) {
93 pattern_destroy(criteria->window_role); 96 pattern_destroy(criteria->window_role);
94#endif 97#endif
95 pattern_destroy(criteria->con_mark); 98 pattern_destroy(criteria->con_mark);
96 free(criteria->workspace); 99 pattern_destroy(criteria->workspace);
100 free(criteria->target);
97 free(criteria->cmdlist); 101 free(criteria->cmdlist);
98 free(criteria->raw); 102 free(criteria->raw);
99 free(criteria); 103 free(criteria);
100} 104}
101 105
102static int regex_cmp(const char *item, const pcre *regex) { 106static int regex_cmp(const char *item, const pcre2_code *regex) {
103 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); 107 pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(regex, NULL);
108 int result = pcre2_match(regex, (PCRE2_SPTR)item, strlen(item), 0, 0, match_data, NULL);
109 pcre2_match_data_free(match_data);
110 return result;
104} 111}
105 112
106#if HAVE_XWAYLAND 113#if HAVE_XWAYLAND
@@ -155,7 +162,7 @@ static bool criteria_matches_container(struct criteria *criteria,
155 bool exists = false; 162 bool exists = false;
156 struct sway_container *con = container; 163 struct sway_container *con = container;
157 for (int i = 0; i < con->marks->length; ++i) { 164 for (int i = 0; i < con->marks->length; ++i) {
158 if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) == 0) { 165 if (regex_cmp(con->marks->items[i], criteria->con_mark->regex) >= 0) {
159 exists = true; 166 exists = true;
160 break; 167 break;
161 } 168 }
@@ -183,7 +190,7 @@ static bool criteria_matches_view(struct criteria *criteria,
183 if (criteria->title) { 190 if (criteria->title) {
184 const char *title = view_get_title(view); 191 const char *title = view_get_title(view);
185 if (!title) { 192 if (!title) {
186 return false; 193 title = "";
187 } 194 }
188 195
189 switch (criteria->title->match_type) { 196 switch (criteria->title->match_type) {
@@ -192,8 +199,8 @@ static bool criteria_matches_view(struct criteria *criteria,
192 return false; 199 return false;
193 } 200 }
194 break; 201 break;
195 case PATTERN_PCRE: 202 case PATTERN_PCRE2:
196 if (regex_cmp(title, criteria->title->regex) != 0) { 203 if (regex_cmp(title, criteria->title->regex) < 0) {
197 return false; 204 return false;
198 } 205 }
199 break; 206 break;
@@ -203,7 +210,7 @@ static bool criteria_matches_view(struct criteria *criteria,
203 if (criteria->shell) { 210 if (criteria->shell) {
204 const char *shell = view_get_shell(view); 211 const char *shell = view_get_shell(view);
205 if (!shell) { 212 if (!shell) {
206 return false; 213 shell = "";
207 } 214 }
208 215
209 switch (criteria->shell->match_type) { 216 switch (criteria->shell->match_type) {
@@ -212,8 +219,8 @@ static bool criteria_matches_view(struct criteria *criteria,
212 return false; 219 return false;
213 } 220 }
214 break; 221 break;
215 case PATTERN_PCRE: 222 case PATTERN_PCRE2:
216 if (regex_cmp(shell, criteria->shell->regex) != 0) { 223 if (regex_cmp(shell, criteria->shell->regex) < 0) {
217 return false; 224 return false;
218 } 225 }
219 break; 226 break;
@@ -223,7 +230,7 @@ static bool criteria_matches_view(struct criteria *criteria,
223 if (criteria->app_id) { 230 if (criteria->app_id) {
224 const char *app_id = view_get_app_id(view); 231 const char *app_id = view_get_app_id(view);
225 if (!app_id) { 232 if (!app_id) {
226 return false; 233 app_id = "";
227 } 234 }
228 235
229 switch (criteria->app_id->match_type) { 236 switch (criteria->app_id->match_type) {
@@ -232,8 +239,8 @@ static bool criteria_matches_view(struct criteria *criteria,
232 return false; 239 return false;
233 } 240 }
234 break; 241 break;
235 case PATTERN_PCRE: 242 case PATTERN_PCRE2:
236 if (regex_cmp(app_id, criteria->app_id->regex) != 0) { 243 if (regex_cmp(app_id, criteria->app_id->regex) < 0) {
237 return false; 244 return false;
238 } 245 }
239 break; 246 break;
@@ -255,7 +262,7 @@ static bool criteria_matches_view(struct criteria *criteria,
255 if (criteria->class) { 262 if (criteria->class) {
256 const char *class = view_get_class(view); 263 const char *class = view_get_class(view);
257 if (!class) { 264 if (!class) {
258 return false; 265 class = "";
259 } 266 }
260 267
261 switch (criteria->class->match_type) { 268 switch (criteria->class->match_type) {
@@ -264,8 +271,8 @@ static bool criteria_matches_view(struct criteria *criteria,
264 return false; 271 return false;
265 } 272 }
266 break; 273 break;
267 case PATTERN_PCRE: 274 case PATTERN_PCRE2:
268 if (regex_cmp(class, criteria->class->regex) != 0) { 275 if (regex_cmp(class, criteria->class->regex) < 0) {
269 return false; 276 return false;
270 } 277 }
271 break; 278 break;
@@ -275,17 +282,17 @@ static bool criteria_matches_view(struct criteria *criteria,
275 if (criteria->instance) { 282 if (criteria->instance) {
276 const char *instance = view_get_instance(view); 283 const char *instance = view_get_instance(view);
277 if (!instance) { 284 if (!instance) {
278 return false; 285 instance = "";
279 } 286 }
280 287
281 switch (criteria->instance->match_type) { 288 switch (criteria->instance->match_type) {
282 case PATTERN_FOCUSED: 289 case PATTERN_FOCUSED:
283 if (focused && strcmp(instance, view_get_instance(focused))) { 290 if (focused && lenient_strcmp(instance, view_get_instance(focused))) {
284 return false; 291 return false;
285 } 292 }
286 break; 293 break;
287 case PATTERN_PCRE: 294 case PATTERN_PCRE2:
288 if (regex_cmp(instance, criteria->instance->regex) != 0) { 295 if (regex_cmp(instance, criteria->instance->regex) < 0) {
289 return false; 296 return false;
290 } 297 }
291 break; 298 break;
@@ -295,17 +302,17 @@ static bool criteria_matches_view(struct criteria *criteria,
295 if (criteria->window_role) { 302 if (criteria->window_role) {
296 const char *window_role = view_get_window_role(view); 303 const char *window_role = view_get_window_role(view);
297 if (!window_role) { 304 if (!window_role) {
298 return false; 305 window_role = "";
299 } 306 }
300 307
301 switch (criteria->window_role->match_type) { 308 switch (criteria->window_role->match_type) {
302 case PATTERN_FOCUSED: 309 case PATTERN_FOCUSED:
303 if (focused && strcmp(window_role, view_get_window_role(focused))) { 310 if (focused && lenient_strcmp(window_role, view_get_window_role(focused))) {
304 return false; 311 return false;
305 } 312 }
306 break; 313 break;
307 case PATTERN_PCRE: 314 case PATTERN_PCRE2:
308 if (regex_cmp(window_role, criteria->window_role->regex) != 0) { 315 if (regex_cmp(window_role, criteria->window_role->regex) < 0) {
309 return false; 316 return false;
310 } 317 }
311 break; 318 break;
@@ -351,7 +358,7 @@ static bool criteria_matches_view(struct criteria *criteria,
351 } 358 }
352 359
353 if (criteria->workspace) { 360 if (criteria->workspace) {
354 struct sway_workspace *ws = view->container->workspace; 361 struct sway_workspace *ws = view->container->pending.workspace;
355 if (!ws) { 362 if (!ws) {
356 return false; 363 return false;
357 } 364 }
@@ -359,12 +366,12 @@ static bool criteria_matches_view(struct criteria *criteria,
359 switch (criteria->workspace->match_type) { 366 switch (criteria->workspace->match_type) {
360 case PATTERN_FOCUSED: 367 case PATTERN_FOCUSED:
361 if (focused && 368 if (focused &&
362 strcmp(ws->name, focused->container->workspace->name)) { 369 strcmp(ws->name, focused->container->pending.workspace->name)) {
363 return false; 370 return false;
364 } 371 }
365 break; 372 break;
366 case PATTERN_PCRE: 373 case PATTERN_PCRE2:
367 if (regex_cmp(ws->name, criteria->workspace->regex) != 0) { 374 if (regex_cmp(ws->name, criteria->workspace->regex) < 0) {
368 return false; 375 return false;
369 } 376 }
370 break; 377 break;
@@ -449,6 +456,7 @@ static enum atom_name parse_window_type(const char *type) {
449#endif 456#endif
450 457
451enum criteria_token { 458enum criteria_token {
459 T_ALL,
452 T_APP_ID, 460 T_APP_ID,
453 T_CON_ID, 461 T_CON_ID,
454 T_CON_MARK, 462 T_CON_MARK,
@@ -471,7 +479,9 @@ enum criteria_token {
471}; 479};
472 480
473static enum criteria_token token_from_name(char *name) { 481static enum criteria_token token_from_name(char *name) {
474 if (strcmp(name, "app_id") == 0) { 482 if (strcmp(name, "all") == 0) {
483 return T_ALL;
484 } else if (strcmp(name, "app_id") == 0) {
475 return T_APP_ID; 485 return T_APP_ID;
476 } else if (strcmp(name, "con_id") == 0) { 486 } else if (strcmp(name, "con_id") == 0) {
477 return T_CON_ID; 487 return T_CON_ID;
@@ -517,8 +527,8 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
517 return false; 527 return false;
518 } 528 }
519 529
520 // Require value, unless token is floating or tiled 530 // Require value, unless token is all, floating or tiled
521 if (!value && token != T_FLOATING && token != T_TILING) { 531 if (!value && token != T_ALL && token != T_FLOATING && token != T_TILING) {
522 const char *fmt = "Token '%s' requires a value"; 532 const char *fmt = "Token '%s' requires a value";
523 int len = strlen(fmt) + strlen(name) - 1; 533 int len = strlen(fmt) + strlen(name) - 1;
524 error = malloc(len); 534 error = malloc(len);
@@ -528,6 +538,9 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
528 538
529 char *endptr = NULL; 539 char *endptr = NULL;
530 switch (token) { 540 switch (token) {
541 case T_ALL:
542 criteria->all = true;
543 break;
531 case T_TITLE: 544 case T_TITLE:
532 pattern_create(&criteria->title, value); 545 pattern_create(&criteria->title, value);
533 break; 546 break;
@@ -676,7 +689,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
676 } 689 }
677 name = calloc(head - namestart + 1, 1); 690 name = calloc(head - namestart + 1, 1);
678 if (head != namestart) { 691 if (head != namestart) {
679 strncpy(name, namestart, head - namestart); 692 memcpy(name, namestart, head - namestart);
680 } 693 }
681 // Parse token value 694 // Parse token value
682 skip_spaces(&head); 695 skip_spaces(&head);
@@ -703,7 +716,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
703 } 716 }
704 } 717 }
705 value = calloc(head - valuestart + 1, 1); 718 value = calloc(head - valuestart + 1, 1);
706 strncpy(value, valuestart, head - valuestart); 719 memcpy(value, valuestart, head - valuestart);
707 if (in_quotes) { 720 if (in_quotes) {
708 ++head; 721 ++head;
709 in_quotes = false; 722 in_quotes = false;
@@ -734,7 +747,7 @@ struct criteria *criteria_parse(char *raw, char **error_arg) {
734 ++head; 747 ++head;
735 int len = head - raw; 748 int len = head - raw;
736 criteria->raw = calloc(len + 1, 1); 749 criteria->raw = calloc(len + 1, 1);
737 strncpy(criteria->raw, raw, len); 750 memcpy(criteria->raw, raw, len);
738 return criteria; 751 return criteria;
739 752
740cleanup: 753cleanup:
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..28043d19
--- /dev/null
+++ b/sway/desktop/launcher.c
@@ -0,0 +1,267 @@
1#include <stdlib.h>
2#include <string.h>
3#include <wlr/types/wlr_xdg_activation_v1.h>
4#include "sway/input/seat.h"
5#include "sway/output.h"
6#include "sway/desktop/launcher.h"
7#include "sway/tree/node.h"
8#include "sway/tree/container.h"
9#include "sway/tree/workspace.h"
10#include "sway/tree/root.h"
11#include "log.h"
12
13/**
14 * Get the pid of a parent process given the pid of a child process.
15 *
16 * Returns the parent pid or NULL if the parent pid cannot be determined.
17 */
18static pid_t get_parent_pid(pid_t child) {
19 pid_t parent = -1;
20 char file_name[100];
21 char *buffer = NULL;
22 const char *sep = " ";
23 FILE *stat = NULL;
24 size_t buf_size = 0;
25
26 snprintf(file_name, sizeof(file_name), "/proc/%d/stat", child);
27
28 if ((stat = fopen(file_name, "r"))) {
29 if (getline(&buffer, &buf_size, stat) != -1) {
30 strtok(buffer, sep); // pid
31 strtok(NULL, sep); // executable name
32 strtok(NULL, sep); // state
33 char *token = strtok(NULL, sep); // parent pid
34 parent = strtol(token, NULL, 10);
35 }
36 free(buffer);
37 fclose(stat);
38 }
39
40 if (parent) {
41 return (parent == child) ? -1 : parent;
42 }
43
44 return -1;
45}
46
47void launcher_ctx_consume(struct launcher_ctx *ctx) {
48 // The view is now responsible for destroying this ctx
49 wl_list_remove(&ctx->token_destroy.link);
50 wl_list_init(&ctx->token_destroy.link);
51
52 if (!ctx->activated) {
53 // An unactivated token hasn't been destroyed yet
54 wlr_xdg_activation_token_v1_destroy(ctx->token);
55 }
56 ctx->token = NULL;
57
58 // Prevent additional matches
59 wl_list_remove(&ctx->link);
60 wl_list_init(&ctx->link);
61}
62
63void launcher_ctx_destroy(struct launcher_ctx *ctx) {
64 if (ctx == NULL) {
65 return;
66 }
67 wl_list_remove(&ctx->node_destroy.link);
68 wl_list_remove(&ctx->token_destroy.link);
69 if (ctx->seat) {
70 wl_list_remove(&ctx->seat_destroy.link);
71 }
72 wl_list_remove(&ctx->link);
73 wlr_xdg_activation_token_v1_destroy(ctx->token);
74 free(ctx->fallback_name);
75 free(ctx);
76}
77
78struct launcher_ctx *launcher_ctx_find_pid(pid_t pid) {
79 if (wl_list_empty(&server.pending_launcher_ctxs)) {
80 return NULL;
81 }
82
83 struct launcher_ctx *ctx = NULL;
84 sway_log(SWAY_DEBUG, "Looking up workspace for pid %d", pid);
85
86 do {
87 struct launcher_ctx *_ctx = NULL;
88 wl_list_for_each(_ctx, &server.pending_launcher_ctxs, link) {
89 if (pid == _ctx->pid) {
90 ctx = _ctx;
91 sway_log(SWAY_DEBUG,
92 "found %s match for pid %d: %s",
93 node_type_to_str(ctx->node->type), pid, node_get_name(ctx->node));
94 break;
95 }
96 }
97 pid = get_parent_pid(pid);
98 } while (pid > 1);
99
100 return ctx;
101}
102
103struct sway_workspace *launcher_ctx_get_workspace(
104 struct launcher_ctx *ctx) {
105 struct sway_workspace *ws = NULL;
106 struct sway_output *output = NULL;
107
108 switch (ctx->node->type) {
109 case N_CONTAINER:
110 // Unimplemented
111 // TODO: add container matching?
112 ws = ctx->node->sway_container->pending.workspace;
113 break;
114 case N_WORKSPACE:
115 ws = ctx->node->sway_workspace;
116 break;
117 case N_OUTPUT:
118 output = ctx->node->sway_output;
119 ws = workspace_by_name(ctx->fallback_name);
120 if (!ws) {
121 sway_log(SWAY_DEBUG,
122 "Creating workspace %s for pid %d because it disappeared",
123 ctx->fallback_name, ctx->pid);
124 if (!output->enabled) {
125 sway_log(SWAY_DEBUG,
126 "Workspace output %s is disabled, trying another one",
127 output->wlr_output->name);
128 output = NULL;
129 }
130 ws = workspace_create(output, ctx->fallback_name);
131 }
132 break;
133 case N_ROOT:
134 ws = workspace_create(NULL, ctx->fallback_name);
135 break;
136 }
137
138 return ws;
139}
140
141static void ctx_handle_node_destroy(struct wl_listener *listener, void *data) {
142 struct launcher_ctx *ctx = wl_container_of(listener, ctx, node_destroy);
143 switch (ctx->node->type) {
144 case N_CONTAINER:
145 // Unimplemented
146 break;
147 case N_WORKSPACE:;
148 struct sway_workspace *ws = ctx->node->sway_workspace;
149 wl_list_remove(&ctx->node_destroy.link);
150 wl_list_init(&ctx->node_destroy.link);
151 // We want to save this ws name to recreate later, hopefully on the
152 // same output
153 free(ctx->fallback_name);
154 ctx->fallback_name = strdup(ws->name);
155 if (!ws->output || ws->output->node.destroying) {
156 // If the output is being destroyed it would be pointless to track
157 // If the output is being disabled, we'll find out if it's still
158 // disabled when we try to match it.
159 ctx->node = &root->node;
160 break;
161 }
162 ctx->node = &ws->output->node;
163 wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy);
164 break;
165 case N_OUTPUT:
166 wl_list_remove(&ctx->node_destroy.link);
167 wl_list_init(&ctx->node_destroy.link);
168 // We'll make the ws ctx->name somewhere else
169 ctx->node = &root->node;
170 break;
171 case N_ROOT:
172 // Unreachable
173 break;
174 }
175}
176
177static void token_handle_destroy(struct wl_listener *listener, void *data) {
178 struct launcher_ctx *ctx = wl_container_of(listener, ctx, token_destroy);
179 ctx->token = NULL;
180 launcher_ctx_destroy(ctx);
181}
182
183struct launcher_ctx *launcher_ctx_create(struct wlr_xdg_activation_token_v1 *token,
184 struct sway_node *node) {
185 struct launcher_ctx *ctx = calloc(1, sizeof(struct launcher_ctx));
186
187 const char *fallback_name = NULL;
188 struct sway_workspace *ws = NULL;
189 switch (node->type) {
190 case N_CONTAINER:
191 // Unimplemented
192 free(ctx);
193 return NULL;
194 case N_WORKSPACE:
195 ws = node->sway_workspace;
196 fallback_name = ws->name;
197 break;
198 case N_OUTPUT:;
199 struct sway_output *output = node->sway_output;
200 ws = output_get_active_workspace(output);
201 fallback_name = ws ? ws->name : NULL;
202 break;
203 case N_ROOT:
204 // Unimplemented
205 free(ctx);
206 return NULL;
207 }
208
209 if (!fallback_name) {
210 // TODO: implement a better fallback.
211 free(ctx);
212 return NULL;
213 }
214
215 ctx->fallback_name = strdup(fallback_name);
216 ctx->token = token;
217 ctx->node = node;
218 // Having surface set means that the focus check in wlroots has passed
219 ctx->had_focused_surface = token->surface != NULL;
220
221 ctx->node_destroy.notify = ctx_handle_node_destroy;
222 wl_signal_add(&ctx->node->events.destroy, &ctx->node_destroy);
223
224 ctx->token_destroy.notify = token_handle_destroy;
225 wl_signal_add(&token->events.destroy, &ctx->token_destroy);
226
227 wl_list_init(&ctx->link);
228 wl_list_insert(&server.pending_launcher_ctxs, &ctx->link);
229
230 token->data = ctx;
231 return ctx;
232}
233
234static void launch_ctx_handle_seat_destroy(struct wl_listener *listener, void *data) {
235 struct launcher_ctx *ctx = wl_container_of(listener, ctx, seat_destroy);
236 ctx->seat = NULL;
237 wl_list_remove(&ctx->seat_destroy.link);
238}
239
240// Creates a context with a new token for the internal launcher
241struct launcher_ctx *launcher_ctx_create_internal(void) {
242 struct sway_seat *seat = input_manager_current_seat();
243 struct sway_workspace *ws = seat_get_focused_workspace(seat);
244 if (!ws) {
245 sway_log(SWAY_DEBUG, "Failed to create launch context. No workspace.");
246 return NULL;
247 }
248
249 struct wlr_xdg_activation_token_v1 *token =
250 wlr_xdg_activation_token_v1_create(server.xdg_activation_v1);
251
252 struct launcher_ctx *ctx = launcher_ctx_create(token, &ws->node);
253 if (!ctx) {
254 wlr_xdg_activation_token_v1_destroy(token);
255 return NULL;
256 }
257 ctx->seat = seat;
258 ctx->seat_destroy.notify = launch_ctx_handle_seat_destroy;
259 wl_signal_add(&seat->wlr_seat->events.destroy, &ctx->seat_destroy);
260
261 return ctx;
262}
263
264const char *launcher_ctx_get_token_name(struct launcher_ctx *ctx) {
265 const char *token = wlr_xdg_activation_token_v1_get_name(ctx->token);
266 return token;
267}
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index d4ca4fb4..4b2584b6 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -2,11 +2,13 @@
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_scene.h>
8#include <wlr/types/wlr_subcompositor.h>
9#include <wlr/types/wlr_xdg_shell.h>
9#include "log.h" 10#include "log.h"
11#include "sway/scene_descriptor.h"
10#include "sway/desktop/transaction.h" 12#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 13#include "sway/input/cursor.h"
12#include "sway/input/input-manager.h" 14#include "sway/input/input-manager.h"
@@ -17,155 +19,55 @@
17#include "sway/tree/arrange.h" 19#include "sway/tree/arrange.h"
18#include "sway/tree/workspace.h" 20#include "sway/tree/workspace.h"
19 21
20static void apply_exclusive(struct wlr_box *usable_area, 22struct wlr_layer_surface_v1 *toplevel_layer_surface_from_surface(
21 uint32_t anchor, int32_t exclusive, 23 struct wlr_surface *surface) {
22 int32_t margin_top, int32_t margin_right, 24 struct wlr_layer_surface_v1 *layer;
23 int32_t margin_bottom, int32_t margin_left) { 25 do {
24 if (exclusive <= 0) { 26 if (!surface) {
25 return; 27 return NULL;
26 } 28 }
27 struct { 29 // Topmost layer surface
28 uint32_t singular_anchor; 30 if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface))) {
29 uint32_t anchor_triplet; 31 return layer;
30 int *positive_axis; 32 }
31 int *negative_axis; 33 // Layer subsurface
32 int margin; 34 if (wlr_subsurface_try_from_wlr_surface(surface)) {
33 } edges[] = { 35 surface = wlr_surface_get_root_surface(surface);
34 // Top 36 continue;
35 { 37 }
36 .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 38
37 .anchor_triplet = 39 // Layer surface popup
38 ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | 40 struct wlr_xdg_surface *xdg_surface = NULL;
39 ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | 41 if ((xdg_surface = wlr_xdg_surface_try_from_wlr_surface(surface)) &&
40 ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, 42 xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP && xdg_surface->popup != NULL) {
41 .positive_axis = &usable_area->y, 43 if (!xdg_surface->popup->parent) {
42 .negative_axis = &usable_area->height, 44 return NULL;
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 }
85 if (edges[i].negative_axis) {
86 *edges[i].negative_axis -= exclusive + edges[i].margin;
87 } 45 }
88 break; 46 surface = wlr_surface_get_root_surface(xdg_surface->popup->parent);
47 continue;
89 } 48 }
90 } 49
50 // Return early if the surface is not a layer/xdg_popup/sub surface
51 return NULL;
52 } while (true);
91} 53}
92 54
93static void arrange_layer(struct sway_output *output, struct wl_list *list, 55static void arrange_surface(struct sway_output *output, const struct wlr_box *full_area,
94 struct wlr_box *usable_area, bool exclusive) { 56 struct wlr_box *usable_area, struct wlr_scene_tree *tree) {
95 struct sway_layer_surface *sway_layer; 57 struct wlr_scene_node *node;
96 struct wlr_box full_area = { 0 }; 58 wl_list_for_each(node, &tree->children, link) {
97 wlr_output_effective_resolution(output->wlr_output, 59 struct sway_layer_surface *surface = scene_descriptor_try_get(node,
98 &full_area.width, &full_area.height); 60 SWAY_SCENE_DESC_LAYER_SHELL);
99 wl_list_for_each(sway_layer, list, link) { 61 // surface could be null during destruction
100 struct wlr_layer_surface_v1 *layer = sway_layer->layer_surface; 62 if (!surface) {
101 struct wlr_layer_surface_v1_state *state = &layer->current;
102 if (exclusive != (state->exclusive_zone > 0)) {
103 continue; 63 continue;
104 } 64 }
105 struct wlr_box bounds; 65
106 if (state->exclusive_zone == -1) { 66 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; 67 continue;
162 } 68 }
163 // Apply 69
164 sway_layer->geo = box; 70 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 } 71 }
170} 72}
171 73
@@ -173,81 +75,94 @@ void arrange_layers(struct sway_output *output) {
173 struct wlr_box usable_area = { 0 }; 75 struct wlr_box usable_area = { 0 };
174 wlr_output_effective_resolution(output->wlr_output, 76 wlr_output_effective_resolution(output->wlr_output,
175 &usable_area.width, &usable_area.height); 77 &usable_area.width, &usable_area.height);
78 const struct wlr_box full_area = usable_area;
79
80 arrange_surface(output, &full_area, &usable_area, output->layers.shell_background);
81 arrange_surface(output, &full_area, &usable_area, output->layers.shell_bottom);
82 arrange_surface(output, &full_area, &usable_area, output->layers.shell_top);
83 arrange_surface(output, &full_area, &usable_area, output->layers.shell_overlay);
176 84
177 // Arrange exclusive surfaces from top->bottom 85 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"); 86 sway_log(SWAY_DEBUG, "Usable area changed, rearranging output");
190 memcpy(&output->usable_area, &usable_area, sizeof(struct wlr_box)); 87 output->usable_area = usable_area;
191 arrange_output(output); 88 arrange_output(output);
89 } else {
90 arrange_popups(root->layers.popup);
192 } 91 }
92}
193 93
194 // Arrange non-exlusive surfaces from top->bottom 94static struct wlr_scene_tree *sway_layer_get_scene(struct sway_output *output,
195 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], 95 enum zwlr_layer_shell_v1_layer type) {
196 &usable_area, false); 96 switch (type) {
197 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], 97 case ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND:
198 &usable_area, false); 98 return output->layers.shell_background;
199 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], 99 case ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM:
200 &usable_area, false); 100 return output->layers.shell_bottom;
201 arrange_layer(output, &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], 101 case ZWLR_LAYER_SHELL_V1_LAYER_TOP:
202 &usable_area, false); 102 return output->layers.shell_top;
203 103 case ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY:
204 // Find topmost keyboard interactive layer, if such a layer exists 104 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 } 105 }
224 106
225 struct sway_seat *seat; 107 sway_assert(false, "unreachable");
226 wl_list_for_each(seat, &server.input->seats, link) { 108 return NULL;
227 if (topmost != NULL) { 109}
228 seat_set_focus_layer(seat, topmost->layer_surface); 110
229 } else if (seat->focused_layer && 111static struct sway_layer_surface *sway_layer_surface_create(
230 !seat->focused_layer->current.keyboard_interactive) { 112 struct wlr_scene_layer_surface_v1 *scene) {
231 seat_set_focus_layer(seat, NULL); 113 struct sway_layer_surface *surface = calloc(1, sizeof(*surface));
232 } 114 if (!surface) {
115 sway_log(SWAY_ERROR, "Could not allocate a scene_layer surface");
116 return NULL;
233 } 117 }
118
119 struct wlr_scene_tree *popups = wlr_scene_tree_create(root->layers.popup);
120 if (!popups) {
121 sway_log(SWAY_ERROR, "Could not allocate a scene_layer popup node");
122 free(surface);
123 return NULL;
124 }
125
126 surface->desc.relative = &scene->tree->node;
127
128 if (!scene_descriptor_assign(&popups->node,
129 SWAY_SCENE_DESC_POPUP, &surface->desc)) {
130 sway_log(SWAY_ERROR, "Failed to allocate a popup scene descriptor");
131 wlr_scene_node_destroy(&popups->node);
132 free(surface);
133 return NULL;
134 }
135
136 surface->tree = scene->tree;
137 surface->scene = scene;
138 surface->layer_surface = scene->layer_surface;
139 surface->popups = popups;
140 surface->layer_surface->data = surface;
141
142 return surface;
234} 143}
235 144
236static struct sway_layer_surface *find_mapped_layer_by_client( 145static struct sway_layer_surface *find_mapped_layer_by_client(
237 struct wl_client *client, struct wlr_output *ignore_output) { 146 struct wl_client *client, struct sway_output *ignore_output) {
238 for (int i = 0; i < root->outputs->length; ++i) { 147 for (int i = 0; i < root->outputs->length; ++i) {
239 struct sway_output *output = root->outputs->items[i]; 148 struct sway_output *output = root->outputs->items[i];
240 if (output->wlr_output == ignore_output) { 149 if (output == ignore_output) {
241 continue; 150 continue;
242 } 151 }
243 // For now we'll only check the overlay layer 152 // For now we'll only check the overlay layer
244 struct sway_layer_surface *lsurface; 153 struct wlr_scene_node *node;
245 wl_list_for_each(lsurface, 154 wl_list_for_each (node, &output->layers.shell_overlay->children, link) {
246 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], link) { 155 struct sway_layer_surface *surface = scene_descriptor_try_get(node,
247 struct wl_resource *resource = lsurface->layer_surface->resource; 156 SWAY_SCENE_DESC_LAYER_SHELL);
157 if (!surface) {
158 continue;
159 }
160
161 struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface;
162 struct wl_resource *resource = layer_surface->resource;
248 if (wl_resource_get_client(resource) == client 163 if (wl_resource_get_client(resource) == client
249 && lsurface->layer_surface->mapped) { 164 && layer_surface->surface->mapped) {
250 return lsurface; 165 return surface;
251 } 166 }
252 } 167 }
253 } 168 }
@@ -255,280 +170,142 @@ static struct sway_layer_surface *find_mapped_layer_by_client(
255} 170}
256 171
257static void handle_output_destroy(struct wl_listener *listener, void *data) { 172static void handle_output_destroy(struct wl_listener *listener, void *data) {
258 struct sway_layer_surface *sway_layer = 173 struct sway_layer_surface *layer =
259 wl_container_of(listener, sway_layer, output_destroy); 174 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 175
279 sway_layer->layer_surface->output = NULL; 176 layer->output = NULL;
280 wlr_layer_surface_v1_close(sway_layer->layer_surface); 177 wlr_scene_node_destroy(&layer->scene->tree->node);
281} 178}
282 179
283static void handle_surface_commit(struct wl_listener *listener, void *data) { 180static void handle_node_destroy(struct wl_listener *listener, void *data) {
284 struct sway_layer_surface *layer = 181 struct sway_layer_surface *layer =
285 wl_container_of(listener, layer, surface_commit); 182 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 183
292 struct sway_output *output = wlr_output->data; 184 // destroy the scene descriptor straight away if it exists, otherwise
293 struct wlr_box old_geo = layer->geo; 185 // we will try to reflow still considering the destroyed node.
294 arrange_layers(output); 186 scene_descriptor_destroy(&layer->tree->node, SWAY_SCENE_DESC_LAYER_SHELL);
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
315 transaction_commit_dirty();
316}
317 187
318static void unmap(struct sway_layer_surface *sway_layer) { 188 // Determine if this layer is being used by an exclusive client. If it is,
319 struct sway_seat *seat; 189 // try and find another layer owned by this client to pass focus to.
320 wl_list_for_each(seat, &server.input->seats, link) { 190 struct sway_seat *seat = input_manager_get_default_seat();
321 if (seat->focused_layer == sway_layer->layer_surface) { 191 struct wl_client *client =
322 seat_set_focus_layer(seat, NULL); 192 wl_resource_get_client(layer->layer_surface->resource);
193 if (!server.session_lock.lock) {
194 struct sway_layer_surface *consider_layer =
195 find_mapped_layer_by_client(client, layer->output);
196 if (consider_layer) {
197 seat_set_focus_layer(seat, consider_layer->layer_surface);
323 } 198 }
324 } 199 }
325 200
326 cursor_rebase_all(); 201 if (layer->output) {
327 202 arrange_layers(layer->output);
328 struct wlr_output *wlr_output = sway_layer->layer_surface->output; 203 transaction_commit_dirty();
329 if (wlr_output == NULL) {
330 return;
331 }
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
340static void handle_destroy(struct wl_listener *listener, void *data) {
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 } 204 }
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 205
367static void handle_map(struct wl_listener *listener, void *data) { 206 wlr_scene_node_destroy(&layer->popups->node);
368 struct sway_layer_surface *sway_layer = wl_container_of(listener,
369 sway_layer, map);
370 struct sway_output *output = sway_layer->layer_surface->output->data;
371 output_damage_surface(output, sway_layer->geo.x, sway_layer->geo.y,
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 207
378static void handle_unmap(struct wl_listener *listener, void *data) { 208 wl_list_remove(&layer->map.link);
379 struct sway_layer_surface *sway_layer = wl_container_of( 209 wl_list_remove(&layer->unmap.link);
380 listener, sway_layer, unmap); 210 wl_list_remove(&layer->surface_commit.link);
381 unmap(sway_layer); 211 wl_list_remove(&layer->node_destroy.link);
382} 212 wl_list_remove(&layer->output_destroy.link);
383 213
384static void subsurface_damage(struct sway_layer_subsurface *subsurface, 214 layer->layer_surface->data = NULL;
385 bool whole) {
386 struct sway_layer_surface *layer = subsurface->layer_surface;
387 struct wlr_output *wlr_output = layer->layer_surface->output;
388 if (!wlr_output) {
389 return;
390 }
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 215
404static void subsurface_handle_map(struct wl_listener *listener, void *data) { 216 free(layer);
405 struct sway_layer_subsurface *subsurface =
406 wl_container_of(listener, subsurface, map);
407 subsurface_damage(subsurface, true);
408} 217}
409 218
410static void subsurface_handle_commit(struct wl_listener *listener, void *data) { 219static void handle_surface_commit(struct wl_listener *listener, void *data) {
411 struct sway_layer_subsurface *subsurface = 220 struct sway_layer_surface *surface =
412 wl_container_of(listener, subsurface, commit); 221 wl_container_of(listener, surface, surface_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 222
428static struct sway_layer_subsurface *create_subsurface( 223 struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface;
429 struct wlr_subsurface *wlr_subsurface, 224 if (!layer_surface->initialized) {
430 struct sway_layer_surface *layer_surface) { 225 return;
431 struct sway_layer_subsurface *subsurface =
432 calloc(1, sizeof(struct sway_layer_surface));
433 if (subsurface == NULL) {
434 return NULL;
435 } 226 }
436 227
437 subsurface->wlr_subsurface = wlr_subsurface; 228 uint32_t committed = layer_surface->current.committed;
438 subsurface->layer_surface = layer_surface; 229 if (committed & WLR_LAYER_SURFACE_V1_STATE_LAYER) {
439 230 enum zwlr_layer_shell_v1_layer layer_type = layer_surface->current.layer;
440 subsurface->map.notify = subsurface_handle_map; 231 struct wlr_scene_tree *output_layer = sway_layer_get_scene(
441 wl_signal_add(&wlr_subsurface->events.map, &subsurface->map); 232 surface->output, layer_type);
442 subsurface->unmap.notify = subsurface_handle_unmap; 233 wlr_scene_node_reparent(&surface->scene->tree->node, output_layer);
443 wl_signal_add(&wlr_subsurface->events.unmap, &subsurface->unmap); 234 }
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 235
460static struct sway_layer_surface *popup_get_layer( 236 if (layer_surface->initial_commit || committed || layer_surface->surface->mapped != surface->mapped) {
461 struct sway_layer_popup *popup) { 237 surface->mapped = layer_surface->surface->mapped;
462 while (popup->parent_type == LAYER_PARENT_POPUP) { 238 arrange_layers(surface->output);
463 popup = popup->parent_popup; 239 transaction_commit_dirty();
464 } 240 }
465 return popup->parent_layer;
466} 241}
467 242
468static void popup_damage(struct sway_layer_popup *layer_popup, bool whole) { 243static void handle_map(struct wl_listener *listener, void *data) {
469 struct wlr_xdg_popup *popup = layer_popup->wlr_popup; 244 struct sway_layer_surface *surface = wl_container_of(listener,
470 struct wlr_surface *surface = popup->base->surface; 245 surface, map);
471 int popup_sx = popup->geometry.x - popup->base->geometry.x; 246
472 int popup_sy = popup->geometry.y - popup->base->geometry.y; 247 struct wlr_layer_surface_v1 *layer_surface =
473 int ox = popup_sx, oy = popup_sy; 248 surface->scene->layer_surface;
474 struct sway_layer_surface *layer; 249
475 while (true) { 250 // focus on new surface
476 if (layer_popup->parent_type == LAYER_PARENT_POPUP) { 251 if (layer_surface->current.keyboard_interactive &&
477 layer_popup = layer_popup->parent_popup; 252 (layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ||
478 ox += layer_popup->wlr_popup->geometry.x; 253 layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_TOP)) {
479 oy += layer_popup->wlr_popup->geometry.y; 254 struct sway_seat *seat;
480 } else { 255 wl_list_for_each(seat, &server.input->seats, link) {
481 layer = layer_popup->parent_layer; 256 // but only if the currently focused layer has a lower precedence
482 ox += layer->geo.x; 257 if (!seat->focused_layer ||
483 oy += layer->geo.y; 258 seat->focused_layer->current.layer >= layer_surface->current.layer) {
484 break; 259 seat_set_focus_layer(seat, layer_surface);
260 }
485 } 261 }
262 arrange_layers(surface->output);
486 } 263 }
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 264
492static void popup_handle_map(struct wl_listener *listener, void *data) { 265 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} 266}
499 267
500static void popup_handle_unmap(struct wl_listener *listener, void *data) { 268static void handle_unmap(struct wl_listener *listener, void *data) {
501 struct sway_layer_popup *popup = wl_container_of(listener, popup, unmap); 269 struct sway_layer_surface *surface = wl_container_of(
502 popup_damage(popup, true); 270 listener, surface, unmap);
503} 271 struct sway_seat *seat;
272 wl_list_for_each(seat, &server.input->seats, link) {
273 if (seat->focused_layer == surface->layer_surface) {
274 seat_set_focus_layer(seat, NULL);
275 }
276 }
504 277
505static void popup_handle_commit(struct wl_listener *listener, void *data) { 278 cursor_rebase_all();
506 struct sway_layer_popup *popup = wl_container_of(listener, popup, commit);
507 popup_damage(popup, false);
508} 279}
509 280
510static void popup_handle_destroy(struct wl_listener *listener, void *data) { 281static void popup_handle_destroy(struct wl_listener *listener, void *data) {
511 struct sway_layer_popup *popup = 282 struct sway_layer_popup *popup =
512 wl_container_of(listener, popup, destroy); 283 wl_container_of(listener, popup, destroy);
513 284
514 wl_list_remove(&popup->map.link);
515 wl_list_remove(&popup->unmap.link);
516 wl_list_remove(&popup->destroy.link); 285 wl_list_remove(&popup->destroy.link);
286 wl_list_remove(&popup->new_popup.link);
517 wl_list_remove(&popup->commit.link); 287 wl_list_remove(&popup->commit.link);
518 free(popup); 288 free(popup);
519} 289}
520 290
521static void popup_unconstrain(struct sway_layer_popup *popup) { 291static 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; 292 struct wlr_xdg_popup *wlr_popup = popup->wlr_popup;
293 struct sway_output *output = popup->toplevel->output;
524 294
525 struct sway_output *output = layer->layer_surface->output->data; 295 // if a client tries to create a popup while we are in the process of destroying
296 // its output, don't crash.
297 if (!output) {
298 return;
299 }
300
301 int lx, ly;
302 wlr_scene_node_coords(&popup->toplevel->scene->tree->node, &lx, &ly);
526 303
527 // the output box expressed in the coordinate system of the toplevel parent 304 // the output box expressed in the coordinate system of the toplevel parent
528 // of the popup 305 // of the popup
529 struct wlr_box output_toplevel_sx_box = { 306 struct wlr_box output_toplevel_sx_box = {
530 .x = -layer->geo.x, 307 .x = output->lx - lx,
531 .y = -layer->geo.y, 308 .y = output->ly - ly,
532 .width = output->width, 309 .width = output->width,
533 .height = output->height, 310 .height = output->height,
534 }; 311 };
@@ -536,32 +313,38 @@ static void popup_unconstrain(struct sway_layer_popup *popup) {
536 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box); 313 wlr_xdg_popup_unconstrain_from_box(wlr_popup, &output_toplevel_sx_box);
537} 314}
538 315
316static void popup_handle_commit(struct wl_listener *listener, void *data) {
317 struct sway_layer_popup *popup = wl_container_of(listener, popup, commit);
318 if (popup->wlr_popup->base->initial_commit) {
319 popup_unconstrain(popup);
320 }
321}
322
539static void popup_handle_new_popup(struct wl_listener *listener, void *data); 323static void popup_handle_new_popup(struct wl_listener *listener, void *data);
540 324
541static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup, 325static struct sway_layer_popup *create_popup(struct wlr_xdg_popup *wlr_popup,
542 enum layer_parent parent_type, void *parent) { 326 struct sway_layer_surface *toplevel, struct wlr_scene_tree *parent) {
543 struct sway_layer_popup *popup = 327 struct sway_layer_popup *popup = calloc(1, sizeof(*popup));
544 calloc(1, sizeof(struct sway_layer_popup));
545 if (popup == NULL) { 328 if (popup == NULL) {
546 return NULL; 329 return NULL;
547 } 330 }
548 331
332 popup->toplevel = toplevel;
549 popup->wlr_popup = wlr_popup; 333 popup->wlr_popup = wlr_popup;
550 popup->parent_type = parent_type; 334 popup->scene = wlr_scene_xdg_surface_create(parent,
551 popup->parent_layer = parent; 335 wlr_popup->base);
336
337 if (!popup->scene) {
338 free(popup);
339 return NULL;
340 }
552 341
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; 342 popup->destroy.notify = popup_handle_destroy;
558 wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); 343 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; 344 popup->new_popup.notify = popup_handle_new_popup;
562 wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); 345 wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup);
563 346 popup->commit.notify = popup_handle_commit;
564 popup_unconstrain(popup); 347 wl_signal_add(&wlr_popup->base->surface->events.commit, &popup->commit);
565 348
566 return popup; 349 return popup;
567} 350}
@@ -570,19 +353,14 @@ static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
570 struct sway_layer_popup *sway_layer_popup = 353 struct sway_layer_popup *sway_layer_popup =
571 wl_container_of(listener, sway_layer_popup, new_popup); 354 wl_container_of(listener, sway_layer_popup, new_popup);
572 struct wlr_xdg_popup *wlr_popup = data; 355 struct wlr_xdg_popup *wlr_popup = data;
573 create_popup(wlr_popup, LAYER_PARENT_POPUP, sway_layer_popup); 356 create_popup(wlr_popup, sway_layer_popup->toplevel, sway_layer_popup->scene);
574} 357}
575 358
576static void handle_new_popup(struct wl_listener *listener, void *data) { 359static void handle_new_popup(struct wl_listener *listener, void *data) {
577 struct sway_layer_surface *sway_layer_surface = 360 struct sway_layer_surface *sway_layer_surface =
578 wl_container_of(listener, sway_layer_surface, new_popup); 361 wl_container_of(listener, sway_layer_surface, new_popup);
579 struct wlr_xdg_popup *wlr_popup = data; 362 struct wlr_xdg_popup *wlr_popup = data;
580 create_popup(wlr_popup, LAYER_PARENT_LAYER, sway_layer_surface); 363 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} 364}
587 365
588void handle_layer_shell_surface(struct wl_listener *listener, void *data) { 366void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
@@ -590,14 +368,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 368 sway_log(SWAY_DEBUG, "new layer surface: namespace %s layer %d anchor %" PRIu32
591 " size %" PRIu32 "x%" PRIu32 " margin %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",", 369 " size %" PRIu32 "x%" PRIu32 " margin %" PRIu32 ",%" PRIu32 ",%" PRIu32 ",%" PRIu32 ",",
592 layer_surface->namespace, 370 layer_surface->namespace,
593 layer_surface->client_pending.layer, 371 layer_surface->pending.layer,
594 layer_surface->client_pending.anchor, 372 layer_surface->pending.anchor,
595 layer_surface->client_pending.desired_width, 373 layer_surface->pending.desired_width,
596 layer_surface->client_pending.desired_height, 374 layer_surface->pending.desired_height,
597 layer_surface->client_pending.margin.top, 375 layer_surface->pending.margin.top,
598 layer_surface->client_pending.margin.right, 376 layer_surface->pending.margin.right,
599 layer_surface->client_pending.margin.bottom, 377 layer_surface->pending.margin.bottom,
600 layer_surface->client_pending.margin.left); 378 layer_surface->pending.margin.left);
601 379
602 if (!layer_surface->output) { 380 if (!layer_surface->output) {
603 // Assign last active output 381 // Assign last active output
@@ -609,12 +387,12 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
609 output = ws->output; 387 output = ws->output;
610 } 388 }
611 } 389 }
612 if (!output || output == root->noop_output) { 390 if (!output || output == root->fallback_output) {
613 if (!root->outputs->length) { 391 if (!root->outputs->length) {
614 sway_log(SWAY_ERROR, 392 sway_log(SWAY_ERROR,
615 "no output to auto-assign layer surface '%s' to", 393 "no output to auto-assign layer surface '%s' to",
616 layer_surface->namespace); 394 layer_surface->namespace);
617 wlr_layer_surface_v1_close(layer_surface); 395 wlr_layer_surface_v1_destroy(layer_surface);
618 return; 396 return;
619 } 397 }
620 output = root->outputs->items[0]; 398 output = root->outputs->items[0];
@@ -622,42 +400,51 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data) {
622 layer_surface->output = output->wlr_output; 400 layer_surface->output = output->wlr_output;
623 } 401 }
624 402
625 struct sway_layer_surface *sway_layer = 403 struct sway_output *output = layer_surface->output->data;
626 calloc(1, sizeof(struct sway_layer_surface)); 404
627 if (!sway_layer) { 405 enum zwlr_layer_shell_v1_layer layer_type = layer_surface->pending.layer;
406 struct wlr_scene_tree *output_layer = sway_layer_get_scene(
407 output, layer_type);
408 struct wlr_scene_layer_surface_v1 *scene_surface =
409 wlr_scene_layer_surface_v1_create(output_layer, layer_surface);
410 if (!scene_surface) {
411 sway_log(SWAY_ERROR, "Could not allocate a layer_surface_v1");
628 return; 412 return;
629 } 413 }
630 414
631 sway_layer->surface_commit.notify = handle_surface_commit; 415 struct sway_layer_surface *surface =
632 wl_signal_add(&layer_surface->surface->events.commit, 416 sway_layer_surface_create(scene_surface);
633 &sway_layer->surface_commit); 417 if (!surface) {
634 418 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 419
650 struct sway_output *output = layer_surface->output->data; 420 sway_log(SWAY_ERROR, "Could not allocate a sway_layer_surface");
651 sway_layer->output_destroy.notify = handle_output_destroy; 421 return;
652 wl_signal_add(&output->events.destroy, &sway_layer->output_destroy); 422 }
653 423
654 wl_list_insert(&output->layers[layer_surface->client_pending.layer], 424 if (!scene_descriptor_assign(&scene_surface->tree->node,
655 &sway_layer->link); 425 SWAY_SCENE_DESC_LAYER_SHELL, surface)) {
656 426 sway_log(SWAY_ERROR, "Failed to allocate a layer surface descriptor");
657 // Temporarily set the layer's current state to client_pending 427 // destroying the layer_surface will also destroy its corresponding
658 // So that we can easily arrange it 428 // scene node
659 struct wlr_layer_surface_v1_state old_state = layer_surface->current; 429 wlr_layer_surface_v1_destroy(layer_surface);
660 layer_surface->current = layer_surface->client_pending; 430 return;
661 arrange_layers(output); 431 }
662 layer_surface->current = old_state; 432
433 surface->output = output;
434
435 surface->surface_commit.notify = handle_surface_commit;
436 wl_signal_add(&layer_surface->surface->events.commit,
437 &surface->surface_commit);
438 surface->map.notify = handle_map;
439 wl_signal_add(&layer_surface->surface->events.map, &surface->map);
440 surface->unmap.notify = handle_unmap;
441 wl_signal_add(&layer_surface->surface->events.unmap, &surface->unmap);
442 surface->new_popup.notify = handle_new_popup;
443 wl_signal_add(&layer_surface->events.new_popup, &surface->new_popup);
444
445 surface->output_destroy.notify = handle_output_destroy;
446 wl_signal_add(&output->events.disable, &surface->output_destroy);
447
448 surface->node_destroy.notify = handle_node_destroy;
449 wl_signal_add(&scene_surface->tree->node.events.destroy, &surface->node_destroy);
663} 450}
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 5edc8f96..70987feb 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -1,42 +1,61 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <strings.h> 3#include <strings.h>
5#include <time.h> 4#include <time.h>
6#include <wayland-server-core.h> 5#include <wayland-server-core.h>
6#include <wlr/config.h>
7#include <wlr/backend/headless.h>
8#include <wlr/render/swapchain.h>
7#include <wlr/render/wlr_renderer.h> 9#include <wlr/render/wlr_renderer.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_buffer.h> 10#include <wlr/types/wlr_buffer.h>
11#include <wlr/types/wlr_gamma_control_v1.h>
10#include <wlr/types/wlr_matrix.h> 12#include <wlr/types/wlr_matrix.h>
11#include <wlr/types/wlr_output_damage.h>
12#include <wlr/types/wlr_output_layout.h> 13#include <wlr/types/wlr_output_layout.h>
14#include <wlr/types/wlr_output_management_v1.h>
15#include <wlr/types/wlr_output_power_management_v1.h>
13#include <wlr/types/wlr_output.h> 16#include <wlr/types/wlr_output.h>
14#include <wlr/types/wlr_presentation_time.h> 17#include <wlr/types/wlr_presentation_time.h>
15#include <wlr/types/wlr_surface.h> 18#include <wlr/types/wlr_compositor.h>
16#include <wlr/util/region.h> 19#include <wlr/util/region.h>
20#include <wlr/util/transform.h>
17#include "config.h" 21#include "config.h"
18#include "log.h" 22#include "log.h"
19#include "sway/config.h" 23#include "sway/config.h"
20#include "sway/desktop/transaction.h" 24#include "sway/desktop/transaction.h"
21#include "sway/input/input-manager.h" 25#include "sway/input/input-manager.h"
22#include "sway/input/seat.h" 26#include "sway/input/seat.h"
27#include "sway/ipc-server.h"
23#include "sway/layers.h" 28#include "sway/layers.h"
24#include "sway/output.h" 29#include "sway/output.h"
30#include "sway/scene_descriptor.h"
25#include "sway/server.h" 31#include "sway/server.h"
26#include "sway/surface.h"
27#include "sway/tree/arrange.h" 32#include "sway/tree/arrange.h"
28#include "sway/tree/container.h" 33#include "sway/tree/container.h"
29#include "sway/tree/root.h" 34#include "sway/tree/root.h"
30#include "sway/tree/view.h" 35#include "sway/tree/view.h"
31#include "sway/tree/workspace.h" 36#include "sway/tree/workspace.h"
32 37
38#if WLR_HAS_DRM_BACKEND
39#include <wlr/backend/drm.h>
40#include <wlr/types/wlr_drm_lease_v1.h>
41#endif
42
43bool output_match_name_or_id(struct sway_output *output,
44 const char *name_or_id) {
45 if (strcmp(name_or_id, "*") == 0) {
46 return true;
47 }
48
49 char identifier[128];
50 output_get_identifier(identifier, sizeof(identifier), output);
51 return strcasecmp(identifier, name_or_id) == 0
52 || strcasecmp(output->wlr_output->name, name_or_id) == 0;
53}
54
33struct sway_output *output_by_name_or_id(const char *name_or_id) { 55struct sway_output *output_by_name_or_id(const char *name_or_id) {
34 for (int i = 0; i < root->outputs->length; ++i) { 56 for (int i = 0; i < root->outputs->length; ++i) {
35 struct sway_output *output = root->outputs->items[i]; 57 struct sway_output *output = root->outputs->items[i];
36 char identifier[128]; 58 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; 59 return output;
41 } 60 }
42 } 61 }
@@ -46,583 +65,217 @@ 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) { 65struct sway_output *all_output_by_name_or_id(const char *name_or_id) {
47 struct sway_output *output; 66 struct sway_output *output;
48 wl_list_for_each(output, &root->all_outputs, link) { 67 wl_list_for_each(output, &root->all_outputs, link) {
49 char identifier[128]; 68 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; 69 return output;
54 } 70 }
55 } 71 }
56 return NULL; 72 return NULL;
57} 73}
58 74
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 75
69 // Coordinates relative to the center of the subsurface 76struct sway_workspace *output_get_active_workspace(struct sway_output *output) {
70 double ox = *sx - pw/2 + sw/2, 77 struct sway_seat *seat = input_manager_current_seat();
71 oy = *sy - ph/2 + sh/2; 78 struct sway_node *focus = seat_get_active_tiling_child(seat, &output->node);
72 // Rotated coordinates 79 if (!focus) {
73 double rx = cos(-rotation)*ox - sin(-rotation)*oy, 80 if (!output->workspaces->length) {
74 ry = cos(-rotation)*oy + sin(-rotation)*ox; 81 return NULL;
75 *sx = rx + pw/2 - sw/2; 82 }
76 *sy = ry + ph/2 - sh/2; 83 return output->workspaces->items[0];
84 }
85 return focus->sway_workspace;
77} 86}
78 87
79struct surface_iterator_data { 88struct send_frame_done_data {
80 sway_surface_iterator_func_t user_iterator; 89 struct timespec when;
81 void *user_data; 90 int msec_until_refresh;
82
83 struct sway_output *output; 91 struct sway_output *output;
84 struct sway_view *view;
85 double ox, oy;
86 int width, height;
87 float rotation;
88}; 92};
89 93
90static bool get_surface_box(struct surface_iterator_data *data, 94struct buffer_timer {
91 struct wlr_surface *surface, int sx, int sy, 95 struct wl_listener destroy;
92 struct wlr_box *surface_box) { 96 struct wl_event_source *frame_done_timer;
93 struct sway_output *output = data->output; 97};
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
143void output_surface_for_each_surface(struct sway_output *output,
144 struct wlr_surface *surface, double ox, double oy,
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 98
162void output_view_for_each_surface(struct sway_output *output, 99static int handle_buffer_timer(void *data) {
163 struct sway_view *view, sway_surface_iterator_func_t iterator, 100 struct wlr_scene_buffer *buffer = data;
164 void *user_data) {
165 struct surface_iterator_data data = {
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}
181 101
182void output_view_for_each_popup_surface(struct sway_output *output, 102 struct timespec now;
183 struct sway_view *view, sway_surface_iterator_func_t iterator, 103 clock_gettime(CLOCK_MONOTONIC, &now);
184 void *user_data) { 104 wlr_scene_buffer_send_frame_done(buffer, &now);
185 struct surface_iterator_data data = { 105 return 0;
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} 106}
201 107
202void output_layer_for_each_surface(struct sway_output *output, 108static void handle_buffer_timer_destroy(struct wl_listener *listener,
203 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, 109 void *data) {
204 void *user_data) { 110 struct buffer_timer *timer = wl_container_of(listener, timer, destroy);
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 111
220 double popup_sx, popup_sy; 112 wl_list_remove(&timer->destroy.link);
221 popup_sx = layer_surface->geo.x + 113 wl_event_source_remove(timer->frame_done_timer);
222 popup->popup->geometry.x - popup->geometry.x; 114 free(timer);
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} 115}
245 116
246void output_layer_for_each_toplevel_surface(struct sway_output *output, 117static 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, 118 struct buffer_timer *timer =
248 void *user_data) { 119 scene_descriptor_try_get(&buffer->node, SWAY_SCENE_DESC_BUFFER_TIMER);
249 struct sway_layer_surface *layer_surface; 120 if (timer) {
250 wl_list_for_each(layer_surface, layer_surfaces, link) { 121 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 } 122 }
257}
258
259 123
260void output_layer_for_each_popup_surface(struct sway_output *output, 124 timer = calloc(1, sizeof(struct buffer_timer));
261 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator, 125 if (!timer) {
262 void *user_data) { 126 return NULL;
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 } 127 }
299}
300 128
301#if HAVE_XWAYLAND 129 timer->frame_done_timer = wl_event_loop_add_timer(server.wl_event_loop,
302void output_unmanaged_for_each_surface(struct sway_output *output, 130 handle_buffer_timer, buffer);
303 struct wl_list *unmanaged, sway_surface_iterator_func_t iterator, 131 if (!timer->frame_done_timer) {
304 void *user_data) { 132 free(timer);
305 struct sway_xwayland_unmanaged *unmanaged_surface; 133 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 } 134 }
315}
316#endif
317 135
318void output_drag_icons_for_each_surface(struct sway_output *output, 136 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 137
334static void for_each_surface_container_iterator(struct sway_container *con, 138 timer->destroy.notify = handle_buffer_timer_destroy;
335 void *_data) { 139 wl_signal_add(&buffer->node.events.destroy, &timer->destroy);
336 if (!con->view || !view_is_visible(con->view)) {
337 return;
338 }
339 140
340 struct surface_iterator_data *data = _data; 141 return timer;
341 output_view_for_each_surface(data->output, con->view,
342 data->user_iterator, data->user_data);
343} 142}
344 143
345static void output_for_each_surface(struct sway_output *output, 144static void send_frame_done_iterator(struct wlr_scene_buffer *buffer,
346 sway_surface_iterator_func_t iterator, void *user_data) { 145 int x, int y, void *user_data) {
347 if (output_has_opaque_overlay_layer_surface(output)) { 146 struct send_frame_done_data *data = user_data;
348 goto overlay; 147 struct sway_output *output = data->output;
349 } 148 int view_max_render_time = 0;
350 149
351 struct surface_iterator_data data = { 150 if (buffer->primary_output != data->output->scene_output) {
352 .user_iterator = iterator, 151 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 } 152 }
404 153
405overlay: 154 struct wlr_scene_node *current = &buffer->node;
406 output_layer_for_each_surface(output, 155 while (true) {
407 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], 156 struct sway_view *view = scene_descriptor_try_get(current,
408 iterator, user_data); 157 SWAY_SCENE_DESC_VIEW);
409 output_drag_icons_for_each_surface(output, &root->drag_icons, 158 if (view) {
410 iterator, user_data); 159 view_max_render_time = view->max_render_time;
411} 160 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 } 161 }
431 return output->workspaces->items[0];
432 }
433 return focus->sway_workspace;
434}
435 162
436bool output_has_opaque_overlay_layer_surface(struct sway_output *output) { 163 if (!current->parent) {
437 struct sway_layer_surface *sway_layer_surface; 164 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 } 165 }
457 }
458 return false;
459}
460
461struct send_frame_done_data {
462 struct timespec when;
463 int msec_until_refresh;
464};
465 166
466static void send_frame_done_iterator(struct sway_output *output, struct sway_view *view, 167 current = &current->parent->node;
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 } 168 }
473 169
474 struct send_frame_done_data *data = user_data;
475
476 int delay = data->msec_until_refresh - output->max_render_time 170 int delay = data->msec_until_refresh - output->max_render_time
477 - view_max_render_time; 171 - view_max_render_time;
478 172
479 if (output->max_render_time == 0 || view_max_render_time == 0 || delay < 1) { 173 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 174
498static bool scan_out_fullscreen_view(struct sway_output *output, 175 if (output->max_render_time != 0 && view_max_render_time != 0 && delay > 0) {
499 struct sway_view *view) { 176 timer = buffer_timer_get_or_create(buffer);
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 } 177 }
505 178
506 if (!wl_list_empty(&view->saved_buffers)) { 179 if (timer) {
507 return false; 180 wl_event_source_timer_update(timer->frame_done_timer, delay);
181 } else {
182 wlr_scene_buffer_send_frame_done(buffer, &data->when);
508 } 183 }
184}
509 185
510 for (int i = 0; i < workspace->current.floating->length; ++i) { 186static enum wlr_scale_filter_mode get_scale_filter(struct sway_output *output,
511 struct sway_container *floater = 187 struct wlr_scene_buffer *buffer) {
512 workspace->current.floating->items[i]; 188 // if we are scaling down, we should always choose linear
513 if (container_is_transient_for(floater, view->container)) { 189 if (buffer->dst_width > 0 && buffer->dst_height > 0 && (
514 return false; 190 buffer->dst_width < buffer->buffer_width ||
515 } 191 buffer->dst_height < buffer->buffer_height)) {
192 return WLR_SCALE_FILTER_BILINEAR;
516 } 193 }
517 194
518#if HAVE_XWAYLAND 195 switch (output->scale_filter) {
519 if (!wl_list_empty(&root->xwayland_unmanaged)) { 196 case SCALE_FILTER_LINEAR:
520 return false; 197 return WLR_SCALE_FILTER_BILINEAR;
198 case SCALE_FILTER_NEAREST:
199 return WLR_SCALE_FILTER_NEAREST;
200 default:
201 abort(); // unreachable
521 } 202 }
522#endif 203}
523 204
524 if (!wl_list_empty(&output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY])) { 205static void output_configure_scene(struct sway_output *output,
525 return false; 206 struct wlr_scene_node *node, float opacity) {
526 } 207 if (!node->enabled) {
527 if (!wl_list_empty(&root->drag_icons)) { 208 return;
528 return false;
529 } 209 }
530 210
531 struct wlr_surface *surface = view->surface; 211 struct sway_container *con =
532 if (surface == NULL) { 212 scene_descriptor_try_get(node, SWAY_SCENE_DESC_CONTAINER);
533 return false; 213 if (con) {
534 } 214 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 } 215 }
541 216
542 if (surface->buffer == NULL) { 217 if (node->type == WLR_SCENE_NODE_BUFFER) {
543 return false; 218 struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
544 }
545 219
546 if ((float)surface->current.scale != wlr_output->scale || 220 // hack: don't call the scene setter because that will damage all outputs
547 surface->current.transform != wlr_output->transform) { 221 // We don't want to damage outputs that aren't our current output that
548 return false; 222 // we're configuring
549 } 223 buffer->filter_mode = get_scale_filter(output, buffer);
550 224
551 wlr_output_attach_buffer(wlr_output, &surface->buffer->base); 225 wlr_scene_buffer_set_opacity(buffer, opacity);
552 if (!wlr_output_test(wlr_output)) { 226 } else if (node->type == WLR_SCENE_NODE_TREE) {
553 return false; 227 struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
228 struct wlr_scene_node *node;
229 wl_list_for_each(node, &tree->children, link) {
230 output_configure_scene(output, node, opacity);
231 }
554 } 232 }
555
556 wlr_presentation_surface_sampled_on_output(server.presentation, surface,
557 wlr_output);
558
559 return wlr_output_commit(wlr_output);
560} 233}
561 234
562static int output_repaint_timer_handler(void *data) { 235static int output_repaint_timer_handler(void *data) {
563 struct sway_output *output = data; 236 struct sway_output *output = data;
564 if (output->wlr_output == NULL) {
565 return 0;
566 }
567
568 output->wlr_output->frame_pending = false;
569 237
570 struct sway_workspace *workspace = output->current.active_workspace; 238 if (!output->enabled) {
571 if (workspace == NULL) {
572 return 0; 239 return 0;
573 } 240 }
574 241
575 struct sway_container *fullscreen_con = root->fullscreen_global; 242 output->wlr_output->frame_pending = false;
576 if (!fullscreen_con) {
577 fullscreen_con = workspace->current.fullscreen;
578 }
579 243
580 if (fullscreen_con && fullscreen_con->view) { 244 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 245
586 if (scanned_out && !last_scanned_out) { 246 if (output->gamma_lut_changed) {
587 sway_log(SWAY_DEBUG, "Scanning out fullscreen view on %s", 247 struct wlr_output_state pending;
588 output->wlr_output->name); 248 wlr_output_state_init(&pending);
249 if (!wlr_scene_output_build_state(output->scene_output, &pending, NULL)) {
250 return 0;
589 } 251 }
590 if (last_scanned_out && !scanned_out) { 252
591 sway_log(SWAY_DEBUG, "Stopping fullscreen view scan out on %s", 253 output->gamma_lut_changed = false;
592 output->wlr_output->name); 254 struct wlr_gamma_control_v1 *gamma_control =
255 wlr_gamma_control_manager_v1_get_control(
256 server.gamma_control_manager_v1, output->wlr_output);
257 if (!wlr_gamma_control_v1_apply(gamma_control, &pending)) {
258 wlr_output_state_finish(&pending);
259 return 0;
593 } 260 }
594 last_scanned_out = scanned_out;
595 261
596 if (scanned_out) { 262 if (!wlr_output_commit_state(output->wlr_output, &pending)) {
263 wlr_gamma_control_v1_send_failed_and_destroy(gamma_control);
264 wlr_output_state_finish(&pending);
597 return 0; 265 return 0;
598 } 266 }
599 }
600 267
601 bool needs_frame; 268 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; 269 return 0;
607 } 270 }
608 271
609 if (needs_frame) { 272 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; 273 return 0;
621} 274}
622 275
623static void damage_handle_frame(struct wl_listener *listener, void *user_data) { 276static void handle_frame(struct wl_listener *listener, void *user_data) {
624 struct sway_output *output = 277 struct sway_output *output =
625 wl_container_of(listener, output, damage_frame); 278 wl_container_of(listener, output, frame);
626 if (!output->enabled || !output->wlr_output->enabled) { 279 if (!output->enabled || !output->wlr_output->enabled) {
627 return; 280 return;
628 } 281 }
@@ -633,9 +286,7 @@ static void damage_handle_frame(struct wl_listener *listener, void *user_data) {
633 286
634 if (output->max_render_time != 0) { 287 if (output->max_render_time != 0) {
635 struct timespec now; 288 struct timespec now;
636 clockid_t presentation_clock 289 clock_gettime(CLOCK_MONOTONIC, &now);
637 = wlr_backend_get_presentation_clock(server.backend);
638 clock_gettime(presentation_clock, &now);
639 290
640 const long NSEC_IN_SECONDS = 1000000000; 291 const long NSEC_IN_SECONDS = 1000000000;
641 struct timespec predicted_refresh = output->last_presentation; 292 struct timespec predicted_refresh = output->last_presentation;
@@ -682,124 +333,8 @@ static void damage_handle_frame(struct wl_listener *listener, void *user_data) {
682 struct send_frame_done_data data = {0}; 333 struct send_frame_done_data data = {0};
683 clock_gettime(CLOCK_MONOTONIC, &data.when); 334 clock_gettime(CLOCK_MONOTONIC, &data.when);
684 data.msec_until_refresh = msec_until_refresh; 335 data.msec_until_refresh = msec_until_refresh;
685 send_frame_done(output, &data); 336 data.output = output;
686} 337 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} 338}
804 339
805static void update_output_manager_config(struct sway_server *server) { 340static void update_output_manager_config(struct sway_server *server) {
@@ -808,73 +343,61 @@ static void update_output_manager_config(struct sway_server *server) {
808 343
809 struct sway_output *output; 344 struct sway_output *output;
810 wl_list_for_each(output, &root->all_outputs, link) { 345 wl_list_for_each(output, &root->all_outputs, link) {
811 if (output == root->noop_output) { 346 if (output == root->fallback_output) {
812 continue; 347 continue;
813 } 348 }
814 struct wlr_output_configuration_head_v1 *config_head = 349 struct wlr_output_configuration_head_v1 *config_head =
815 wlr_output_configuration_head_v1_create(config, output->wlr_output); 350 wlr_output_configuration_head_v1_create(config, output->wlr_output);
816 struct wlr_box *output_box = wlr_output_layout_get_box( 351 struct wlr_box output_box;
817 root->output_layout, output->wlr_output); 352 wlr_output_layout_get_box(root->output_layout,
818 // We mark the output enabled even if it is switched off by DPMS 353 output->wlr_output, &output_box);
819 config_head->state.enabled = output->enabled; 354 // We mark the output enabled when it's switched off but not disabled
820 config_head->state.mode = output->current_mode; 355 config_head->state.enabled = !wlr_box_empty(&output_box);
821 if (output_box) { 356 config_head->state.x = output_box.x;
822 config_head->state.x = output_box->x; 357 config_head->state.y = output_box.y;
823 config_head->state.y = output_box->y;
824 }
825 } 358 }
826 359
827 wlr_output_manager_v1_set_configuration(server->output_manager_v1, config); 360 wlr_output_manager_v1_set_configuration(server->output_manager_v1, config);
361
362 ipc_event_output();
828} 363}
829 364
830static void handle_destroy(struct wl_listener *listener, void *data) { 365static 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; 366 struct sway_server *server = output->server;
833 wl_signal_emit(&output->events.destroy, output);
834 367
835 if (output->enabled) { 368 if (output->enabled) {
836 output_disable(output); 369 output_disable(output);
837 } 370 }
371
838 output_begin_destroy(output); 372 output_begin_destroy(output);
839 373
374 wl_list_remove(&output->link);
375
376 wl_list_remove(&output->layout_destroy.link);
840 wl_list_remove(&output->destroy.link); 377 wl_list_remove(&output->destroy.link);
841 wl_list_remove(&output->commit.link); 378 wl_list_remove(&output->commit.link);
842 wl_list_remove(&output->mode.link);
843 wl_list_remove(&output->present.link); 379 wl_list_remove(&output->present.link);
380 wl_list_remove(&output->frame.link);
381 wl_list_remove(&output->request_state.link);
382
383 wlr_scene_output_destroy(output->scene_output);
384 output->scene_output = NULL;
385 output->wlr_output->data = NULL;
386 output->wlr_output = NULL;
844 387
845 transaction_commit_dirty(); 388 transaction_commit_dirty();
846 389
847 update_output_manager_config(server); 390 update_output_manager_config(server);
848} 391}
849 392
850static void handle_mode(struct wl_listener *listener, void *data) { 393static void handle_destroy(struct wl_listener *listener, void *data) {
851 struct sway_output *output = wl_container_of(listener, output, mode); 394 struct sway_output *output = wl_container_of(listener, output, destroy);
852 if (!output->enabled && !output->enabling) { 395 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} 396}
874 397
875static void update_textures(struct sway_container *con, void *data) { 398static void handle_layout_destroy(struct wl_listener *listener, void *data) {
876 container_update_title_textures(con); 399 struct sway_output *output = wl_container_of(listener, output, layout_destroy);
877 container_update_marks_textures(con); 400 begin_destroy(output);
878} 401}
879 402
880static void handle_commit(struct wl_listener *listener, void *data) { 403static void handle_commit(struct wl_listener *listener, void *data) {
@@ -885,24 +408,28 @@ static void handle_commit(struct wl_listener *listener, void *data) {
885 return; 408 return;
886 } 409 }
887 410
888 if (event->committed & WLR_OUTPUT_STATE_SCALE) { 411 if (event->state->committed & (
889 output_for_each_container(output, update_textures, NULL); 412 WLR_OUTPUT_STATE_MODE |
890 } 413 WLR_OUTPUT_STATE_TRANSFORM |
891 414 WLR_OUTPUT_STATE_SCALE)) {
892 if (event->committed & (WLR_OUTPUT_STATE_TRANSFORM | WLR_OUTPUT_STATE_SCALE)) {
893 arrange_layers(output); 415 arrange_layers(output);
894 arrange_output(output); 416 arrange_output(output);
895 transaction_commit_dirty(); 417 transaction_commit_dirty();
896 418
897 update_output_manager_config(output->server); 419 update_output_manager_config(output->server);
898 } 420 }
421
422 // Next time the output is enabled, try to re-apply the gamma LUT
423 if ((event->state->committed & WLR_OUTPUT_STATE_ENABLED) && !output->wlr_output->enabled) {
424 output->gamma_lut_changed = true;
425 }
899} 426}
900 427
901static void handle_present(struct wl_listener *listener, void *data) { 428static void handle_present(struct wl_listener *listener, void *data) {
902 struct sway_output *output = wl_container_of(listener, output, present); 429 struct sway_output *output = wl_container_of(listener, output, present);
903 struct wlr_output_event_present *output_event = data; 430 struct wlr_output_event_present *output_event = data;
904 431
905 if (!output->enabled) { 432 if (!output->enabled || !output_event->presented) {
906 return; 433 return;
907 } 434 }
908 435
@@ -910,37 +437,91 @@ static void handle_present(struct wl_listener *listener, void *data) {
910 output->refresh_nsec = output_event->refresh; 437 output->refresh_nsec = output_event->refresh;
911} 438}
912 439
440static void handle_request_state(struct wl_listener *listener, void *data) {
441 struct sway_output *output =
442 wl_container_of(listener, output, request_state);
443 const struct wlr_output_event_request_state *event = data;
444 wlr_output_commit_state(output->wlr_output, event->state);
445}
446
447static unsigned int last_headless_num = 0;
448
913void handle_new_output(struct wl_listener *listener, void *data) { 449void handle_new_output(struct wl_listener *listener, void *data) {
914 struct sway_server *server = wl_container_of(listener, server, new_output); 450 struct sway_server *server = wl_container_of(listener, server, new_output);
915 struct wlr_output *wlr_output = data; 451 struct wlr_output *wlr_output = data;
916 sway_log(SWAY_DEBUG, "New output %p: %s", wlr_output, wlr_output->name); 452
453 if (wlr_output == root->fallback_output->wlr_output) {
454 return;
455 }
456
457 if (wlr_output_is_headless(wlr_output)) {
458 char name[64];
459 snprintf(name, sizeof(name), "HEADLESS-%u", ++last_headless_num);
460 wlr_output_set_name(wlr_output, name);
461 }
462
463 sway_log(SWAY_DEBUG, "New output %p: %s (non-desktop: %d)",
464 wlr_output, wlr_output->name, wlr_output->non_desktop);
465
466 if (wlr_output->non_desktop) {
467 sway_log(SWAY_DEBUG, "Not configuring non-desktop output");
468 struct sway_output_non_desktop *non_desktop = output_non_desktop_create(wlr_output);
469#if WLR_HAS_DRM_BACKEND
470 if (server->drm_lease_manager) {
471 wlr_drm_lease_v1_manager_offer_output(server->drm_lease_manager,
472 wlr_output);
473 }
474#endif
475 list_add(root->non_desktop_outputs, non_desktop);
476 return;
477 }
478
479 if (!wlr_output_init_render(wlr_output, server->allocator,
480 server->renderer)) {
481 sway_log(SWAY_ERROR, "Failed to init output render");
482 return;
483 }
484
485 // Create the scene output here so we're not accidentally creating one for
486 // the fallback output
487 struct wlr_scene_output *scene_output =
488 wlr_scene_output_create(root->root_scene, wlr_output);
489 if (!scene_output) {
490 sway_log(SWAY_ERROR, "Failed to create a scene output");
491 return;
492 }
917 493
918 struct sway_output *output = output_create(wlr_output); 494 struct sway_output *output = output_create(wlr_output);
919 if (!output) { 495 if (!output) {
496 sway_log(SWAY_ERROR, "Failed to create a sway output");
497 wlr_scene_output_destroy(scene_output);
920 return; 498 return;
921 } 499 }
500
922 output->server = server; 501 output->server = server;
923 output->damage = wlr_output_damage_create(wlr_output); 502 output->scene_output = scene_output;
924 503
504 wl_signal_add(&root->output_layout->events.destroy, &output->layout_destroy);
505 output->layout_destroy.notify = handle_layout_destroy;
925 wl_signal_add(&wlr_output->events.destroy, &output->destroy); 506 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
926 output->destroy.notify = handle_destroy; 507 output->destroy.notify = handle_destroy;
927 wl_signal_add(&wlr_output->events.commit, &output->commit); 508 wl_signal_add(&wlr_output->events.commit, &output->commit);
928 output->commit.notify = handle_commit; 509 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); 510 wl_signal_add(&wlr_output->events.present, &output->present);
932 output->present.notify = handle_present; 511 output->present.notify = handle_present;
933 wl_signal_add(&output->damage->events.frame, &output->damage_frame); 512 wl_signal_add(&wlr_output->events.frame, &output->frame);
934 output->damage_frame.notify = damage_handle_frame; 513 output->frame.notify = handle_frame;
935 wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); 514 wl_signal_add(&wlr_output->events.request_state, &output->request_state);
936 output->damage_destroy.notify = damage_handle_destroy; 515 output->request_state.notify = handle_request_state;
937 516
938 output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop, 517 output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
939 output_repaint_timer_handler, output); 518 output_repaint_timer_handler, output);
940 519
941 struct output_config *oc = find_output_config(output); 520 if (server->session_lock.lock) {
942 apply_output_config(oc, output); 521 sway_session_lock_add_output(server->session_lock.lock, output);
943 free_output_config(oc); 522 }
523
524 apply_all_output_configs();
944 525
945 transaction_commit_dirty(); 526 transaction_commit_dirty();
946 527
@@ -954,62 +535,103 @@ void handle_output_layout_change(struct wl_listener *listener,
954 update_output_manager_config(server); 535 update_output_manager_config(server);
955} 536}
956 537
538void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) {
539 struct sway_server *server =
540 wl_container_of(listener, server, gamma_control_set_gamma);
541 const struct wlr_gamma_control_manager_v1_set_gamma_event *event = data;
542
543 struct sway_output *output = event->output->data;
544
545 if(!output) {
546 return;
547 }
548
549 output->gamma_lut_changed = true;
550 wlr_output_schedule_frame(output->wlr_output);
551}
552
553static struct output_config *output_config_for_config_head(
554 struct wlr_output_configuration_head_v1 *config_head,
555 struct sway_output *output) {
556 struct output_config *oc = new_output_config(output->wlr_output->name);
557 oc->enabled = config_head->state.enabled;
558 if (!oc->enabled) {
559 return oc;
560 }
561
562 if (config_head->state.mode != NULL) {
563 struct wlr_output_mode *mode = config_head->state.mode;
564 oc->width = mode->width;
565 oc->height = mode->height;
566 oc->refresh_rate = mode->refresh / 1000.f;
567 } else {
568 oc->width = config_head->state.custom_mode.width;
569 oc->height = config_head->state.custom_mode.height;
570 oc->refresh_rate =
571 config_head->state.custom_mode.refresh / 1000.f;
572 }
573 oc->x = config_head->state.x;
574 oc->y = config_head->state.y;
575 oc->transform = config_head->state.transform;
576 oc->scale = config_head->state.scale;
577 oc->adaptive_sync = config_head->state.adaptive_sync_enabled;
578 return oc;
579}
580
957static void output_manager_apply(struct sway_server *server, 581static void output_manager_apply(struct sway_server *server,
958 struct wlr_output_configuration_v1 *config, bool test_only) { 582 struct wlr_output_configuration_v1 *config, bool test_only) {
959 // TODO: perform atomic tests on the whole backend atomically 583 size_t configs_len = wl_list_length(&root->all_outputs);
960 584 struct matched_output_config *configs = calloc(configs_len, sizeof(*configs));
961 struct wlr_output_configuration_head_v1 *config_head; 585 if (!configs) {
962 // First disable outputs we need to disable 586 return;
963 bool ok = true; 587 }
964 wl_list_for_each(config_head, &config->heads, link) { 588
965 struct wlr_output *wlr_output = config_head->state.output; 589 int config_idx = 0;
966 struct sway_output *output = wlr_output->data; 590 struct sway_output *sway_output;
967 if (!output->enabled || config_head->state.enabled) { 591 wl_list_for_each(sway_output, &root->all_outputs, link) {
592 if (sway_output == root->fallback_output) {
593 configs_len--;
968 continue; 594 continue;
969 } 595 }
970 struct output_config *oc = new_output_config(output->wlr_output->name);
971 oc->enabled = false;
972 596
973 if (test_only) { 597 struct matched_output_config *cfg = &configs[config_idx++];
974 ok &= test_output_config(oc, output); 598 cfg->output = sway_output;
975 } else {
976 oc = store_output_config(oc);
977 ok &= apply_output_config(oc, output);
978 }
979 }
980 599
981 // Then enable outputs that need to 600 struct wlr_output_configuration_head_v1 *config_head;
982 wl_list_for_each(config_head, &config->heads, link) { 601 wl_list_for_each(config_head, &config->heads, link) {
983 struct wlr_output *wlr_output = config_head->state.output; 602 if (config_head->state.output == sway_output->wlr_output) {
984 struct sway_output *output = wlr_output->data; 603 cfg->config = output_config_for_config_head(config_head, sway_output);
985 if (!config_head->state.enabled) { 604 break;
986 continue; 605 }
987 } 606 }
988 struct output_config *oc = new_output_config(output->wlr_output->name); 607 if (!cfg->config) {
989 oc->enabled = true; 608 cfg->config = find_output_config(sway_output);
990 if (config_head->state.mode != NULL) {
991 struct wlr_output_mode *mode = config_head->state.mode;
992 oc->width = mode->width;
993 oc->height = mode->height;
994 oc->refresh_rate = mode->refresh / 1000.f;
995 } else {
996 oc->width = config_head->state.custom_mode.width;
997 oc->height = config_head->state.custom_mode.height;
998 oc->refresh_rate =
999 config_head->state.custom_mode.refresh / 1000.f;
1000 } 609 }
1001 oc->x = config_head->state.x; 610 }
1002 oc->y = config_head->state.y; 611
1003 oc->transform = config_head->state.transform; 612 bool ok = apply_output_configs(configs, configs_len, test_only);
1004 oc->scale = config_head->state.scale; 613 for (size_t idx = 0; idx < configs_len; idx++) {
614 struct matched_output_config *cfg = &configs[idx];
1005 615
1006 if (test_only) { 616 // Only store new configs for successful non-test commits. Old configs,
1007 ok &= test_output_config(oc, output); 617 // test-only and failed commits just get freed.
618 bool store_config = false;
619 if (!test_only && ok) {
620 struct wlr_output_configuration_head_v1 *config_head;
621 wl_list_for_each(config_head, &config->heads, link) {
622 if (config_head->state.output == cfg->output->wlr_output) {
623 store_config = true;
624 break;
625 }
626 }
627 }
628 if (store_config) {
629 store_output_config(cfg->config);
1008 } else { 630 } else {
1009 oc = store_output_config(oc); 631 free_output_config(cfg->config);
1010 ok &= apply_output_config(oc, output);
1011 } 632 }
1012 } 633 }
634 free(configs);
1013 635
1014 if (ok) { 636 if (ok) {
1015 wlr_output_configuration_v1_send_succeeded(config); 637 wlr_output_configuration_v1_send_succeeded(config);
@@ -1047,12 +669,12 @@ void handle_output_power_manager_set_mode(struct wl_listener *listener,
1047 struct output_config *oc = new_output_config(output->wlr_output->name); 669 struct output_config *oc = new_output_config(output->wlr_output->name);
1048 switch (event->mode) { 670 switch (event->mode) {
1049 case ZWLR_OUTPUT_POWER_V1_MODE_OFF: 671 case ZWLR_OUTPUT_POWER_V1_MODE_OFF:
1050 oc->dpms_state = DPMS_OFF; 672 oc->power = 0;
1051 break; 673 break;
1052 case ZWLR_OUTPUT_POWER_V1_MODE_ON: 674 case ZWLR_OUTPUT_POWER_V1_MODE_ON:
1053 oc->dpms_state = DPMS_ON; 675 oc->power = 1;
1054 break; 676 break;
1055 } 677 }
1056 oc = store_output_config(oc); 678 store_output_config(oc);
1057 apply_output_config(oc, output); 679 apply_all_output_configs();
1058} 680}
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..042141ab 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -1,11 +1,10 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <string.h> 3#include <string.h>
5#include <time.h> 4#include <time.h>
6#include <wlr/types/wlr_buffer.h> 5#include <wlr/types/wlr_buffer.h>
7#include "sway/config.h" 6#include "sway/config.h"
8#include "sway/desktop.h" 7#include "sway/scene_descriptor.h"
9#include "sway/desktop/idle_inhibit_v1.h" 8#include "sway/desktop/idle_inhibit_v1.h"
10#include "sway/desktop/transaction.h" 9#include "sway/desktop/transaction.h"
11#include "sway/input/cursor.h" 10#include "sway/input/cursor.h"
@@ -35,6 +34,8 @@ struct sway_transaction_instruction {
35 struct sway_container_state container_state; 34 struct sway_container_state container_state;
36 }; 35 };
37 uint32_t serial; 36 uint32_t serial;
37 bool server_request;
38 bool waiting;
38}; 39};
39 40
40static struct sway_transaction *transaction_create(void) { 41static struct sway_transaction *transaction_create(void) {
@@ -86,7 +87,11 @@ static void transaction_destroy(struct sway_transaction *transaction) {
86static void copy_output_state(struct sway_output *output, 87static void copy_output_state(struct sway_output *output,
87 struct sway_transaction_instruction *instruction) { 88 struct sway_transaction_instruction *instruction) {
88 struct sway_output_state *state = &instruction->output_state; 89 struct sway_output_state *state = &instruction->output_state;
89 state->workspaces = create_list(); 90 if (state->workspaces) {
91 state->workspaces->length = 0;
92 } else {
93 state->workspaces = create_list();
94 }
90 list_cat(state->workspaces, output->workspaces); 95 list_cat(state->workspaces, output->workspaces);
91 96
92 state->active_workspace = output_get_active_workspace(output); 97 state->active_workspace = output_get_active_workspace(output);
@@ -104,8 +109,16 @@ static void copy_workspace_state(struct sway_workspace *ws,
104 state->layout = ws->layout; 109 state->layout = ws->layout;
105 110
106 state->output = ws->output; 111 state->output = ws->output;
107 state->floating = create_list(); 112 if (state->floating) {
108 state->tiling = create_list(); 113 state->floating->length = 0;
114 } else {
115 state->floating = create_list();
116 }
117 if (state->tiling) {
118 state->tiling->length = 0;
119 } else {
120 state->tiling = create_list();
121 }
109 list_cat(state->floating, ws->floating); 122 list_cat(state->floating, ws->floating);
110 list_cat(state->tiling, ws->tiling); 123 list_cat(state->tiling, ws->tiling);
111 124
@@ -115,8 +128,8 @@ static void copy_workspace_state(struct sway_workspace *ws,
115 // Set focused_inactive_child to the direct tiling child 128 // Set focused_inactive_child to the direct tiling child
116 struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws); 129 struct sway_container *focus = seat_get_focus_inactive_tiling(seat, ws);
117 if (focus) { 130 if (focus) {
118 while (focus->parent) { 131 while (focus->pending.parent) {
119 focus = focus->parent; 132 focus = focus->pending.parent;
120 } 133 }
121 } 134 }
122 state->focused_inactive_child = focus; 135 state->focused_inactive_child = focus;
@@ -126,28 +139,19 @@ static void copy_container_state(struct sway_container *container,
126 struct sway_transaction_instruction *instruction) { 139 struct sway_transaction_instruction *instruction) {
127 struct sway_container_state *state = &instruction->container_state; 140 struct sway_container_state *state = &instruction->container_state;
128 141
129 state->layout = container->layout; 142 if (state->children) {
130 state->x = container->x; 143 list_free(state->children);
131 state->y = container->y; 144 }
132 state->width = container->width; 145
133 state->height = container->height; 146 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 147
148 if (!container->view) { 148 if (!container->view) {
149 // We store a copy of the child list to avoid having it mutated after
150 // we copy the state.
149 state->children = create_list(); 151 state->children = create_list();
150 list_cat(state->children, container->children); 152 list_cat(state->children, container->pending.children);
153 } else {
154 state->children = NULL;
151 } 155 }
152 156
153 struct sway_seat *seat = input_manager_current_seat(); 157 struct sway_seat *seat = input_manager_current_seat();
@@ -161,14 +165,36 @@ static void copy_container_state(struct sway_container *container,
161} 165}
162 166
163static void transaction_add_node(struct sway_transaction *transaction, 167static void transaction_add_node(struct sway_transaction *transaction,
164 struct sway_node *node) { 168 struct sway_node *node, bool server_request) {
165 struct sway_transaction_instruction *instruction = 169 struct sway_transaction_instruction *instruction = NULL;
166 calloc(1, sizeof(struct sway_transaction_instruction)); 170
167 if (!sway_assert(instruction, "Unable to allocate instruction")) { 171 // Check if we have an instruction for this node already, in which case we
168 return; 172 // update that instead of creating a new one.
173 if (node->ntxnrefs > 0) {
174 for (int idx = 0; idx < transaction->instructions->length; idx++) {
175 struct sway_transaction_instruction *other =
176 transaction->instructions->items[idx];
177 if (other->node == node) {
178 instruction = other;
179 break;
180 }
181 }
182 }
183
184 if (!instruction) {
185 instruction = calloc(1, sizeof(struct sway_transaction_instruction));
186 if (!sway_assert(instruction, "Unable to allocate instruction")) {
187 return;
188 }
189 instruction->transaction = transaction;
190 instruction->node = node;
191 instruction->server_request = server_request;
192
193 list_add(transaction->instructions, instruction);
194 node->ntxnrefs++;
195 } else if (server_request) {
196 instruction->server_request = true;
169 } 197 }
170 instruction->transaction = transaction;
171 instruction->node = node;
172 198
173 switch (node->type) { 199 switch (node->type) {
174 case N_ROOT: 200 case N_ROOT:
@@ -183,46 +209,24 @@ static void transaction_add_node(struct sway_transaction *transaction,
183 copy_container_state(node->sway_container, instruction); 209 copy_container_state(node->sway_container, instruction);
184 break; 210 break;
185 } 211 }
186
187 list_add(transaction->instructions, instruction);
188 node->ntxnrefs++;
189} 212}
190 213
191static void apply_output_state(struct sway_output *output, 214static void apply_output_state(struct sway_output *output,
192 struct sway_output_state *state) { 215 struct sway_output_state *state) {
193 output_damage_whole(output);
194 list_free(output->current.workspaces); 216 list_free(output->current.workspaces);
195 memcpy(&output->current, state, sizeof(struct sway_output_state)); 217 memcpy(&output->current, state, sizeof(struct sway_output_state));
196 output_damage_whole(output);
197} 218}
198 219
199static void apply_workspace_state(struct sway_workspace *ws, 220static void apply_workspace_state(struct sway_workspace *ws,
200 struct sway_workspace_state *state) { 221 struct sway_workspace_state *state) {
201 output_damage_whole(ws->current.output);
202 list_free(ws->current.floating); 222 list_free(ws->current.floating);
203 list_free(ws->current.tiling); 223 list_free(ws->current.tiling);
204 memcpy(&ws->current, state, sizeof(struct sway_workspace_state)); 224 memcpy(&ws->current, state, sizeof(struct sway_workspace_state));
205 output_damage_whole(ws->current.output);
206} 225}
207 226
208static void apply_container_state(struct sway_container *container, 227static void apply_container_state(struct sway_container *container,
209 struct sway_container_state *state) { 228 struct sway_container_state *state) {
210 struct sway_view *view = container->view; 229 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 230 // There are separate children lists for each instruction state, the
227 // container's current state and the container's pending state 231 // container's current state and the container's pending state
228 // (ie. con->children). The list itself needs to be freed here. 232 // (ie. con->children). The list itself needs to be freed here.
@@ -232,48 +236,445 @@ static void apply_container_state(struct sway_container *container,
232 236
233 memcpy(&container->current, state, sizeof(struct sway_container_state)); 237 memcpy(&container->current, state, sizeof(struct sway_container_state));
234 238
235 if (view && !wl_list_empty(&view->saved_buffers)) { 239 if (view) {
236 if (!container->node.destroying || container->node.ntxnrefs == 1) { 240 if (view->saved_surface_tree) {
237 view_remove_saved_buffer(view); 241 if (!container->node.destroying || container->node.ntxnrefs == 1) {
242 view_remove_saved_buffer(view);
243 }
244 }
245
246 // If the view hasn't responded to the configure, center it within
247 // the container. This is important for fullscreen views which
248 // refuse to resize to the size of the output.
249 if (view->surface) {
250 view_center_and_clip_surface(view);
251 }
252 }
253}
254
255static void arrange_title_bar(struct sway_container *con,
256 int x, int y, int width, int height) {
257 container_update(con);
258
259 bool has_title_bar = height > 0;
260 wlr_scene_node_set_enabled(&con->title_bar.tree->node, has_title_bar);
261 if (!has_title_bar) {
262 return;
263 }
264
265 wlr_scene_node_set_position(&con->title_bar.tree->node, x, y);
266
267 con->title_width = width;
268 container_arrange_title_bar(con);
269}
270
271static void disable_container(struct sway_container *con) {
272 if (con->view) {
273 wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree);
274 } else {
275 for (int i = 0; i < con->current.children->length; i++) {
276 struct sway_container *child = con->current.children->items[i];
277
278 wlr_scene_node_reparent(&child->scene_tree->node, con->content_tree);
279
280 disable_container(child);
281 }
282 }
283}
284
285static void arrange_container(struct sway_container *con,
286 int width, int height, bool title_bar, int gaps);
287
288static void arrange_children(enum sway_container_layout layout, list_t *children,
289 struct sway_container *active, struct wlr_scene_tree *content,
290 int width, int height, int gaps) {
291 int title_bar_height = container_titlebar_height();
292
293 if (layout == L_TABBED) {
294 struct sway_container *first = children->length == 1 ?
295 ((struct sway_container *)children->items[0]) : NULL;
296 if (config->hide_lone_tab && first && first->view &&
297 first->current.border != B_NORMAL) {
298 title_bar_height = 0;
299 }
300
301 double w = (double) width / children->length;
302 int title_offset = 0;
303 for (int i = 0; i < children->length; i++) {
304 struct sway_container *child = children->items[i];
305 bool activated = child == active;
306 int next_title_offset = round(w * i + w);
307
308 arrange_title_bar(child, title_offset, -title_bar_height,
309 next_title_offset - title_offset, title_bar_height);
310 wlr_scene_node_set_enabled(&child->border.tree->node, activated);
311 wlr_scene_node_set_position(&child->scene_tree->node, 0, title_bar_height);
312 wlr_scene_node_reparent(&child->scene_tree->node, content);
313
314 if (activated) {
315 arrange_container(child, width, height - title_bar_height,
316 false, 0);
317 } else {
318 disable_container(child);
319 }
320
321 title_offset = next_title_offset;
238 } 322 }
323 } else if (layout == L_STACKED) {
324 struct sway_container *first = children->length == 1 ?
325 ((struct sway_container *)children->items[0]) : NULL;
326 if (config->hide_lone_tab && first && first->view &&
327 first->current.border != B_NORMAL) {
328 title_bar_height = 0;
329 }
330
331 int title_height = title_bar_height * children->length;
332
333 int y = 0;
334 for (int i = 0; i < children->length; i++) {
335 struct sway_container *child = children->items[i];
336 bool activated = child == active;
337
338 arrange_title_bar(child, 0, y - title_height, width, title_bar_height);
339 wlr_scene_node_set_enabled(&child->border.tree->node, activated);
340 wlr_scene_node_set_position(&child->scene_tree->node, 0, title_height);
341 wlr_scene_node_reparent(&child->scene_tree->node, content);
342
343 if (activated) {
344 arrange_container(child, width, height - title_height,
345 false, 0);
346 } else {
347 disable_container(child);
348 }
349
350 y += title_bar_height;
351 }
352 } else if (layout == L_VERT) {
353 int off = 0;
354 for (int i = 0; i < children->length; i++) {
355 struct sway_container *child = children->items[i];
356 int cheight = child->current.height;
357
358 wlr_scene_node_set_enabled(&child->border.tree->node, true);
359 wlr_scene_node_set_position(&child->scene_tree->node, 0, off);
360 wlr_scene_node_reparent(&child->scene_tree->node, content);
361 arrange_container(child, width, cheight, true, gaps);
362 off += cheight + gaps;
363 }
364 } else if (layout == L_HORIZ) {
365 int off = 0;
366 for (int i = 0; i < children->length; i++) {
367 struct sway_container *child = children->items[i];
368 int cwidth = child->current.width;
369
370 wlr_scene_node_set_enabled(&child->border.tree->node, true);
371 wlr_scene_node_set_position(&child->scene_tree->node, off, 0);
372 wlr_scene_node_reparent(&child->scene_tree->node, content);
373 arrange_container(child, cwidth, height, true, gaps);
374 off += cwidth + gaps;
375 }
376 } else {
377 sway_assert(false, "unreachable");
378 }
379}
380
381static void arrange_container(struct sway_container *con,
382 int width, int height, bool title_bar, int gaps) {
383 // this container might have previously been in the scratchpad,
384 // make sure it's enabled for viewing
385 wlr_scene_node_set_enabled(&con->scene_tree->node, true);
386
387 if (con->output_handler) {
388 wlr_scene_buffer_set_dest_size(con->output_handler, width, height);
239 } 389 }
240 390
241 // Damage the new location 391 if (con->view) {
242 desktop_damage_whole_container(container); 392 int border_top = container_titlebar_height();
243 if (view && view->surface) { 393 int border_width = con->current.border_thickness;
244 struct wlr_surface *surface = view->surface; 394
245 struct wlr_box box = { 395 if (title_bar && con->current.border != B_NORMAL) {
246 .x = container->current.content_x - view->geometry.x, 396 wlr_scene_node_set_enabled(&con->title_bar.tree->node, false);
247 .y = container->current.content_y - view->geometry.y, 397 wlr_scene_node_set_enabled(&con->border.top->node, true);
248 .width = surface->current.width,
249 .height = surface->current.height,
250 };
251 desktop_damage_box(&box);
252 }
253
254 // If the view hasn't responded to the configure, center it within
255 // the container. This is important for fullscreen views which
256 // refuse to resize to the size of the output.
257 if (view && view->surface) {
258 if (view->geometry.width < container->current.content_width) {
259 container->surface_x = container->current.content_x +
260 (container->current.content_width - view->geometry.width) / 2;
261 } else { 398 } else {
262 container->surface_x = container->current.content_x; 399 wlr_scene_node_set_enabled(&con->border.top->node, false);
263 } 400 }
264 if (view->geometry.height < container->current.content_height) { 401
265 container->surface_y = container->current.content_y + 402 if (con->current.border == B_NORMAL) {
266 (container->current.content_height - view->geometry.height) / 2; 403 if (title_bar) {
404 arrange_title_bar(con, 0, 0, width, border_top);
405 } else {
406 border_top = 0;
407 // should be handled by the parent container
408 }
409 } else if (con->current.border == B_PIXEL) {
410 container_update(con);
411 border_top = title_bar && con->current.border_top ? border_width : 0;
412 } else if (con->current.border == B_NONE) {
413 container_update(con);
414 border_top = 0;
415 border_width = 0;
416 } else if (con->current.border == B_CSD) {
417 border_top = 0;
418 border_width = 0;
419 } else {
420 sway_assert(false, "unreachable");
421 }
422
423 int border_bottom = con->current.border_bottom ? border_width : 0;
424 int border_left = con->current.border_left ? border_width : 0;
425 int border_right = con->current.border_right ? border_width : 0;
426
427 wlr_scene_rect_set_size(con->border.top, width, border_top);
428 wlr_scene_rect_set_size(con->border.bottom, width, border_bottom);
429 wlr_scene_rect_set_size(con->border.left,
430 border_left, height - border_top - border_bottom);
431 wlr_scene_rect_set_size(con->border.right,
432 border_right, height - border_top - border_bottom);
433
434 wlr_scene_node_set_position(&con->border.top->node, 0, 0);
435 wlr_scene_node_set_position(&con->border.bottom->node,
436 0, height - border_bottom);
437 wlr_scene_node_set_position(&con->border.left->node,
438 0, border_top);
439 wlr_scene_node_set_position(&con->border.right->node,
440 width - border_right, border_top);
441
442 // make sure to reparent, it's possible that the client just came out of
443 // fullscreen mode where the parent of the surface is not the container
444 wlr_scene_node_reparent(&con->view->scene_tree->node, con->content_tree);
445 wlr_scene_node_set_position(&con->view->scene_tree->node,
446 border_left, border_top);
447 } else {
448 // make sure to disable the title bar if the parent is not managing it
449 if (title_bar) {
450 wlr_scene_node_set_enabled(&con->title_bar.tree->node, false);
451 }
452
453 arrange_children(con->current.layout, con->current.children,
454 con->current.focused_inactive_child, con->content_tree,
455 width, height, gaps);
456 }
457}
458
459static int container_get_gaps(struct sway_container *con) {
460 struct sway_workspace *ws = con->current.workspace;
461 struct sway_container *temp = con;
462 while (temp) {
463 enum sway_container_layout layout;
464 if (temp->current.parent) {
465 layout = temp->current.parent->current.layout;
267 } else { 466 } else {
268 container->surface_y = container->current.content_y; 467 layout = ws->current.layout;
269 } 468 }
469 if (layout == L_TABBED || layout == L_STACKED) {
470 return 0;
471 }
472 temp = temp->pending.parent;
270 } 473 }
474 return ws->gaps_inner;
475}
476
477static void arrange_fullscreen(struct wlr_scene_tree *tree,
478 struct sway_container *fs, struct sway_workspace *ws,
479 int width, int height) {
480 struct wlr_scene_node *fs_node;
481 if (fs->view) {
482 fs_node = &fs->view->scene_tree->node;
483
484 // if we only care about the view, disable any decorations
485 wlr_scene_node_set_enabled(&fs->scene_tree->node, false);
486 } else {
487 fs_node = &fs->scene_tree->node;
488 arrange_container(fs, width, height, true, container_get_gaps(fs));
489 }
490
491 wlr_scene_node_reparent(fs_node, tree);
492 wlr_scene_node_lower_to_bottom(fs_node);
493 wlr_scene_node_set_position(fs_node, 0, 0);
494}
495
496static void arrange_workspace_floating(struct sway_workspace *ws) {
497 for (int i = 0; i < ws->current.floating->length; i++) {
498 struct sway_container *floater = ws->current.floating->items[i];
499 struct wlr_scene_tree *layer = root->layers.floating;
271 500
272 if (!container->node.destroying) { 501 if (floater->current.fullscreen_mode != FULLSCREEN_NONE) {
273 container_discover_outputs(container); 502 continue;
503 }
504
505 if (root->fullscreen_global) {
506 if (container_is_transient_for(floater, root->fullscreen_global)) {
507 layer = root->layers.fullscreen_global;
508 }
509 } else {
510 for (int i = 0; i < root->outputs->length; i++) {
511 struct sway_output *output = root->outputs->items[i];
512 struct sway_workspace *active = output->current.active_workspace;
513
514 if (active && active->fullscreen &&
515 container_is_transient_for(floater, active->fullscreen)) {
516 layer = root->layers.fullscreen;
517 }
518 }
519 }
520
521 wlr_scene_node_reparent(&floater->scene_tree->node, layer);
522 wlr_scene_node_set_position(&floater->scene_tree->node,
523 floater->current.x, floater->current.y);
524 wlr_scene_node_set_enabled(&floater->scene_tree->node, true);
525
526 arrange_container(floater, floater->current.width, floater->current.height,
527 true, ws->gaps_inner);
274 } 528 }
275} 529}
276 530
531static void arrange_workspace_tiling(struct sway_workspace *ws,
532 int width, int height) {
533 arrange_children(ws->current.layout, ws->current.tiling,
534 ws->current.focused_inactive_child, ws->layers.tiling,
535 width, height, ws->gaps_inner);
536}
537
538static void disable_workspace(struct sway_workspace *ws) {
539 // if any containers were just moved to a disabled workspace it will
540 // have the parent of the old workspace. Move the workspace so that it won't
541 // be shown.
542 for (int i = 0; i < ws->current.tiling->length; i++) {
543 struct sway_container *child = ws->current.tiling->items[i];
544
545 wlr_scene_node_reparent(&child->scene_tree->node, ws->layers.tiling);
546 disable_container(child);
547 }
548
549 for (int i = 0; i < ws->current.floating->length; i++) {
550 struct sway_container *floater = ws->current.floating->items[i];
551 wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating);
552 disable_container(floater);
553 wlr_scene_node_set_enabled(&floater->scene_tree->node, false);
554 }
555}
556
557static void arrange_output(struct sway_output *output, int width, int height) {
558 for (int i = 0; i < output->current.workspaces->length; i++) {
559 struct sway_workspace *child = output->current.workspaces->items[i];
560
561 bool activated = output->current.active_workspace == child;
562
563 wlr_scene_node_reparent(&child->layers.tiling->node, output->layers.tiling);
564 wlr_scene_node_reparent(&child->layers.fullscreen->node, output->layers.fullscreen);
565
566 for (int i = 0; i < child->current.floating->length; i++) {
567 struct sway_container *floater = child->current.floating->items[i];
568 wlr_scene_node_reparent(&floater->scene_tree->node, root->layers.floating);
569 wlr_scene_node_set_enabled(&floater->scene_tree->node, activated);
570 }
571
572 if (activated) {
573 struct sway_container *fs = child->current.fullscreen;
574 wlr_scene_node_set_enabled(&child->layers.tiling->node, !fs);
575 wlr_scene_node_set_enabled(&child->layers.fullscreen->node, fs);
576
577 arrange_workspace_floating(child);
578
579 wlr_scene_node_set_enabled(&output->layers.shell_background->node, !fs);
580 wlr_scene_node_set_enabled(&output->layers.shell_bottom->node, !fs);
581 wlr_scene_node_set_enabled(&output->layers.fullscreen->node, fs);
582
583 if (fs) {
584 wlr_scene_rect_set_size(output->fullscreen_background, width, height);
585
586 arrange_fullscreen(child->layers.fullscreen, fs, child,
587 width, height);
588 } else {
589 struct wlr_box *area = &output->usable_area;
590 struct side_gaps *gaps = &child->current_gaps;
591
592 wlr_scene_node_set_position(&child->layers.tiling->node,
593 gaps->left + area->x, gaps->top + area->y);
594
595 arrange_workspace_tiling(child,
596 area->width - gaps->left - gaps->right,
597 area->height - gaps->top - gaps->bottom);
598 }
599 } else {
600 wlr_scene_node_set_enabled(&child->layers.tiling->node, false);
601 wlr_scene_node_set_enabled(&child->layers.fullscreen->node, false);
602
603 disable_workspace(child);
604 }
605 }
606}
607
608void arrange_popups(struct wlr_scene_tree *popups) {
609 struct wlr_scene_node *node;
610 wl_list_for_each(node, &popups->children, link) {
611 struct sway_popup_desc *popup = scene_descriptor_try_get(node,
612 SWAY_SCENE_DESC_POPUP);
613
614 int lx, ly;
615 wlr_scene_node_coords(popup->relative, &lx, &ly);
616 wlr_scene_node_set_position(node, lx, ly);
617 }
618}
619
620static void arrange_root(struct sway_root *root) {
621 struct sway_container *fs = root->fullscreen_global;
622
623 wlr_scene_node_set_enabled(&root->layers.shell_background->node, !fs);
624 wlr_scene_node_set_enabled(&root->layers.shell_bottom->node, !fs);
625 wlr_scene_node_set_enabled(&root->layers.tiling->node, !fs);
626 wlr_scene_node_set_enabled(&root->layers.floating->node, !fs);
627 wlr_scene_node_set_enabled(&root->layers.shell_top->node, !fs);
628 wlr_scene_node_set_enabled(&root->layers.fullscreen->node, !fs);
629
630 // hide all contents in the scratchpad
631 for (int i = 0; i < root->scratchpad->length; i++) {
632 struct sway_container *con = root->scratchpad->items[i];
633
634 wlr_scene_node_set_enabled(&con->scene_tree->node, false);
635 }
636
637 if (fs) {
638 for (int i = 0; i < root->outputs->length; i++) {
639 struct sway_output *output = root->outputs->items[i];
640 struct sway_workspace *ws = output->current.active_workspace;
641
642 if (ws) {
643 arrange_workspace_floating(ws);
644 }
645 }
646
647 arrange_fullscreen(root->layers.fullscreen_global, fs, NULL,
648 root->width, root->height);
649 } else {
650 for (int i = 0; i < root->outputs->length; i++) {
651 struct sway_output *output = root->outputs->items[i];
652
653 wlr_scene_output_set_position(output->scene_output, output->lx, output->ly);
654
655 wlr_scene_node_reparent(&output->layers.shell_background->node, root->layers.shell_background);
656 wlr_scene_node_reparent(&output->layers.shell_bottom->node, root->layers.shell_bottom);
657 wlr_scene_node_reparent(&output->layers.tiling->node, root->layers.tiling);
658 wlr_scene_node_reparent(&output->layers.shell_top->node, root->layers.shell_top);
659 wlr_scene_node_reparent(&output->layers.shell_overlay->node, root->layers.shell_overlay);
660 wlr_scene_node_reparent(&output->layers.fullscreen->node, root->layers.fullscreen);
661 wlr_scene_node_reparent(&output->layers.session_lock->node, root->layers.session_lock);
662
663 wlr_scene_node_set_position(&output->layers.shell_background->node, output->lx, output->ly);
664 wlr_scene_node_set_position(&output->layers.shell_bottom->node, output->lx, output->ly);
665 wlr_scene_node_set_position(&output->layers.tiling->node, output->lx, output->ly);
666 wlr_scene_node_set_position(&output->layers.fullscreen->node, output->lx, output->ly);
667 wlr_scene_node_set_position(&output->layers.shell_top->node, output->lx, output->ly);
668 wlr_scene_node_set_position(&output->layers.shell_overlay->node, output->lx, output->ly);
669 wlr_scene_node_set_position(&output->layers.session_lock->node, output->lx, output->ly);
670
671 arrange_output(output, output->width, output->height);
672 }
673 }
674
675 arrange_popups(root->layers.popup);
676}
677
277/** 678/**
278 * Apply a transaction to the "current" state of the tree. 679 * Apply a transaction to the "current" state of the tree.
279 */ 680 */
@@ -313,74 +714,29 @@ static void transaction_apply(struct sway_transaction *transaction) {
313 714
314 node->instruction = NULL; 715 node->instruction = NULL;
315 } 716 }
316
317 cursor_rebase_all();
318} 717}
319 718
320static void transaction_commit(struct sway_transaction *transaction); 719static void transaction_commit_pending(void);
321 720
322// Return true if both transactions operate on the same nodes 721static void transaction_progress(void) {
323static bool transaction_same_nodes(struct sway_transaction *a, 722 if (!server.queued_transaction) {
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
338static void transaction_progress_queue(void) {
339 if (!server.transactions->length) {
340 return; 723 return;
341 } 724 }
342 // Only the first transaction in the queue is committed, so that's the one 725 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; 726 return;
347 } 727 }
348 transaction_apply(transaction); 728 transaction_apply(server.queued_transaction);
349 transaction_destroy(transaction); 729 arrange_root(root);
350 list_del(server.transactions, 0); 730 cursor_rebase_all();
731 transaction_destroy(server.queued_transaction);
732 server.queued_transaction = NULL;
351 733
352 if (server.transactions->length == 0) { 734 if (!server.pending_transaction) {
353 // The transaction queue is empty, so we're done. 735 sway_idle_inhibit_v1_check_active();
354 sway_idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
355 return; 736 return;
356 } 737 }
357 738
358 // If there's a bunch of consecutive transactions which all apply to the 739 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} 740}
385 741
386static int handle_timeout(void *data) { 742static int handle_timeout(void *data) {
@@ -388,7 +744,7 @@ static int handle_timeout(void *data) {
388 sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)", 744 sway_log(SWAY_DEBUG, "Transaction %p timed out (%zi waiting)",
389 transaction, transaction->num_waiting); 745 transaction, transaction->num_waiting);
390 transaction->num_waiting = 0; 746 transaction->num_waiting = 0;
391 transaction_progress_queue(); 747 transaction_progress();
392 return 0; 748 return 0;
393} 749}
394 750
@@ -400,6 +756,9 @@ static bool should_configure(struct sway_node *node,
400 if (node->destroying) { 756 if (node->destroying) {
401 return false; 757 return false;
402 } 758 }
759 if (!instruction->server_request) {
760 return false;
761 }
403 struct sway_container_state *cstate = &node->sway_container->current; 762 struct sway_container_state *cstate = &node->sway_container->current;
404 struct sway_container_state *istate = &instruction->container_state; 763 struct sway_container_state *istate = &instruction->container_state;
405#if HAVE_XWAYLAND 764#if HAVE_XWAYLAND
@@ -431,28 +790,24 @@ static void transaction_commit(struct sway_transaction *transaction) {
431 struct sway_transaction_instruction *instruction = 790 struct sway_transaction_instruction *instruction =
432 transaction->instructions->items[i]; 791 transaction->instructions->items[i];
433 struct sway_node *node = instruction->node; 792 struct sway_node *node = instruction->node;
793 bool hidden = node_is_view(node) && !node->destroying &&
794 !view_is_visible(node->sway_container->view);
434 if (should_configure(node, instruction)) { 795 if (should_configure(node, instruction)) {
435 instruction->serial = view_configure(node->sway_container->view, 796 instruction->serial = view_configure(node->sway_container->view,
436 instruction->container_state.content_x, 797 instruction->container_state.content_x,
437 instruction->container_state.content_y, 798 instruction->container_state.content_y,
438 instruction->container_state.content_width, 799 instruction->container_state.content_width,
439 instruction->container_state.content_height); 800 instruction->container_state.content_height);
440 ++transaction->num_waiting; 801 if (!hidden) {
441 802 instruction->waiting = true;
442 // From here on we are rendering a saved buffer of the view, which 803 ++transaction->num_waiting;
443 // means we can send a frame done event to make the client redraw it 804 }
444 // as soon as possible. Additionally, this is required if a view is 805
445 // mapping and its default geometry doesn't intersect an output. 806 view_send_frame_done(node->sway_container->view);
446 struct timespec now;
447 clock_gettime(CLOCK_MONOTONIC, &now);
448 wlr_surface_send_frame_done(
449 node->sway_container->view->surface, &now);
450 } 807 }
451 if (node_is_view(node) && wl_list_empty(&node->sway_container->view->saved_buffers)) { 808 if (!hidden && node_is_view(node) &&
809 !node->sway_container->view->saved_surface_tree) {
452 view_save_buffer(node->sway_container->view); 810 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 } 811 }
457 node->instruction = instruction; 812 node->instruction = instruction;
458 } 813 }
@@ -483,6 +838,17 @@ static void transaction_commit(struct sway_transaction *transaction) {
483 } 838 }
484} 839}
485 840
841static void transaction_commit_pending(void) {
842 if (server.queued_transaction) {
843 return;
844 }
845 struct sway_transaction *transaction = server.pending_transaction;
846 server.pending_transaction = NULL;
847 server.queued_transaction = transaction;
848 transaction_commit(transaction);
849 transaction_progress();
850}
851
486static void set_instruction_ready( 852static void set_instruction_ready(
487 struct sway_transaction_instruction *instruction) { 853 struct sway_transaction_instruction *instruction) {
488 struct sway_transaction *transaction = instruction->transaction; 854 struct sway_transaction *transaction = instruction->transaction;
@@ -501,25 +867,28 @@ static void set_instruction_ready(
501 } 867 }
502 868
503 // If the transaction has timed out then its num_waiting will be 0 already. 869 // If the transaction has timed out then its num_waiting will be 0 already.
504 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 870 if (instruction->waiting && transaction->num_waiting > 0 &&
871 --transaction->num_waiting == 0) {
505 sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction); 872 sway_log(SWAY_DEBUG, "Transaction %p is ready", transaction);
506 wl_event_source_timer_update(transaction->timer, 0); 873 wl_event_source_timer_update(transaction->timer, 0);
507 } 874 }
508 875
509 instruction->node->instruction = NULL; 876 instruction->node->instruction = NULL;
510 transaction_progress_queue(); 877 transaction_progress();
511} 878}
512 879
513void transaction_notify_view_ready_by_serial(struct sway_view *view, 880bool transaction_notify_view_ready_by_serial(struct sway_view *view,
514 uint32_t serial) { 881 uint32_t serial) {
515 struct sway_transaction_instruction *instruction = 882 struct sway_transaction_instruction *instruction =
516 view->container->node.instruction; 883 view->container->node.instruction;
517 if (instruction != NULL && instruction->serial == serial) { 884 if (instruction != NULL && instruction->serial == serial) {
518 set_instruction_ready(instruction); 885 set_instruction_ready(instruction);
886 return true;
519 } 887 }
888 return false;
520} 889}
521 890
522void transaction_notify_view_ready_by_geometry(struct sway_view *view, 891bool transaction_notify_view_ready_by_geometry(struct sway_view *view,
523 double x, double y, int width, int height) { 892 double x, double y, int width, int height) {
524 struct sway_transaction_instruction *instruction = 893 struct sway_transaction_instruction *instruction =
525 view->container->node.instruction; 894 view->container->node.instruction;
@@ -529,39 +898,37 @@ void transaction_notify_view_ready_by_geometry(struct sway_view *view,
529 instruction->container_state.content_width == width && 898 instruction->container_state.content_width == width &&
530 instruction->container_state.content_height == height) { 899 instruction->container_state.content_height == height) {
531 set_instruction_ready(instruction); 900 set_instruction_ready(instruction);
901 return true;
532 } 902 }
903 return false;
533} 904}
534 905
535void transaction_notify_view_ready_immediately(struct sway_view *view) { 906static 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) { 907 if (!server.dirty_nodes->length) {
545 return; 908 return;
546 } 909 }
547 struct sway_transaction *transaction = transaction_create(); 910
548 if (!transaction) { 911 if (!server.pending_transaction) {
549 return; 912 server.pending_transaction = transaction_create();
913 if (!server.pending_transaction) {
914 return;
915 }
550 } 916 }
917
551 for (int i = 0; i < server.dirty_nodes->length; ++i) { 918 for (int i = 0; i < server.dirty_nodes->length; ++i) {
552 struct sway_node *node = server.dirty_nodes->items[i]; 919 struct sway_node *node = server.dirty_nodes->items[i];
553 transaction_add_node(transaction, node); 920 transaction_add_node(server.pending_transaction, node, server_request);
554 node->dirty = false; 921 node->dirty = false;
555 } 922 }
556 server.dirty_nodes->length = 0; 923 server.dirty_nodes->length = 0;
557 924
558 list_add(server.transactions, transaction); 925 transaction_commit_pending();
926}
559 927
560 // We only commit the first transaction added to the queue. 928void transaction_commit_dirty(void) {
561 if (server.transactions->length == 1) { 929 _transaction_commit_dirty(true);
562 transaction_commit(transaction); 930}
563 // Attempting to progress the queue here is useful 931
564 // if the transaction has nothing to wait for. 932void transaction_commit_dirty_client(void) {
565 transaction_progress_queue(); 933 _transaction_commit_dirty(false);
566 }
567} 934}
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 667fb9e5..7c417891 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 199309L
2#include <float.h> 1#include <float.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -7,7 +6,7 @@
7#include <wlr/util/edges.h> 6#include <wlr/util/edges.h>
8#include "log.h" 7#include "log.h"
9#include "sway/decoration.h" 8#include "sway/decoration.h"
10#include "sway/desktop.h" 9#include "sway/scene_descriptor.h"
11#include "sway/desktop/transaction.h" 10#include "sway/desktop/transaction.h"
12#include "sway/input/cursor.h" 11#include "sway/input/cursor.h"
13#include "sway/input/input-manager.h" 12#include "sway/input/input-manager.h"
@@ -19,64 +18,45 @@
19#include "sway/tree/workspace.h" 18#include "sway/tree/workspace.h"
20#include "sway/xdg_decoration.h" 19#include "sway/xdg_decoration.h"
21 20
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( 21static struct sway_xdg_popup *popup_create(
55 struct wlr_xdg_popup *wlr_popup, struct sway_view *view); 22 struct wlr_xdg_popup *wlr_popup, struct sway_view *view,
23 struct wlr_scene_tree *parent);
56 24
57static void popup_handle_new_popup(struct wl_listener *listener, void *data) { 25static void popup_handle_new_popup(struct wl_listener *listener, void *data) {
58 struct sway_xdg_popup *popup = 26 struct sway_xdg_popup *popup =
59 wl_container_of(listener, popup, new_popup); 27 wl_container_of(listener, popup, new_popup);
60 struct wlr_xdg_popup *wlr_popup = data; 28 struct wlr_xdg_popup *wlr_popup = data;
61 popup_create(wlr_popup, popup->child.view); 29 popup_create(wlr_popup, popup->view, popup->xdg_surface_tree);
62} 30}
63 31
64static void popup_handle_destroy(struct wl_listener *listener, void *data) { 32static void popup_handle_destroy(struct wl_listener *listener, void *data) {
65 struct sway_xdg_popup *popup = wl_container_of(listener, popup, destroy); 33 struct sway_xdg_popup *popup = wl_container_of(listener, popup, destroy);
66 view_child_destroy(&popup->child); 34
35 wl_list_remove(&popup->new_popup.link);
36 wl_list_remove(&popup->destroy.link);
37 wl_list_remove(&popup->surface_commit.link);
38 wl_list_remove(&popup->reposition.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;
46
47 struct sway_workspace *workspace = view->container->pending.workspace;
48 if (!workspace) {
49 // is null if in the scratchpad
50 return;
51 }
72 52
73 struct sway_output *output = view->container->workspace->output; 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,72 @@ 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 void popup_handle_reposition(struct wl_listener *listener, void *data) {
75 struct sway_xdg_popup *popup = wl_container_of(listener, popup, reposition);
76 popup_unconstrain(popup);
77}
78
79static struct sway_xdg_popup *popup_create(struct wlr_xdg_popup *wlr_popup,
80 struct sway_view *view, struct wlr_scene_tree *parent) {
89 struct wlr_xdg_surface *xdg_surface = wlr_popup->base; 81 struct wlr_xdg_surface *xdg_surface = wlr_popup->base;
90 82
91 struct sway_xdg_popup *popup = 83 struct sway_xdg_popup *popup = calloc(1, sizeof(struct sway_xdg_popup));
92 calloc(1, sizeof(struct sway_xdg_popup)); 84 if (!popup) {
93 if (popup == NULL) { 85 return NULL;
86 }
87
88 popup->wlr_xdg_popup = wlr_popup;
89 popup->view = view;
90
91 popup->scene_tree = wlr_scene_tree_create(parent);
92 if (!popup->scene_tree) {
93 free(popup);
94 return NULL;
95 }
96
97 popup->xdg_surface_tree = wlr_scene_xdg_surface_create(
98 popup->scene_tree, xdg_surface);
99 if (!popup->xdg_surface_tree) {
100 wlr_scene_node_destroy(&popup->scene_tree->node);
101 free(popup);
94 return NULL; 102 return NULL;
95 } 103 }
96 view_child_init(&popup->child, &popup_impl, view, xdg_surface->surface);
97 popup->wlr_xdg_surface = xdg_surface;
98 104
105 popup->desc.relative = &view->content_tree->node;
106 popup->desc.view = view;
107
108 if (!scene_descriptor_assign(&popup->scene_tree->node,
109 SWAY_SCENE_DESC_POPUP, &popup->desc)) {
110 sway_log(SWAY_ERROR, "Failed to allocate a popup scene descriptor");
111 wlr_scene_node_destroy(&popup->scene_tree->node);
112 free(popup);
113 return NULL;
114 }
115
116 popup->wlr_xdg_popup = xdg_surface->popup;
117 struct sway_xdg_shell_view *shell_view =
118 wl_container_of(view, shell_view, view);
119 xdg_surface->data = shell_view;
120
121 wl_signal_add(&xdg_surface->surface->events.commit, &popup->surface_commit);
122 popup->surface_commit.notify = popup_handle_surface_commit;
99 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup); 123 wl_signal_add(&xdg_surface->events.new_popup, &popup->new_popup);
100 popup->new_popup.notify = popup_handle_new_popup; 124 popup->new_popup.notify = popup_handle_new_popup;
101 wl_signal_add(&xdg_surface->events.destroy, &popup->destroy); 125 wl_signal_add(&wlr_popup->events.reposition, &popup->reposition);
126 popup->reposition.notify = popup_handle_reposition;
127 wl_signal_add(&wlr_popup->events.destroy, &popup->destroy);
102 popup->destroy.notify = popup_handle_destroy; 128 popup->destroy.notify = popup_handle_destroy;
103 129
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; 130 return popup;
110} 131}
111 132
112
113static struct sway_xdg_shell_view *xdg_shell_view_from_view( 133static struct sway_xdg_shell_view *xdg_shell_view_from_view(
114 struct sway_view *view) { 134 struct sway_view *view) {
115 if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL, 135 if (!sway_assert(view->type == SWAY_VIEW_XDG_SHELL,
@@ -122,7 +142,7 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view(
122static void get_constraints(struct sway_view *view, double *min_width, 142static void get_constraints(struct sway_view *view, double *min_width,
123 double *max_width, double *min_height, double *max_height) { 143 double *max_width, double *min_height, double *max_height) {
124 struct wlr_xdg_toplevel_state *state = 144 struct wlr_xdg_toplevel_state *state =
125 &view->wlr_xdg_surface->toplevel->current; 145 &view->wlr_xdg_toplevel->current;
126 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN; 146 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
127 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX; 147 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
128 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN; 148 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
@@ -136,9 +156,9 @@ static const char *get_string_prop(struct sway_view *view,
136 } 156 }
137 switch (prop) { 157 switch (prop) {
138 case VIEW_PROP_TITLE: 158 case VIEW_PROP_TITLE:
139 return view->wlr_xdg_surface->toplevel->title; 159 return view->wlr_xdg_toplevel->title;
140 case VIEW_PROP_APP_ID: 160 case VIEW_PROP_APP_ID:
141 return view->wlr_xdg_surface->toplevel->app_id; 161 return view->wlr_xdg_toplevel->app_id;
142 default: 162 default:
143 return NULL; 163 return NULL;
144 } 164 }
@@ -151,50 +171,52 @@ static uint32_t configure(struct sway_view *view, double lx, double ly,
151 if (xdg_shell_view == NULL) { 171 if (xdg_shell_view == NULL) {
152 return 0; 172 return 0;
153 } 173 }
154 return wlr_xdg_toplevel_set_size(view->wlr_xdg_surface, width, height); 174 return wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel,
175 width, height);
155} 176}
156 177
157static void set_activated(struct sway_view *view, bool activated) { 178static void set_activated(struct sway_view *view, bool activated) {
158 if (xdg_shell_view_from_view(view) == NULL) { 179 if (xdg_shell_view_from_view(view) == NULL) {
159 return; 180 return;
160 } 181 }
161 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 182 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} 183}
166 184
167static void set_tiled(struct sway_view *view, bool tiled) { 185static void set_tiled(struct sway_view *view, bool tiled) {
168 if (xdg_shell_view_from_view(view) == NULL) { 186 if (xdg_shell_view_from_view(view) == NULL) {
169 return; 187 return;
170 } 188 }
171 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 189 if (wl_resource_get_version(view->wlr_xdg_toplevel->resource) >=
172 enum wlr_edges edges = WLR_EDGE_NONE; 190 XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) {
173 if (tiled) { 191 enum wlr_edges edges = WLR_EDGE_NONE;
174 edges = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP | 192 if (tiled) {
175 WLR_EDGE_BOTTOM; 193 edges = WLR_EDGE_LEFT | WLR_EDGE_RIGHT | WLR_EDGE_TOP |
194 WLR_EDGE_BOTTOM;
195 }
196 wlr_xdg_toplevel_set_tiled(view->wlr_xdg_toplevel, edges);
197 } else {
198 // The version is too low for the tiled state; configure as maximized instead
199 // to stop the client from drawing decorations outside of the toplevel geometry.
200 wlr_xdg_toplevel_set_maximized(view->wlr_xdg_toplevel, tiled);
176 } 201 }
177 wlr_xdg_toplevel_set_tiled(surface, edges);
178} 202}
179 203
180static void set_fullscreen(struct sway_view *view, bool fullscreen) { 204static void set_fullscreen(struct sway_view *view, bool fullscreen) {
181 if (xdg_shell_view_from_view(view) == NULL) { 205 if (xdg_shell_view_from_view(view) == NULL) {
182 return; 206 return;
183 } 207 }
184 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 208 wlr_xdg_toplevel_set_fullscreen(view->wlr_xdg_toplevel, fullscreen);
185 wlr_xdg_toplevel_set_fullscreen(surface, fullscreen);
186} 209}
187 210
188static void set_resizing(struct sway_view *view, bool resizing) { 211static void set_resizing(struct sway_view *view, bool resizing) {
189 if (xdg_shell_view_from_view(view) == NULL) { 212 if (xdg_shell_view_from_view(view) == NULL) {
190 return; 213 return;
191 } 214 }
192 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 215 wlr_xdg_toplevel_set_resizing(view->wlr_xdg_toplevel, resizing);
193 wlr_xdg_toplevel_set_resizing(surface, resizing);
194} 216}
195 217
196static bool wants_floating(struct sway_view *view) { 218static bool wants_floating(struct sway_view *view) {
197 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_surface->toplevel; 219 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel;
198 struct wlr_xdg_toplevel_state *state = &toplevel->current; 220 struct wlr_xdg_toplevel_state *state = &toplevel->current;
199 return (state->min_width != 0 && state->min_height != 0 221 return (state->min_width != 0 && state->min_height != 0
200 && (state->min_width == state->max_width 222 && (state->min_width == state->max_width
@@ -202,35 +224,17 @@ static bool wants_floating(struct sway_view *view) {
202 || toplevel->parent; 224 || toplevel->parent;
203} 225}
204 226
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, 227static bool is_transient_for(struct sway_view *child,
224 struct sway_view *ancestor) { 228 struct sway_view *ancestor) {
225 if (xdg_shell_view_from_view(child) == NULL) { 229 if (xdg_shell_view_from_view(child) == NULL) {
226 return false; 230 return false;
227 } 231 }
228 struct wlr_xdg_surface *surface = child->wlr_xdg_surface; 232 struct wlr_xdg_toplevel *toplevel = child->wlr_xdg_toplevel;
229 while (surface && surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 233 while (toplevel) {
230 if (surface->toplevel->parent == ancestor->wlr_xdg_surface) { 234 if (toplevel->parent == ancestor->wlr_xdg_toplevel) {
231 return true; 235 return true;
232 } 236 }
233 surface = surface->toplevel->parent; 237 toplevel = toplevel->parent;
234 } 238 }
235 return false; 239 return false;
236} 240}
@@ -239,17 +243,13 @@ static void _close(struct sway_view *view) {
239 if (xdg_shell_view_from_view(view) == NULL) { 243 if (xdg_shell_view_from_view(view) == NULL) {
240 return; 244 return;
241 } 245 }
242 struct wlr_xdg_surface *surface = view->wlr_xdg_surface; 246 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} 247}
248 248
249static void close_popups(struct sway_view *view) { 249static void close_popups(struct sway_view *view) {
250 struct wlr_xdg_popup *popup, *tmp; 250 struct wlr_xdg_popup *popup, *tmp;
251 wl_list_for_each_safe(popup, tmp, &view->wlr_xdg_surface->popups, link) { 251 wl_list_for_each_safe(popup, tmp, &view->wlr_xdg_toplevel->base->popups, link) {
252 wlr_xdg_popup_destroy(popup->base); 252 wlr_xdg_popup_destroy(popup);
253 } 253 }
254} 254}
255 255
@@ -271,8 +271,6 @@ static const struct sway_view_impl view_impl = {
271 .set_fullscreen = set_fullscreen, 271 .set_fullscreen = set_fullscreen,
272 .set_resizing = set_resizing, 272 .set_resizing = set_resizing,
273 .wants_floating = wants_floating, 273 .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, 274 .is_transient_for = is_transient_for,
277 .close = _close, 275 .close = _close,
278 .close_popups = close_popups, 276 .close_popups = close_popups,
@@ -283,7 +281,21 @@ static void handle_commit(struct wl_listener *listener, void *data) {
283 struct sway_xdg_shell_view *xdg_shell_view = 281 struct sway_xdg_shell_view *xdg_shell_view =
284 wl_container_of(listener, xdg_shell_view, commit); 282 wl_container_of(listener, xdg_shell_view, commit);
285 struct sway_view *view = &xdg_shell_view->view; 283 struct sway_view *view = &xdg_shell_view->view;
286 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_surface; 284 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_toplevel->base;
285
286 if (xdg_surface->initial_commit) {
287 if (view->xdg_decoration != NULL) {
288 set_xdg_decoration_mode(view->xdg_decoration);
289 }
290 // XXX: https://github.com/swaywm/sway/issues/2176
291 wlr_xdg_surface_schedule_configure(xdg_surface);
292 // TODO: wlr_xdg_toplevel_set_bounds()
293 return;
294 }
295
296 if (!xdg_surface->surface->mapped) {
297 return;
298 }
287 299
288 struct wlr_box new_geo; 300 struct wlr_box new_geo;
289 wlr_xdg_surface_get_geometry(xdg_surface, &new_geo); 301 wlr_xdg_surface_get_geometry(xdg_surface, &new_geo);
@@ -293,22 +305,35 @@ static void handle_commit(struct wl_listener *listener, void *data) {
293 new_geo.y != view->geometry.y; 305 new_geo.y != view->geometry.y;
294 306
295 if (new_size) { 307 if (new_size) {
296 // The view has unexpectedly sent a new size 308 // The client changed its surface size in this commit. For floating
297 desktop_damage_view(view); 309 // containers, we resize the container to match. For tiling containers,
298 view_update_size(view, new_geo.width, new_geo.height); 310 // we only recenter the surface.
299 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box)); 311 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
300 desktop_damage_view(view); 312 if (container_is_floating(view->container)) {
301 transaction_commit_dirty(); 313 view_update_size(view);
314 // Only set the toplevel size the current container actually has a size.
315 if (view->container->current.width) {
316 wlr_xdg_toplevel_set_size(view->wlr_xdg_toplevel, view->geometry.width,
317 view->geometry.height);
318 }
319 transaction_commit_dirty_client();
320 }
321
322 view_center_and_clip_surface(view);
302 } 323 }
303 324
304 if (view->container->node.instruction) { 325 if (view->container->node.instruction) {
305 transaction_notify_view_ready_by_serial(view, 326 bool successful = transaction_notify_view_ready_by_serial(view,
306 xdg_surface->configure_serial); 327 xdg_surface->current.configure_serial);
307 } else if (new_size) { 328
308 transaction_notify_view_ready_immediately(view); 329 // If we saved the view and this commit isn't what we're looking for
330 // that means the user will never actually see the buffers submitted to
331 // us here. Just send frame done events to these surfaces so they can
332 // commit another time for us.
333 if (view->saved_surface_tree && !successful) {
334 view_send_frame_done(view);
335 }
309 } 336 }
310
311 view_damage_from(view);
312} 337}
313 338
314static void handle_set_title(struct wl_listener *listener, void *data) { 339static void handle_set_title(struct wl_listener *listener, void *data) {
@@ -323,6 +348,7 @@ static void handle_set_app_id(struct wl_listener *listener, void *data) {
323 struct sway_xdg_shell_view *xdg_shell_view = 348 struct sway_xdg_shell_view *xdg_shell_view =
324 wl_container_of(listener, xdg_shell_view, set_app_id); 349 wl_container_of(listener, xdg_shell_view, set_app_id);
325 struct sway_view *view = &xdg_shell_view->view; 350 struct sway_view *view = &xdg_shell_view->view;
351 view_update_app_id(view);
326 view_execute_criteria(view); 352 view_execute_criteria(view);
327} 353}
328 354
@@ -330,31 +356,42 @@ static void handle_new_popup(struct wl_listener *listener, void *data) {
330 struct sway_xdg_shell_view *xdg_shell_view = 356 struct sway_xdg_shell_view *xdg_shell_view =
331 wl_container_of(listener, xdg_shell_view, new_popup); 357 wl_container_of(listener, xdg_shell_view, new_popup);
332 struct wlr_xdg_popup *wlr_popup = data; 358 struct wlr_xdg_popup *wlr_popup = data;
333 popup_create(wlr_popup, &xdg_shell_view->view); 359
360 struct sway_xdg_popup *popup = popup_create(wlr_popup,
361 &xdg_shell_view->view, root->layers.popup);
362 if (!popup) {
363 return;
364 }
365
366 int lx, ly;
367 wlr_scene_node_coords(&popup->view->content_tree->node, &lx, &ly);
368 wlr_scene_node_set_position(&popup->scene_tree->node, lx, ly);
369}
370
371static void handle_request_maximize(struct wl_listener *listener, void *data) {
372 struct sway_xdg_shell_view *xdg_shell_view =
373 wl_container_of(listener, xdg_shell_view, request_maximize);
374 struct wlr_xdg_toplevel *toplevel = xdg_shell_view->view.wlr_xdg_toplevel;
375 wlr_xdg_surface_schedule_configure(toplevel->base);
334} 376}
335 377
336static void handle_request_fullscreen(struct wl_listener *listener, void *data) { 378static void handle_request_fullscreen(struct wl_listener *listener, void *data) {
337 struct sway_xdg_shell_view *xdg_shell_view = 379 struct sway_xdg_shell_view *xdg_shell_view =
338 wl_container_of(listener, xdg_shell_view, request_fullscreen); 380 wl_container_of(listener, xdg_shell_view, request_fullscreen);
339 struct wlr_xdg_toplevel_set_fullscreen_event *e = data; 381 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; 382 struct sway_view *view = &xdg_shell_view->view;
343 383
344 if (!sway_assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL, 384 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; 385 return;
351 } 386 }
352 387
353 struct sway_container *container = view->container; 388 struct sway_container *container = view->container;
354 if (e->fullscreen && e->output && e->output->data) { 389 struct wlr_xdg_toplevel_requested *req = &toplevel->requested;
355 struct sway_output *output = e->output->data; 390 if (req->fullscreen && req->fullscreen_output && req->fullscreen_output->data) {
391 struct sway_output *output = req->fullscreen_output->data;
356 struct sway_workspace *ws = output_get_active_workspace(output); 392 struct sway_workspace *ws = output_get_active_workspace(output);
357 if (ws && !container_is_scratchpad_hidden(container)) { 393 if (ws && !container_is_scratchpad_hidden(container) &&
394 container->pending.workspace != ws) {
358 if (container_is_floating(container)) { 395 if (container_is_floating(container)) {
359 workspace_add_floating(ws, container); 396 workspace_add_floating(ws, container);
360 } else { 397 } else {
@@ -363,22 +400,18 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
363 } 400 }
364 } 401 }
365 402
366 container_set_fullscreen(container, e->fullscreen); 403 container_set_fullscreen(container, req->fullscreen);
367 404
368 arrange_root(); 405 arrange_root();
369 transaction_commit_dirty(); 406 transaction_commit_dirty();
370} 407}
371 408
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) { 409static void handle_request_move(struct wl_listener *listener, void *data) {
378 struct sway_xdg_shell_view *xdg_shell_view = 410 struct sway_xdg_shell_view *xdg_shell_view =
379 wl_container_of(listener, xdg_shell_view, request_move); 411 wl_container_of(listener, xdg_shell_view, request_move);
380 struct sway_view *view = &xdg_shell_view->view; 412 struct sway_view *view = &xdg_shell_view->view;
381 if (!container_is_floating(view->container)) { 413 if (!container_is_floating(view->container) ||
414 view->container->pending.fullscreen_mode) {
382 return; 415 return;
383 } 416 }
384 struct wlr_xdg_toplevel_move_event *e = data; 417 struct wlr_xdg_toplevel_move_event *e = data;
@@ -413,10 +446,9 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
413 446
414 view_unmap(view); 447 view_unmap(view);
415 448
416 wl_list_remove(&xdg_shell_view->commit.link);
417 wl_list_remove(&xdg_shell_view->new_popup.link); 449 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); 450 wl_list_remove(&xdg_shell_view->request_maximize.link);
451 wl_list_remove(&xdg_shell_view->request_fullscreen.link);
420 wl_list_remove(&xdg_shell_view->request_move.link); 452 wl_list_remove(&xdg_shell_view->request_move.link);
421 wl_list_remove(&xdg_shell_view->request_resize.link); 453 wl_list_remove(&xdg_shell_view->request_resize.link);
422 wl_list_remove(&xdg_shell_view->set_title.link); 454 wl_list_remove(&xdg_shell_view->set_title.link);
@@ -427,62 +459,61 @@ static void handle_map(struct wl_listener *listener, void *data) {
427 struct sway_xdg_shell_view *xdg_shell_view = 459 struct sway_xdg_shell_view *xdg_shell_view =
428 wl_container_of(listener, xdg_shell_view, map); 460 wl_container_of(listener, xdg_shell_view, map);
429 struct sway_view *view = &xdg_shell_view->view; 461 struct sway_view *view = &xdg_shell_view->view;
430 struct wlr_xdg_surface *xdg_surface = view->wlr_xdg_surface; 462 struct wlr_xdg_toplevel *toplevel = view->wlr_xdg_toplevel;
431 463
432 view->natural_width = view->wlr_xdg_surface->geometry.width; 464 view->natural_width = toplevel->base->current.geometry.width;
433 view->natural_height = view->wlr_xdg_surface->geometry.height; 465 view->natural_height = toplevel->base->current.geometry.height;
434 if (!view->natural_width && !view->natural_height) { 466 if (!view->natural_width && !view->natural_height) {
435 view->natural_width = view->wlr_xdg_surface->surface->current.width; 467 view->natural_width = toplevel->base->surface->current.width;
436 view->natural_height = view->wlr_xdg_surface->surface->current.height; 468 view->natural_height = toplevel->base->surface->current.height;
437 } 469 }
438 470
439 bool csd = false; 471 bool csd = false;
440 472
441 if (!view->xdg_decoration) { 473 if (view->xdg_decoration) {
474 enum wlr_xdg_toplevel_decoration_v1_mode mode =
475 view->xdg_decoration->wlr_xdg_decoration->requested_mode;
476 csd = mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
477 } else {
442 struct sway_server_decoration *deco = 478 struct sway_server_decoration *deco =
443 decoration_from_surface(xdg_surface->surface); 479 decoration_from_surface(toplevel->base->surface);
444 csd = !deco || deco->wlr_server_decoration->mode == 480 csd = !deco || deco->wlr_server_decoration->mode ==
445 WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT; 481 WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT;
446
447 } 482 }
448 483
449 view_map(view, view->wlr_xdg_surface->surface, 484 view_map(view, toplevel->base->surface,
450 xdg_surface->toplevel->client_pending.fullscreen, 485 toplevel->requested.fullscreen,
451 xdg_surface->toplevel->client_pending.fullscreen_output, 486 toplevel->requested.fullscreen_output,
452 csd); 487 csd);
453 488
454 transaction_commit_dirty(); 489 transaction_commit_dirty();
455 490
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; 491 xdg_shell_view->new_popup.notify = handle_new_popup;
461 wl_signal_add(&xdg_surface->events.new_popup, 492 wl_signal_add(&toplevel->base->events.new_popup,
462 &xdg_shell_view->new_popup); 493 &xdg_shell_view->new_popup);
463 494
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; 495 xdg_shell_view->request_maximize.notify = handle_request_maximize;
469 wl_signal_add(&xdg_surface->toplevel->events.request_maximize, 496 wl_signal_add(&toplevel->events.request_maximize,
470 &xdg_shell_view->request_maximize); 497 &xdg_shell_view->request_maximize);
471 498
499 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen;
500 wl_signal_add(&toplevel->events.request_fullscreen,
501 &xdg_shell_view->request_fullscreen);
502
472 xdg_shell_view->request_move.notify = handle_request_move; 503 xdg_shell_view->request_move.notify = handle_request_move;
473 wl_signal_add(&xdg_surface->toplevel->events.request_move, 504 wl_signal_add(&toplevel->events.request_move,
474 &xdg_shell_view->request_move); 505 &xdg_shell_view->request_move);
475 506
476 xdg_shell_view->request_resize.notify = handle_request_resize; 507 xdg_shell_view->request_resize.notify = handle_request_resize;
477 wl_signal_add(&xdg_surface->toplevel->events.request_resize, 508 wl_signal_add(&toplevel->events.request_resize,
478 &xdg_shell_view->request_resize); 509 &xdg_shell_view->request_resize);
479 510
480 xdg_shell_view->set_title.notify = handle_set_title; 511 xdg_shell_view->set_title.notify = handle_set_title;
481 wl_signal_add(&xdg_surface->toplevel->events.set_title, 512 wl_signal_add(&toplevel->events.set_title,
482 &xdg_shell_view->set_title); 513 &xdg_shell_view->set_title);
483 514
484 xdg_shell_view->set_app_id.notify = handle_set_app_id; 515 xdg_shell_view->set_app_id.notify = handle_set_app_id;
485 wl_signal_add(&xdg_surface->toplevel->events.set_app_id, 516 wl_signal_add(&toplevel->events.set_app_id,
486 &xdg_shell_view->set_app_id); 517 &xdg_shell_view->set_app_id);
487} 518}
488 519
@@ -496,7 +527,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
496 wl_list_remove(&xdg_shell_view->destroy.link); 527 wl_list_remove(&xdg_shell_view->destroy.link);
497 wl_list_remove(&xdg_shell_view->map.link); 528 wl_list_remove(&xdg_shell_view->map.link);
498 wl_list_remove(&xdg_shell_view->unmap.link); 529 wl_list_remove(&xdg_shell_view->unmap.link);
499 view->wlr_xdg_surface = NULL; 530 wl_list_remove(&xdg_shell_view->commit.link);
531 view->wlr_xdg_toplevel = NULL;
500 if (view->xdg_decoration) { 532 if (view->xdg_decoration) {
501 view->xdg_decoration->view = NULL; 533 view->xdg_decoration->view = NULL;
502 } 534 }
@@ -508,17 +540,12 @@ struct sway_view *view_from_wlr_xdg_surface(
508 return xdg_surface->data; 540 return xdg_surface->data;
509} 541}
510 542
511void handle_xdg_shell_surface(struct wl_listener *listener, void *data) { 543void handle_xdg_shell_toplevel(struct wl_listener *listener, void *data) {
512 struct wlr_xdg_surface *xdg_surface = data; 544 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 545
519 sway_log(SWAY_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'", 546 sway_log(SWAY_DEBUG, "New xdg_shell toplevel title='%s' app_id='%s'",
520 xdg_surface->toplevel->title, xdg_surface->toplevel->app_id); 547 xdg_toplevel->title, xdg_toplevel->app_id);
521 wlr_xdg_surface_ping(xdg_surface); 548 wlr_xdg_surface_ping(xdg_toplevel->base);
522 549
523 struct sway_xdg_shell_view *xdg_shell_view = 550 struct sway_xdg_shell_view *xdg_shell_view =
524 calloc(1, sizeof(struct sway_xdg_shell_view)); 551 calloc(1, sizeof(struct sway_xdg_shell_view));
@@ -526,17 +553,29 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
526 return; 553 return;
527 } 554 }
528 555
529 view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl); 556 if (!view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl)) {
530 xdg_shell_view->view.wlr_xdg_surface = xdg_surface; 557 free(xdg_shell_view);
558 return;
559 }
560 xdg_shell_view->view.wlr_xdg_toplevel = xdg_toplevel;
531 561
532 xdg_shell_view->map.notify = handle_map; 562 xdg_shell_view->map.notify = handle_map;
533 wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); 563 wl_signal_add(&xdg_toplevel->base->surface->events.map, &xdg_shell_view->map);
534 564
535 xdg_shell_view->unmap.notify = handle_unmap; 565 xdg_shell_view->unmap.notify = handle_unmap;
536 wl_signal_add(&xdg_surface->events.unmap, &xdg_shell_view->unmap); 566 wl_signal_add(&xdg_toplevel->base->surface->events.unmap, &xdg_shell_view->unmap);
567
568 xdg_shell_view->commit.notify = handle_commit;
569 wl_signal_add(&xdg_toplevel->base->surface->events.commit,
570 &xdg_shell_view->commit);
537 571
538 xdg_shell_view->destroy.notify = handle_destroy; 572 xdg_shell_view->destroy.notify = handle_destroy;
539 wl_signal_add(&xdg_surface->events.destroy, &xdg_shell_view->destroy); 573 wl_signal_add(&xdg_toplevel->events.destroy, &xdg_shell_view->destroy);
574
575 wlr_scene_xdg_surface_create(xdg_shell_view->view.content_tree, xdg_toplevel->base);
576
577 xdg_toplevel->base->data = xdg_shell_view;
540 578
541 xdg_surface->data = xdg_shell_view; 579 wlr_xdg_toplevel_set_wm_capabilities(xdg_toplevel,
580 XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN);
542} 581}
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index e1a2e463..270cf08f 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -1,20 +1,23 @@
1#define _POSIX_C_SOURCE 199309L
2#include <float.h> 1#include <float.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
5#include <wayland-server-core.h> 4#include <wayland-server-core.h>
6#include <wlr/types/wlr_output_layout.h> 5#include <wlr/types/wlr_output_layout.h>
7#include <wlr/types/wlr_output.h> 6#include <wlr/types/wlr_output.h>
7#include <wlr/types/wlr_xdg_activation_v1.h>
8#include <wlr/types/wlr_scene.h>
8#include <wlr/xwayland.h> 9#include <wlr/xwayland.h>
10#include <xcb/xcb_icccm.h>
9#include "log.h" 11#include "log.h"
10#include "sway/desktop.h"
11#include "sway/desktop/transaction.h" 12#include "sway/desktop/transaction.h"
12#include "sway/input/cursor.h" 13#include "sway/input/cursor.h"
13#include "sway/input/input-manager.h" 14#include "sway/input/input-manager.h"
14#include "sway/input/seat.h" 15#include "sway/input/seat.h"
15#include "sway/output.h" 16#include "sway/output.h"
17#include "sway/scene_descriptor.h"
16#include "sway/tree/arrange.h" 18#include "sway/tree/arrange.h"
17#include "sway/tree/container.h" 19#include "sway/tree/container.h"
20#include "sway/server.h"
18#include "sway/tree/view.h" 21#include "sway/tree/view.h"
19#include "sway/tree/workspace.h" 22#include "sway/tree/workspace.h"
20 23
@@ -42,29 +45,12 @@ static void unmanaged_handle_request_configure(struct wl_listener *listener,
42 ev->width, ev->height); 45 ev->width, ev->height);
43} 46}
44 47
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) { 48static void unmanaged_handle_set_geometry(struct wl_listener *listener, void *data) {
55 struct sway_xwayland_unmanaged *surface = 49 struct sway_xwayland_unmanaged *surface =
56 wl_container_of(listener, surface, set_geometry); 50 wl_container_of(listener, surface, set_geometry);
57 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 51 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
58 52
59 if (xsurface->x != surface->lx || xsurface->y != surface->ly) { 53 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} 54}
69 55
70static void unmanaged_handle_map(struct wl_listener *listener, void *data) { 56static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
@@ -72,17 +58,18 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
72 wl_container_of(listener, surface, map); 58 wl_container_of(listener, surface, map);
73 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 59 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
74 60
75 wl_list_insert(root->xwayland_unmanaged.prev, &surface->link); 61 surface->surface_scene = wlr_scene_surface_create(root->layers.unmanaged,
76 62 xsurface->surface);
77 wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry);
78 surface->set_geometry.notify = unmanaged_handle_set_geometry;
79 63
80 wl_signal_add(&xsurface->surface->events.commit, &surface->commit); 64 if (surface->surface_scene) {
81 surface->commit.notify = unmanaged_handle_commit; 65 scene_descriptor_assign(&surface->surface_scene->buffer->node,
66 SWAY_SCENE_DESC_XWAYLAND_UNMANAGED, surface);
67 wlr_scene_node_set_position(&surface->surface_scene->buffer->node,
68 xsurface->x, xsurface->y);
82 69
83 surface->lx = xsurface->x; 70 wl_signal_add(&xsurface->events.set_geometry, &surface->set_geometry);
84 surface->ly = xsurface->y; 71 surface->set_geometry.notify = unmanaged_handle_set_geometry;
85 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true); 72 }
86 73
87 if (wlr_xwayland_or_surface_wants_focus(xsurface)) { 74 if (wlr_xwayland_or_surface_wants_focus(xsurface)) {
88 struct sway_seat *seat = input_manager_current_seat(); 75 struct sway_seat *seat = input_manager_current_seat();
@@ -96,23 +83,22 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
96 struct sway_xwayland_unmanaged *surface = 83 struct sway_xwayland_unmanaged *surface =
97 wl_container_of(listener, surface, unmap); 84 wl_container_of(listener, surface, unmap);
98 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 85 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
99 desktop_damage_surface(xsurface->surface, xsurface->x, xsurface->y, true); 86
100 wl_list_remove(&surface->link); 87 if (surface->surface_scene) {
101 wl_list_remove(&surface->set_geometry.link); 88 wl_list_remove(&surface->set_geometry.link);
102 wl_list_remove(&surface->commit.link); 89
90 wlr_scene_node_destroy(&surface->surface_scene->buffer->node);
91 surface->surface_scene = NULL;
92 }
103 93
104 struct sway_seat *seat = input_manager_current_seat(); 94 struct sway_seat *seat = input_manager_current_seat();
105 if (seat->wlr_seat->keyboard_state.focused_surface == xsurface->surface) { 95 if (seat->wlr_seat->keyboard_state.focused_surface == xsurface->surface) {
106 // This simply returns focus to the parent surface if there's one available. 96 // This simply returns focus to the parent surface if there's one available.
107 // This seems to handle JetBrains issues. 97 // This seems to handle JetBrains issues.
108 if (xsurface->parent && xsurface->parent->surface && 98 if (xsurface->parent && xsurface->parent->surface
109 wlr_surface_is_xwayland_surface(xsurface->parent->surface)) { 99 && wlr_xwayland_or_surface_wants_focus(xsurface->parent)) {
110 struct wlr_xwayland_surface *next_surface = 100 seat_set_focus_surface(seat, xsurface->parent->surface, false);
111 wlr_xwayland_surface_from_wlr_surface(xsurface->parent->surface); 101 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 } 102 }
117 103
118 // Restore focus 104 // Restore focus
@@ -125,18 +111,53 @@ static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
125 } 111 }
126} 112}
127 113
114static void unmanaged_handle_request_activate(struct wl_listener *listener, void *data) {
115 struct sway_xwayland_unmanaged *surface =
116 wl_container_of(listener, surface, request_activate);
117 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
118 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
119 return;
120 }
121 struct sway_seat *seat = input_manager_current_seat();
122 struct sway_container *focus = seat_get_focused_container(seat);
123 if (focus && focus->view && focus->view->pid != xsurface->pid) {
124 return;
125 }
126
127 seat_set_focus_surface(seat, xsurface->surface, false);
128}
129
130static void unmanaged_handle_associate(struct wl_listener *listener, void *data) {
131 struct sway_xwayland_unmanaged *surface =
132 wl_container_of(listener, surface, associate);
133 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
134 wl_signal_add(&xsurface->surface->events.map, &surface->map);
135 surface->map.notify = unmanaged_handle_map;
136 wl_signal_add(&xsurface->surface->events.unmap, &surface->unmap);
137 surface->unmap.notify = unmanaged_handle_unmap;
138}
139
140static void unmanaged_handle_dissociate(struct wl_listener *listener, void *data) {
141 struct sway_xwayland_unmanaged *surface =
142 wl_container_of(listener, surface, dissociate);
143 wl_list_remove(&surface->map.link);
144 wl_list_remove(&surface->unmap.link);
145}
146
128static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) { 147static void unmanaged_handle_destroy(struct wl_listener *listener, void *data) {
129 struct sway_xwayland_unmanaged *surface = 148 struct sway_xwayland_unmanaged *surface =
130 wl_container_of(listener, surface, destroy); 149 wl_container_of(listener, surface, destroy);
131 wl_list_remove(&surface->request_configure.link); 150 wl_list_remove(&surface->request_configure.link);
132 wl_list_remove(&surface->map.link); 151 wl_list_remove(&surface->associate.link);
133 wl_list_remove(&surface->unmap.link); 152 wl_list_remove(&surface->dissociate.link);
134 wl_list_remove(&surface->destroy.link); 153 wl_list_remove(&surface->destroy.link);
135 wl_list_remove(&surface->override_redirect.link); 154 wl_list_remove(&surface->override_redirect.link);
155 wl_list_remove(&surface->request_activate.link);
136 free(surface); 156 free(surface);
137} 157}
138 158
139static void handle_map(struct wl_listener *listener, void *data); 159static void handle_map(struct wl_listener *listener, void *data);
160static void handle_associate(struct wl_listener *listener, void *data);
140 161
141struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface); 162struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsurface);
142 163
@@ -145,14 +166,22 @@ static void unmanaged_handle_override_redirect(struct wl_listener *listener, voi
145 wl_container_of(listener, surface, override_redirect); 166 wl_container_of(listener, surface, override_redirect);
146 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface; 167 struct wlr_xwayland_surface *xsurface = surface->wlr_xwayland_surface;
147 168
148 bool mapped = xsurface->mapped; 169 bool associated = xsurface->surface != NULL;
170 bool mapped = associated && xsurface->surface->mapped;
149 if (mapped) { 171 if (mapped) {
150 unmanaged_handle_unmap(&surface->unmap, NULL); 172 unmanaged_handle_unmap(&surface->unmap, NULL);
151 } 173 }
174 if (associated) {
175 unmanaged_handle_dissociate(&surface->dissociate, NULL);
176 }
152 177
153 unmanaged_handle_destroy(&surface->destroy, NULL); 178 unmanaged_handle_destroy(&surface->destroy, NULL);
154 xsurface->data = NULL; 179 xsurface->data = NULL;
180
155 struct sway_xwayland_view *xwayland_view = create_xwayland_view(xsurface); 181 struct sway_xwayland_view *xwayland_view = create_xwayland_view(xsurface);
182 if (associated) {
183 handle_associate(&xwayland_view->associate, NULL);
184 }
156 if (mapped) { 185 if (mapped) {
157 handle_map(&xwayland_view->map, xsurface); 186 handle_map(&xwayland_view->map, xsurface);
158 } 187 }
@@ -172,14 +201,16 @@ static struct sway_xwayland_unmanaged *create_unmanaged(
172 wl_signal_add(&xsurface->events.request_configure, 201 wl_signal_add(&xsurface->events.request_configure,
173 &surface->request_configure); 202 &surface->request_configure);
174 surface->request_configure.notify = unmanaged_handle_request_configure; 203 surface->request_configure.notify = unmanaged_handle_request_configure;
175 wl_signal_add(&xsurface->events.map, &surface->map); 204 wl_signal_add(&xsurface->events.associate, &surface->associate);
176 surface->map.notify = unmanaged_handle_map; 205 surface->associate.notify = unmanaged_handle_associate;
177 wl_signal_add(&xsurface->events.unmap, &surface->unmap); 206 wl_signal_add(&xsurface->events.dissociate, &surface->dissociate);
178 surface->unmap.notify = unmanaged_handle_unmap; 207 surface->dissociate.notify = unmanaged_handle_dissociate;
179 wl_signal_add(&xsurface->events.destroy, &surface->destroy); 208 wl_signal_add(&xsurface->events.destroy, &surface->destroy);
180 surface->destroy.notify = unmanaged_handle_destroy; 209 surface->destroy.notify = unmanaged_handle_destroy;
181 wl_signal_add(&xsurface->events.set_override_redirect, &surface->override_redirect); 210 wl_signal_add(&xsurface->events.set_override_redirect, &surface->override_redirect);
182 surface->override_redirect.notify = unmanaged_handle_override_redirect; 211 surface->override_redirect.notify = unmanaged_handle_override_redirect;
212 wl_signal_add(&xsurface->events.request_activate, &surface->request_activate);
213 surface->request_activate.notify = unmanaged_handle_request_activate;
183 214
184 return surface; 215 return surface;
185} 216}
@@ -258,6 +289,7 @@ static void set_activated(struct sway_view *view, bool activated) {
258 } 289 }
259 290
260 wlr_xwayland_surface_activate(surface, activated); 291 wlr_xwayland_surface_activate(surface, activated);
292 wlr_xwayland_surface_restack(surface, NULL, XCB_STACK_MODE_ABOVE);
261} 293}
262 294
263static void set_tiled(struct sway_view *view, bool tiled) { 295static void set_tiled(struct sway_view *view, bool tiled) {
@@ -297,7 +329,7 @@ static bool wants_floating(struct sway_view *view) {
297 } 329 }
298 } 330 }
299 331
300 struct wlr_xwayland_surface_size_hints *size_hints = surface->size_hints; 332 xcb_size_hints_t *size_hints = surface->size_hints;
301 if (size_hints != NULL && 333 if (size_hints != NULL &&
302 size_hints->min_width > 0 && size_hints->min_height > 0 && 334 size_hints->min_width > 0 && size_hints->min_height > 0 &&
303 (size_hints->max_width == size_hints->min_width || 335 (size_hints->max_width == size_hints->min_width ||
@@ -351,7 +383,7 @@ static void destroy(struct sway_view *view) {
351static void get_constraints(struct sway_view *view, double *min_width, 383static void get_constraints(struct sway_view *view, double *min_width,
352 double *max_width, double *min_height, double *max_height) { 384 double *max_width, double *min_height, double *max_height) {
353 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface; 385 struct wlr_xwayland_surface *surface = view->wlr_xwayland_surface;
354 struct wlr_xwayland_surface_size_hints *size_hints = surface->size_hints; 386 xcb_size_hints_t *size_hints = surface->size_hints;
355 387
356 if (size_hints == NULL) { 388 if (size_hints == NULL) {
357 *min_width = DBL_MIN; 389 *min_width = DBL_MIN;
@@ -381,17 +413,6 @@ static const struct sway_view_impl view_impl = {
381 .destroy = destroy, 413 .destroy = destroy,
382}; 414};
383 415
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) { 416static void handle_commit(struct wl_listener *listener, void *data) {
396 struct sway_xwayland_view *xwayland_view = 417 struct sway_xwayland_view *xwayland_view =
397 wl_container_of(listener, xwayland_view, commit); 418 wl_container_of(listener, xwayland_view, commit);
@@ -399,33 +420,38 @@ static void handle_commit(struct wl_listener *listener, void *data) {
399 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 420 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
400 struct wlr_surface_state *state = &xsurface->surface->current; 421 struct wlr_surface_state *state = &xsurface->surface->current;
401 422
423 struct wlr_box new_geo = {0};
424 new_geo.width = state->width;
425 new_geo.height = state->height;
426
427 bool new_size = new_geo.width != view->geometry.width ||
428 new_geo.height != view->geometry.height;
429
430 if (new_size) {
431 // The client changed its surface size in this commit. For floating
432 // containers, we resize the container to match. For tiling containers,
433 // we only recenter the surface.
434 memcpy(&view->geometry, &new_geo, sizeof(struct wlr_box));
435 if (container_is_floating(view->container)) {
436 view_update_size(view);
437 transaction_commit_dirty_client();
438 }
439
440 view_center_and_clip_surface(view);
441 }
442
402 if (view->container->node.instruction) { 443 if (view->container->node.instruction) {
403 get_geometry(view, &view->geometry); 444 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); 445 xsurface->x, xsurface->y, state->width, state->height);
406 } else { 446
407 struct wlr_box new_geo; 447 // If we saved the view and this commit isn't what we're looking for
408 get_geometry(view, &new_geo); 448 // that means the user will never actually see the buffers submitted to
409 449 // us here. Just send frame done events to these surfaces so they can
410 if ((new_geo.width != view->geometry.width || 450 // commit another time for us.
411 new_geo.height != view->geometry.height || 451 if (view->saved_surface_tree && !successful) {
412 new_geo.x != view->geometry.x || 452 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 } 453 }
426 } 454 }
427
428 view_damage_from(view);
429} 455}
430 456
431static void handle_destroy(struct wl_listener *listener, void *data) { 457static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -438,6 +464,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
438 wl_list_remove(&xwayland_view->commit.link); 464 wl_list_remove(&xwayland_view->commit.link);
439 } 465 }
440 466
467 xwayland_view->view.wlr_xwayland_surface = NULL;
468
441 wl_list_remove(&xwayland_view->destroy.link); 469 wl_list_remove(&xwayland_view->destroy.link);
442 wl_list_remove(&xwayland_view->request_configure.link); 470 wl_list_remove(&xwayland_view->request_configure.link);
443 wl_list_remove(&xwayland_view->request_fullscreen.link); 471 wl_list_remove(&xwayland_view->request_fullscreen.link);
@@ -448,11 +476,12 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
448 wl_list_remove(&xwayland_view->set_title.link); 476 wl_list_remove(&xwayland_view->set_title.link);
449 wl_list_remove(&xwayland_view->set_class.link); 477 wl_list_remove(&xwayland_view->set_class.link);
450 wl_list_remove(&xwayland_view->set_role.link); 478 wl_list_remove(&xwayland_view->set_role.link);
479 wl_list_remove(&xwayland_view->set_startup_id.link);
451 wl_list_remove(&xwayland_view->set_window_type.link); 480 wl_list_remove(&xwayland_view->set_window_type.link);
452 wl_list_remove(&xwayland_view->set_hints.link); 481 wl_list_remove(&xwayland_view->set_hints.link);
453 wl_list_remove(&xwayland_view->set_decorations.link); 482 wl_list_remove(&xwayland_view->set_decorations.link);
454 wl_list_remove(&xwayland_view->map.link); 483 wl_list_remove(&xwayland_view->associate.link);
455 wl_list_remove(&xwayland_view->unmap.link); 484 wl_list_remove(&xwayland_view->dissociate.link);
456 wl_list_remove(&xwayland_view->override_redirect.link); 485 wl_list_remove(&xwayland_view->override_redirect.link);
457 view_begin_destroy(&xwayland_view->view); 486 view_begin_destroy(&xwayland_view->view);
458} 487}
@@ -466,16 +495,28 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
466 return; 495 return;
467 } 496 }
468 497
498 wl_list_remove(&xwayland_view->commit.link);
499 wl_list_remove(&xwayland_view->surface_tree_destroy.link);
500
501 if (xwayland_view->surface_tree) {
502 wlr_scene_node_destroy(&xwayland_view->surface_tree->node);
503 xwayland_view->surface_tree = NULL;
504 }
505
469 view_unmap(view); 506 view_unmap(view);
507}
470 508
471 wl_list_remove(&xwayland_view->commit.link); 509static void handle_surface_tree_destroy(struct wl_listener *listener, void *data) {
510 struct sway_xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view,
511 surface_tree_destroy);
512 xwayland_view->surface_tree = NULL;
472} 513}
473 514
474static void handle_map(struct wl_listener *listener, void *data) { 515static void handle_map(struct wl_listener *listener, void *data) {
475 struct sway_xwayland_view *xwayland_view = 516 struct sway_xwayland_view *xwayland_view =
476 wl_container_of(listener, xwayland_view, map); 517 wl_container_of(listener, xwayland_view, map);
477 struct wlr_xwayland_surface *xsurface = data;
478 struct sway_view *view = &xwayland_view->view; 518 struct sway_view *view = &xwayland_view->view;
519 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
479 520
480 view->natural_width = xsurface->width; 521 view->natural_width = xsurface->width;
481 view->natural_height = xsurface->height; 522 view->natural_height = xsurface->height;
@@ -488,23 +529,42 @@ static void handle_map(struct wl_listener *listener, void *data) {
488 // Put it back into the tree 529 // Put it back into the tree
489 view_map(view, xsurface->surface, xsurface->fullscreen, NULL, false); 530 view_map(view, xsurface->surface, xsurface->fullscreen, NULL, false);
490 531
532 xwayland_view->surface_tree = wlr_scene_subsurface_tree_create(
533 xwayland_view->view.content_tree, xsurface->surface);
534
535 if (xwayland_view->surface_tree) {
536 xwayland_view->surface_tree_destroy.notify = handle_surface_tree_destroy;
537 wl_signal_add(&xwayland_view->surface_tree->node.events.destroy,
538 &xwayland_view->surface_tree_destroy);
539 }
540
491 transaction_commit_dirty(); 541 transaction_commit_dirty();
492} 542}
493 543
544static void handle_dissociate(struct wl_listener *listener, void *data);
545
494static void handle_override_redirect(struct wl_listener *listener, void *data) { 546static void handle_override_redirect(struct wl_listener *listener, void *data) {
495 struct sway_xwayland_view *xwayland_view = 547 struct sway_xwayland_view *xwayland_view =
496 wl_container_of(listener, xwayland_view, override_redirect); 548 wl_container_of(listener, xwayland_view, override_redirect);
497 struct wlr_xwayland_surface *xsurface = data;
498 struct sway_view *view = &xwayland_view->view; 549 struct sway_view *view = &xwayland_view->view;
550 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
499 551
500 bool mapped = xsurface->mapped; 552 bool associated = xsurface->surface != NULL;
553 bool mapped = associated && xsurface->surface->mapped;
501 if (mapped) { 554 if (mapped) {
502 handle_unmap(&xwayland_view->unmap, NULL); 555 handle_unmap(&xwayland_view->unmap, NULL);
503 } 556 }
557 if (associated) {
558 handle_dissociate(&xwayland_view->dissociate, NULL);
559 }
504 560
505 handle_destroy(&xwayland_view->destroy, view); 561 handle_destroy(&xwayland_view->destroy, view);
506 xsurface->data = NULL; 562 xsurface->data = NULL;
563
507 struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface); 564 struct sway_xwayland_unmanaged *unmanaged = create_unmanaged(xsurface);
565 if (associated) {
566 unmanaged_handle_associate(&unmanaged->associate, NULL);
567 }
508 if (mapped) { 568 if (mapped) {
509 unmanaged_handle_map(&unmanaged->map, xsurface); 569 unmanaged_handle_map(&unmanaged->map, xsurface);
510 } 570 }
@@ -516,7 +576,7 @@ static void handle_request_configure(struct wl_listener *listener, void *data) {
516 struct wlr_xwayland_surface_configure_event *ev = data; 576 struct wlr_xwayland_surface_configure_event *ev = data;
517 struct sway_view *view = &xwayland_view->view; 577 struct sway_view *view = &xwayland_view->view;
518 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 578 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
519 if (!xsurface->mapped) { 579 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
520 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y, 580 wlr_xwayland_surface_configure(xsurface, ev->x, ev->y,
521 ev->width, ev->height); 581 ev->width, ev->height);
522 return; 582 return;
@@ -527,10 +587,10 @@ static void handle_request_configure(struct wl_listener *listener, void *data) {
527 view->natural_height = ev->height; 587 view->natural_height = ev->height;
528 container_floating_resize_and_center(view->container); 588 container_floating_resize_and_center(view->container);
529 589
530 configure(view, view->container->content_x, 590 configure(view, view->container->pending.content_x,
531 view->container->content_y, 591 view->container->pending.content_y,
532 view->container->content_width, 592 view->container->pending.content_width,
533 view->container->content_height); 593 view->container->pending.content_height);
534 node_set_dirty(&view->container->node); 594 node_set_dirty(&view->container->node);
535 } else { 595 } else {
536 configure(view, view->container->current.content_x, 596 configure(view, view->container->current.content_x,
@@ -545,7 +605,7 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
545 wl_container_of(listener, xwayland_view, request_fullscreen); 605 wl_container_of(listener, xwayland_view, request_fullscreen);
546 struct sway_view *view = &xwayland_view->view; 606 struct sway_view *view = &xwayland_view->view;
547 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 607 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
548 if (!xsurface->mapped) { 608 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
549 return; 609 return;
550 } 610 }
551 container_set_fullscreen(view->container, xsurface->fullscreen); 611 container_set_fullscreen(view->container, xsurface->fullscreen);
@@ -559,7 +619,7 @@ static void handle_request_minimize(struct wl_listener *listener, void *data) {
559 wl_container_of(listener, xwayland_view, request_minimize); 619 wl_container_of(listener, xwayland_view, request_minimize);
560 struct sway_view *view = &xwayland_view->view; 620 struct sway_view *view = &xwayland_view->view;
561 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 621 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
562 if (!xsurface->mapped) { 622 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
563 return; 623 return;
564 } 624 }
565 625
@@ -574,10 +634,11 @@ static void handle_request_move(struct wl_listener *listener, void *data) {
574 wl_container_of(listener, xwayland_view, request_move); 634 wl_container_of(listener, xwayland_view, request_move);
575 struct sway_view *view = &xwayland_view->view; 635 struct sway_view *view = &xwayland_view->view;
576 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 636 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
577 if (!xsurface->mapped) { 637 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
578 return; 638 return;
579 } 639 }
580 if (!container_is_floating(view->container)) { 640 if (!container_is_floating(view->container) ||
641 view->container->pending.fullscreen_mode) {
581 return; 642 return;
582 } 643 }
583 struct sway_seat *seat = input_manager_current_seat(); 644 struct sway_seat *seat = input_manager_current_seat();
@@ -589,7 +650,7 @@ static void handle_request_resize(struct wl_listener *listener, void *data) {
589 wl_container_of(listener, xwayland_view, request_resize); 650 wl_container_of(listener, xwayland_view, request_resize);
590 struct sway_view *view = &xwayland_view->view; 651 struct sway_view *view = &xwayland_view->view;
591 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 652 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
592 if (!xsurface->mapped) { 653 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
593 return; 654 return;
594 } 655 }
595 if (!container_is_floating(view->container)) { 656 if (!container_is_floating(view->container)) {
@@ -605,10 +666,10 @@ static void handle_request_activate(struct wl_listener *listener, void *data) {
605 wl_container_of(listener, xwayland_view, request_activate); 666 wl_container_of(listener, xwayland_view, request_activate);
606 struct sway_view *view = &xwayland_view->view; 667 struct sway_view *view = &xwayland_view->view;
607 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 668 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
608 if (!xsurface->mapped) { 669 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
609 return; 670 return;
610 } 671 }
611 view_request_activate(view); 672 view_request_activate(view, NULL);
612 673
613 transaction_commit_dirty(); 674 transaction_commit_dirty();
614} 675}
@@ -618,7 +679,7 @@ static void handle_set_title(struct wl_listener *listener, void *data) {
618 wl_container_of(listener, xwayland_view, set_title); 679 wl_container_of(listener, xwayland_view, set_title);
619 struct sway_view *view = &xwayland_view->view; 680 struct sway_view *view = &xwayland_view->view;
620 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 681 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
621 if (!xsurface->mapped) { 682 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
622 return; 683 return;
623 } 684 }
624 view_update_title(view, false); 685 view_update_title(view, false);
@@ -630,7 +691,7 @@ static void handle_set_class(struct wl_listener *listener, void *data) {
630 wl_container_of(listener, xwayland_view, set_class); 691 wl_container_of(listener, xwayland_view, set_class);
631 struct sway_view *view = &xwayland_view->view; 692 struct sway_view *view = &xwayland_view->view;
632 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 693 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
633 if (!xsurface->mapped) { 694 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
634 return; 695 return;
635 } 696 }
636 view_execute_criteria(view); 697 view_execute_criteria(view);
@@ -641,18 +702,43 @@ static void handle_set_role(struct wl_listener *listener, void *data) {
641 wl_container_of(listener, xwayland_view, set_role); 702 wl_container_of(listener, xwayland_view, set_role);
642 struct sway_view *view = &xwayland_view->view; 703 struct sway_view *view = &xwayland_view->view;
643 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 704 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
644 if (!xsurface->mapped) { 705 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
645 return; 706 return;
646 } 707 }
647 view_execute_criteria(view); 708 view_execute_criteria(view);
648} 709}
649 710
711static void handle_set_startup_id(struct wl_listener *listener, void *data) {
712 struct sway_xwayland_view *xwayland_view =
713 wl_container_of(listener, xwayland_view, set_startup_id);
714 struct sway_view *view = &xwayland_view->view;
715 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
716 if (xsurface->startup_id == NULL) {
717 return;
718 }
719
720 struct wlr_xdg_activation_token_v1 *token =
721 wlr_xdg_activation_v1_find_token(
722 server.xdg_activation_v1, xsurface->startup_id);
723 if (token == NULL) {
724 // Tried to activate with an unknown or expired token
725 return;
726 }
727
728 struct launcher_ctx *ctx = token->data;
729 if (token->data == NULL) {
730 // TODO: support external launchers in X
731 return;
732 }
733 view_assign_ctx(view, ctx);
734}
735
650static void handle_set_window_type(struct wl_listener *listener, void *data) { 736static void handle_set_window_type(struct wl_listener *listener, void *data) {
651 struct sway_xwayland_view *xwayland_view = 737 struct sway_xwayland_view *xwayland_view =
652 wl_container_of(listener, xwayland_view, set_window_type); 738 wl_container_of(listener, xwayland_view, set_window_type);
653 struct sway_view *view = &xwayland_view->view; 739 struct sway_view *view = &xwayland_view->view;
654 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 740 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
655 if (!xsurface->mapped) { 741 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
656 return; 742 return;
657 } 743 }
658 view_execute_criteria(view); 744 view_execute_criteria(view);
@@ -663,20 +749,39 @@ static void handle_set_hints(struct wl_listener *listener, void *data) {
663 wl_container_of(listener, xwayland_view, set_hints); 749 wl_container_of(listener, xwayland_view, set_hints);
664 struct sway_view *view = &xwayland_view->view; 750 struct sway_view *view = &xwayland_view->view;
665 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; 751 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
666 if (!xsurface->mapped) { 752 if (xsurface->surface == NULL || !xsurface->surface->mapped) {
667 return; 753 return;
668 } 754 }
669 if (!xsurface->hints_urgency && view->urgent_timer) { 755 const bool hints_urgency = xcb_icccm_wm_hints_get_urgency(xsurface->hints);
756 if (!hints_urgency && view->urgent_timer) {
670 // The view is in the timeout period. We'll ignore the request to 757 // 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 758 // unset urgency so that the view remains urgent until the timer clears
672 // it. 759 // it.
673 return; 760 return;
674 } 761 }
675 if (view->allow_request_urgent) { 762 if (view->allow_request_urgent) {
676 view_set_urgent(view, (bool)xsurface->hints_urgency); 763 view_set_urgent(view, hints_urgency);
677 } 764 }
678} 765}
679 766
767static void handle_associate(struct wl_listener *listener, void *data) {
768 struct sway_xwayland_view *xwayland_view =
769 wl_container_of(listener, xwayland_view, associate);
770 struct wlr_xwayland_surface *xsurface =
771 xwayland_view->view.wlr_xwayland_surface;
772 wl_signal_add(&xsurface->surface->events.unmap, &xwayland_view->unmap);
773 xwayland_view->unmap.notify = handle_unmap;
774 wl_signal_add(&xsurface->surface->events.map, &xwayland_view->map);
775 xwayland_view->map.notify = handle_map;
776}
777
778static void handle_dissociate(struct wl_listener *listener, void *data) {
779 struct sway_xwayland_view *xwayland_view =
780 wl_container_of(listener, xwayland_view, dissociate);
781 wl_list_remove(&xwayland_view->map.link);
782 wl_list_remove(&xwayland_view->unmap.link);
783}
784
680struct sway_view *view_from_wlr_xwayland_surface( 785struct sway_view *view_from_wlr_xwayland_surface(
681 struct wlr_xwayland_surface *xsurface) { 786 struct wlr_xwayland_surface *xsurface) {
682 return xsurface->data; 787 return xsurface->data;
@@ -692,7 +797,10 @@ struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsu
692 return NULL; 797 return NULL;
693 } 798 }
694 799
695 view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl); 800 if (!view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl)) {
801 free(xwayland_view);
802 return NULL;
803 }
696 xwayland_view->view.wlr_xwayland_surface = xsurface; 804 xwayland_view->view.wlr_xwayland_surface = xsurface;
697 805
698 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy); 806 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy);
@@ -731,6 +839,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); 839 wl_signal_add(&xsurface->events.set_role, &xwayland_view->set_role);
732 xwayland_view->set_role.notify = handle_set_role; 840 xwayland_view->set_role.notify = handle_set_role;
733 841
842 wl_signal_add(&xsurface->events.set_startup_id,
843 &xwayland_view->set_startup_id);
844 xwayland_view->set_startup_id.notify = handle_set_startup_id;
845
734 wl_signal_add(&xsurface->events.set_window_type, 846 wl_signal_add(&xsurface->events.set_window_type,
735 &xwayland_view->set_window_type); 847 &xwayland_view->set_window_type);
736 xwayland_view->set_window_type.notify = handle_set_window_type; 848 xwayland_view->set_window_type.notify = handle_set_window_type;
@@ -742,11 +854,11 @@ struct sway_xwayland_view *create_xwayland_view(struct wlr_xwayland_surface *xsu
742 &xwayland_view->set_decorations); 854 &xwayland_view->set_decorations);
743 xwayland_view->set_decorations.notify = handle_set_decorations; 855 xwayland_view->set_decorations.notify = handle_set_decorations;
744 856
745 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); 857 wl_signal_add(&xsurface->events.associate, &xwayland_view->associate);
746 xwayland_view->unmap.notify = handle_unmap; 858 xwayland_view->associate.notify = handle_associate;
747 859
748 wl_signal_add(&xsurface->events.map, &xwayland_view->map); 860 wl_signal_add(&xsurface->events.dissociate, &xwayland_view->dissociate);
749 xwayland_view->map.notify = handle_map; 861 xwayland_view->dissociate.notify = handle_dissociate;
750 862
751 wl_signal_add(&xsurface->events.set_override_redirect, 863 wl_signal_add(&xsurface->events.set_override_redirect,
752 &xwayland_view->override_redirect); 864 &xwayland_view->override_redirect);
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index fa604426..3d04826c 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -1,14 +1,14 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <math.h> 2#include <math.h>
4#include <libevdev/libevdev.h> 3#include <libevdev/libevdev.h>
5#include <linux/input-event-codes.h> 4#include <linux/input-event-codes.h>
6#include <errno.h> 5#include <errno.h>
6#include <time.h>
7#include <strings.h> 7#include <strings.h>
8#include <wlr/types/wlr_box.h>
9#include <wlr/types/wlr_cursor.h> 8#include <wlr/types/wlr_cursor.h>
10#include <wlr/types/wlr_idle.h> 9#include <wlr/types/wlr_cursor_shape_v1.h>
11#include <wlr/types/wlr_pointer.h> 10#include <wlr/types/wlr_pointer.h>
11#include <wlr/types/wlr_relative_pointer_v1.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>
14#include <wlr/types/wlr_tablet_pad.h> 14#include <wlr/types/wlr_tablet_pad.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_popup_desc *popup =
94 scene_descriptor_try_get(current, SWAY_SCENE_DESC_POPUP);
95 if (popup && popup->view) {
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}
@@ -292,7 +243,7 @@ static enum sway_input_idle_source idle_source_from_device(
292 return IDLE_SOURCE_POINTER; 243 return IDLE_SOURCE_POINTER;
293 case WLR_INPUT_DEVICE_TOUCH: 244 case WLR_INPUT_DEVICE_TOUCH:
294 return IDLE_SOURCE_TOUCH; 245 return IDLE_SOURCE_TOUCH;
295 case WLR_INPUT_DEVICE_TABLET_TOOL: 246 case WLR_INPUT_DEVICE_TABLET:
296 return IDLE_SOURCE_TABLET_TOOL; 247 return IDLE_SOURCE_TABLET_TOOL;
297 case WLR_INPUT_DEVICE_TABLET_PAD: 248 case WLR_INPUT_DEVICE_TABLET_PAD:
298 return IDLE_SOURCE_TABLET_PAD; 249 return IDLE_SOURCE_TABLET_PAD;
@@ -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,35 +329,34 @@ 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,
408 struct wlr_input_device *device, uint32_t time_msec, uint32_t button, 358 struct wlr_input_device *device, uint32_t time_msec, uint32_t button,
409 enum wlr_button_state state) { 359 enum wl_pointer_button_state state) {
410 if (time_msec == 0) { 360 if (time_msec == 0) {
411 time_msec = get_current_time_msec(); 361 time_msec = get_current_time_msec();
412 } 362 }
@@ -416,9 +366,9 @@ 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 == WL_POINTER_BUTTON_STATE_PRESSED) {
422 cursor->pressed_button_count++; 372 cursor->pressed_button_count++;
423 } else { 373 } else {
424 if (cursor->pressed_button_count > 0) { 374 if (cursor->pressed_button_count > 0) {
@@ -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, WL_POINTER_BUTTON_STATE_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, WL_POINTER_BUTTON_STATE_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) {
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, WL_POINTER_BUTTON_STATE_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, WL_POINTER_BUTTON_STATE_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, WL_POINTER_BUTTON_STATE_PRESSED);
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, WL_POINTER_BUTTON_STATE_RELEASED);
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..248ca34e 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -1,12 +1,12 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <stdio.h> 2#include <stdio.h>
4#include <string.h> 3#include <string.h>
5#include <math.h> 4#include <math.h>
5#include <assert.h>
6#include <wlr/config.h>
6#include <wlr/backend/libinput.h> 7#include <wlr/backend/libinput.h>
7#include <wlr/types/wlr_cursor.h> 8#include <wlr/types/wlr_cursor.h>
8#include <wlr/types/wlr_keyboard_group.h> 9#include <wlr/types/wlr_keyboard_group.h>
9#include <wlr/types/wlr_input_inhibitor.h>
10#include <wlr/types/wlr_virtual_keyboard_v1.h> 10#include <wlr/types/wlr_virtual_keyboard_v1.h>
11#include <wlr/types/wlr_virtual_pointer_v1.h> 11#include <wlr/types/wlr_virtual_pointer_v1.h>
12#include "sway/config.h" 12#include "sway/config.h"
@@ -22,6 +22,10 @@
22#include "list.h" 22#include "list.h"
23#include "log.h" 23#include "log.h"
24 24
25#if WLR_HAS_LIBINPUT_BACKEND
26#include <wlr/backend/libinput.h>
27#endif
28
25#define DEFAULT_SEAT "seat0" 29#define DEFAULT_SEAT "seat0"
26 30
27struct input_config *current_input_config = NULL; 31struct input_config *current_input_config = NULL;
@@ -63,8 +67,15 @@ struct sway_seat *input_manager_sway_seat_from_wlr_seat(struct wlr_seat *wlr_sea
63} 67}
64 68
65char *input_device_get_identifier(struct wlr_input_device *device) { 69char *input_device_get_identifier(struct wlr_input_device *device) {
66 int vendor = device->vendor; 70 int vendor = 0, product = 0;
67 int product = device->product; 71#if WLR_HAS_LIBINPUT_BACKEND
72 if (wlr_input_device_is_libinput(device)) {
73 struct libinput_device *libinput_dev = wlr_libinput_get_device_handle(device);
74 vendor = libinput_device_get_id_vendor(libinput_dev);
75 product = libinput_device_get_id_product(libinput_dev);
76 }
77#endif
78
68 char *name = strdup(device->name ? device->name : ""); 79 char *name = strdup(device->name ? device->name : "");
69 strip_whitespace(name); 80 strip_whitespace(name);
70 81
@@ -76,20 +87,13 @@ char *input_device_get_identifier(struct wlr_input_device *device) {
76 } 87 }
77 } 88 }
78 89
79 const char *fmt = "%d:%d:%s"; 90 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); 91 free(name);
89 return identifier; 92 return identifier;
90} 93}
91 94
92static bool device_is_touchpad(struct sway_input_device *device) { 95static bool device_is_touchpad(struct sway_input_device *device) {
96#if WLR_HAS_LIBINPUT_BACKEND
93 if (device->wlr_device->type != WLR_INPUT_DEVICE_POINTER || 97 if (device->wlr_device->type != WLR_INPUT_DEVICE_POINTER ||
94 !wlr_input_device_is_libinput(device->wlr_device)) { 98 !wlr_input_device_is_libinput(device->wlr_device)) {
95 return false; 99 return false;
@@ -99,6 +103,9 @@ static bool device_is_touchpad(struct sway_input_device *device) {
99 wlr_libinput_get_device_handle(device->wlr_device); 103 wlr_libinput_get_device_handle(device->wlr_device);
100 104
101 return libinput_device_config_tap_get_finger_count(libinput_device) > 0; 105 return libinput_device_config_tap_get_finger_count(libinput_device) > 0;
106#else
107 return false;
108#endif
102} 109}
103 110
104const char *input_device_get_type(struct sway_input_device *device) { 111const char *input_device_get_type(struct sway_input_device *device) {
@@ -113,7 +120,7 @@ const char *input_device_get_type(struct sway_input_device *device) {
113 return "keyboard"; 120 return "keyboard";
114 case WLR_INPUT_DEVICE_TOUCH: 121 case WLR_INPUT_DEVICE_TOUCH:
115 return "touch"; 122 return "touch";
116 case WLR_INPUT_DEVICE_TABLET_TOOL: 123 case WLR_INPUT_DEVICE_TABLET:
117 return "tablet_tool"; 124 return "tablet_tool";
118 case WLR_INPUT_DEVICE_TABLET_PAD: 125 case WLR_INPUT_DEVICE_TABLET_PAD:
119 return "tablet_pad"; 126 return "tablet_pad";
@@ -236,7 +243,11 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
236 243
237 apply_input_type_config(input_device); 244 apply_input_type_config(input_device);
238 245
239 sway_input_configure_libinput_device(input_device); 246#if WLR_HAS_LIBINPUT_BACKEND
247 bool config_changed = sway_input_configure_libinput_device(input_device);
248#else
249 bool config_changed = false;
250#endif
240 251
241 wl_signal_add(&device->events.destroy, &input_device->device_destroy); 252 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
242 input_device->device_destroy.notify = handle_device_destroy; 253 input_device->device_destroy.notify = handle_device_destroy;
@@ -274,29 +285,9 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
274 } 285 }
275 286
276 ipc_event_input("added", input_device); 287 ipc_event_input("added", input_device);
277}
278 288
279static void handle_inhibit_activate(struct wl_listener *listener, void *data) { 289 if (config_changed) {
280 struct sway_input_manager *input_manager = wl_container_of( 290 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 } 291 }
301} 292}
302 293
@@ -377,7 +368,7 @@ void handle_virtual_keyboard(struct wl_listener *listener, void *data) {
377 struct sway_input_manager *input_manager = 368 struct sway_input_manager *input_manager =
378 wl_container_of(listener, input_manager, virtual_keyboard_new); 369 wl_container_of(listener, input_manager, virtual_keyboard_new);
379 struct wlr_virtual_keyboard_v1 *keyboard = data; 370 struct wlr_virtual_keyboard_v1 *keyboard = data;
380 struct wlr_input_device *device = &keyboard->input_device; 371 struct wlr_input_device *device = &keyboard->keyboard.base;
381 372
382 // TODO: Amend protocol to allow NULL seat 373 // TODO: Amend protocol to allow NULL seat
383 struct sway_seat *seat = keyboard->seat ? 374 struct sway_seat *seat = keyboard->seat ?
@@ -410,7 +401,7 @@ void handle_virtual_pointer(struct wl_listener *listener, void *data) {
410 wl_container_of(listener, input_manager, virtual_pointer_new); 401 wl_container_of(listener, input_manager, virtual_pointer_new);
411 struct wlr_virtual_pointer_v1_new_pointer_event *event = data; 402 struct wlr_virtual_pointer_v1_new_pointer_event *event = data;
412 struct wlr_virtual_pointer_v1 *pointer = event->new_pointer; 403 struct wlr_virtual_pointer_v1 *pointer = event->new_pointer;
413 struct wlr_input_device *device = &pointer->input_device; 404 struct wlr_input_device *device = &pointer->pointer.base;
414 405
415 struct sway_seat *seat = event->suggested_seat ? 406 struct sway_seat *seat = event->suggested_seat ?
416 input_manager_sway_seat_from_wlr_seat(event->suggested_seat) : 407 input_manager_sway_seat_from_wlr_seat(event->suggested_seat) :
@@ -442,6 +433,20 @@ void handle_virtual_pointer(struct wl_listener *listener, void *data) {
442 } 433 }
443} 434}
444 435
436static void handle_transient_seat_manager_create_seat(
437 struct wl_listener *listener, void *data) {
438 struct wlr_transient_seat_v1 *transient_seat = data;
439 static uint64_t i;
440 char name[256];
441 snprintf(name, sizeof(name), "transient-%"PRIx64, i++);
442 struct sway_seat *seat = seat_create(name);
443 if (seat && seat->wlr_seat) {
444 wlr_transient_seat_v1_ready(transient_seat, seat->wlr_seat);
445 } else {
446 wlr_transient_seat_v1_deny(transient_seat);
447 }
448}
449
445struct sway_input_manager *input_manager_create(struct sway_server *server) { 450struct sway_input_manager *input_manager_create(struct sway_server *server) {
446 struct sway_input_manager *input = 451 struct sway_input_manager *input =
447 calloc(1, sizeof(struct sway_input_manager)); 452 calloc(1, sizeof(struct sway_input_manager));
@@ -468,14 +473,6 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) {
468 &input->virtual_pointer_new); 473 &input->virtual_pointer_new);
469 input->virtual_pointer_new.notify = handle_virtual_pointer; 474 input->virtual_pointer_new.notify = handle_virtual_pointer;
470 475
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 = 476 input->keyboard_shortcuts_inhibit =
480 wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display); 477 wlr_keyboard_shortcuts_inhibit_v1_create(server->wl_display);
481 input->keyboard_shortcuts_inhibit_new_inhibitor.notify = 478 input->keyboard_shortcuts_inhibit_new_inhibitor.notify =
@@ -483,6 +480,17 @@ struct sway_input_manager *input_manager_create(struct sway_server *server) {
483 wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor, 480 wl_signal_add(&input->keyboard_shortcuts_inhibit->events.new_inhibitor,
484 &input->keyboard_shortcuts_inhibit_new_inhibitor); 481 &input->keyboard_shortcuts_inhibit_new_inhibitor);
485 482
483 input->pointer_gestures = wlr_pointer_gestures_v1_create(server->wl_display);
484
485 input->transient_seat_manager =
486 wlr_transient_seat_manager_v1_create(server->wl_display);
487 assert(input->transient_seat_manager);
488
489 input->transient_seat_create.notify =
490 handle_transient_seat_manager_create_seat;
491 wl_signal_add(&input->transient_seat_manager->events.create_seat,
492 &input->transient_seat_create);
493
486 return input; 494 return input;
487} 495}
488 496
@@ -520,21 +528,50 @@ static void retranslate_keysyms(struct input_config *input_config) {
520 return; 528 return;
521 } 529 }
522 } 530 }
531
532 for (int i = 0; i < config->input_type_configs->length; ++i) {
533 struct input_config *ic = config->input_type_configs->items[i];
534 if (ic->xkb_layout || ic->xkb_file) {
535 // this is the first config with xkb_layout or xkb_file
536 if (ic->identifier == input_config->identifier) {
537 translate_keysyms(ic);
538 }
539
540 return;
541 }
542 }
523} 543}
524 544
525static void input_manager_configure_input( 545static void input_manager_configure_input(
526 struct sway_input_device *input_device) { 546 struct sway_input_device *input_device) {
527 sway_input_configure_libinput_device(input_device); 547#if WLR_HAS_LIBINPUT_BACKEND
548 bool config_changed = sway_input_configure_libinput_device(input_device);
549#else
550 bool config_changed = false;
551#endif
528 struct sway_seat *seat = NULL; 552 struct sway_seat *seat = NULL;
529 wl_list_for_each(seat, &server.input->seats, link) { 553 wl_list_for_each(seat, &server.input->seats, link) {
530 seat_configure_device(seat, input_device); 554 seat_configure_device(seat, input_device);
531 } 555 }
556 if (config_changed) {
557 ipc_event_input("libinput_config", input_device);
558 }
532} 559}
533 560
534void input_manager_configure_all_inputs(void) { 561void input_manager_configure_all_input_mappings(void) {
535 struct sway_input_device *input_device = NULL; 562 struct sway_input_device *input_device;
536 wl_list_for_each(input_device, &server.input->devices, link) { 563 wl_list_for_each(input_device, &server.input->devices, link) {
537 input_manager_configure_input(input_device); 564 struct sway_seat *seat;
565 wl_list_for_each(seat, &server.input->seats, link) {
566 seat_configure_device_mapping(seat, input_device);
567 }
568
569#if WLR_HAS_LIBINPUT_BACKEND
570 // Input devices mapped to unavailable outputs get their libinput
571 // send_events setting switched to false. We need to re-enable this
572 // when the output appears.
573 sway_input_configure_libinput_device_send_events(input_device);
574#endif
538 } 575 }
539} 576}
540 577
@@ -556,7 +593,9 @@ void input_manager_apply_input_config(struct input_config *input_config) {
556} 593}
557 594
558void input_manager_reset_input(struct sway_input_device *input_device) { 595void input_manager_reset_input(struct sway_input_device *input_device) {
596#if WLR_HAS_LIBINPUT_BACKEND
559 sway_input_reset_libinput_device(input_device); 597 sway_input_reset_libinput_device(input_device);
598#endif
560 struct sway_seat *seat = NULL; 599 struct sway_seat *seat = NULL;
561 wl_list_for_each(seat, &server.input->seats, link) { 600 wl_list_for_each(seat, &server.input->seats, link) {
562 seat_reset_device(seat, input_device); 601 seat_reset_device(seat, input_device);
@@ -564,6 +603,13 @@ void input_manager_reset_input(struct sway_input_device *input_device) {
564} 603}
565 604
566void input_manager_reset_all_inputs(void) { 605void input_manager_reset_all_inputs(void) {
606 // Set the active keyboard to NULL to avoid spamming configuration updates
607 // for all keyboard devices.
608 struct sway_seat *seat;
609 wl_list_for_each(seat, &server.input->seats, link) {
610 wlr_seat_set_keyboard(seat->wlr_seat, NULL);
611 }
612
567 struct sway_input_device *input_device = NULL; 613 struct sway_input_device *input_device = NULL;
568 wl_list_for_each(input_device, &server.input->devices, link) { 614 wl_list_for_each(input_device, &server.input->devices, link) {
569 input_manager_reset_input(input_device); 615 input_manager_reset_input(input_device);
@@ -572,7 +618,6 @@ void input_manager_reset_all_inputs(void) {
572 // If there is at least one keyboard using the default keymap, repeat delay, 618 // 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 619 // and repeat rate, then it is possible that there is a keyboard group that
574 // need their keyboard disarmed. 620 // need their keyboard disarmed.
575 struct sway_seat *seat;
576 wl_list_for_each(seat, &server.input->seats, link) { 621 wl_list_for_each(seat, &server.input->seats, link) {
577 struct sway_keyboard_group *group; 622 struct sway_keyboard_group *group;
578 wl_list_for_each(group, &seat->keyboard_groups, link) { 623 wl_list_for_each(group, &seat->keyboard_groups, link) {
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index ce259eb2..f74d0658 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;
@@ -30,6 +32,7 @@ static struct modifier_key {
30 { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 }, 32 { XKB_MOD_NAME_NUM, WLR_MODIFIER_MOD2 },
31 { "Mod3", WLR_MODIFIER_MOD3 }, 33 { "Mod3", WLR_MODIFIER_MOD3 },
32 { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO }, 34 { XKB_MOD_NAME_LOGO, WLR_MODIFIER_LOGO },
35 { "Super", WLR_MODIFIER_LOGO },
33 { "Mod5", WLR_MODIFIER_MOD5 }, 36 { "Mod5", WLR_MODIFIER_MOD5 },
34}; 37};
35 38
@@ -265,14 +268,12 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
265 xkb_keysym_t keysym = pressed_keysyms[i]; 268 xkb_keysym_t keysym = pressed_keysyms[i];
266 if (keysym >= XKB_KEY_XF86Switch_VT_1 && 269 if (keysym >= XKB_KEY_XF86Switch_VT_1 &&
267 keysym <= XKB_KEY_XF86Switch_VT_12) { 270 keysym <= XKB_KEY_XF86Switch_VT_12) {
268 if (wlr_backend_is_multi(server.backend)) { 271#if WLR_HAS_SESSION
269 struct wlr_session *session = 272 if (server.session) {
270 wlr_backend_get_session(server.backend); 273 unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1;
271 if (session) { 274 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 } 275 }
276#endif
276 return true; 277 return true;
277 } 278 }
278 } 279 }
@@ -292,14 +293,12 @@ static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard,
292static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard, 293static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard,
293 xkb_keycode_t keycode, const xkb_keysym_t **keysyms, 294 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
294 uint32_t *modifiers) { 295 uint32_t *modifiers) {
295 struct wlr_input_device *device = 296 *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( 297 xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
299 device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); 298 keyboard->wlr->xkb_state, keycode, XKB_CONSUMED_MODE_XKB);
300 *modifiers = *modifiers & ~consumed; 299 *modifiers = *modifiers & ~consumed;
301 300
302 return xkb_state_key_get_syms(device->keyboard->xkb_state, 301 return xkb_state_key_get_syms(keyboard->wlr->xkb_state,
303 keycode, keysyms); 302 keycode, keysyms);
304} 303}
305 304
@@ -315,13 +314,11 @@ static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard,
315static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard, 314static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard,
316 xkb_keycode_t keycode, const xkb_keysym_t **keysyms, 315 xkb_keycode_t keycode, const xkb_keysym_t **keysyms,
317 uint32_t *modifiers) { 316 uint32_t *modifiers) {
318 struct wlr_input_device *device = 317 *modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
319 keyboard->seat_device->input_device->wlr_device;
320 *modifiers = wlr_keyboard_get_modifiers(device->keyboard);
321 318
322 xkb_layout_index_t layout_index = xkb_state_key_get_layout( 319 xkb_layout_index_t layout_index = xkb_state_key_get_layout(
323 device->keyboard->xkb_state, keycode); 320 keyboard->wlr->xkb_state, keycode);
324 return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap, 321 return xkb_keymap_key_get_syms_by_level(keyboard->wlr->keymap,
325 keycode, layout_index, 0, keysyms); 322 keycode, layout_index, 0, keysyms);
326} 323}
327 324
@@ -361,8 +358,7 @@ static void update_keyboard_state(struct sway_keyboard *keyboard,
361 keyinfo->keycode, &keyinfo->translated_keysyms, 358 keyinfo->keycode, &keyinfo->translated_keysyms,
362 &keyinfo->translated_modifiers); 359 &keyinfo->translated_modifiers);
363 360
364 keyinfo->code_modifiers = wlr_keyboard_get_modifiers( 361 keyinfo->code_modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
365 keyboard->seat_device->input_device->wlr_device->keyboard);
366 362
367 // Update shortcut model keyinfo 363 // Update shortcut model keyinfo
368 update_shortcut_state(&keyboard->state_keycodes, raw_keycode, keystate, 364 update_shortcut_state(&keyboard->state_keycodes, raw_keycode, keystate,
@@ -379,16 +375,38 @@ static void update_keyboard_state(struct sway_keyboard *keyboard,
379 } 375 }
380} 376}
381 377
378/**
379 * Get keyboard grab of the seat from sway_keyboard if we should forward events
380 * to it.
381 *
382 * Returns NULL if the keyboard is not grabbed by an input method,
383 * or if event is from virtual keyboard of the same client as grab.
384 * TODO: see swaywm/wlroots#2322
385 */
386static struct wlr_input_method_keyboard_grab_v2 *keyboard_get_im_grab(
387 struct sway_keyboard *keyboard) {
388 struct wlr_input_method_v2 *input_method = keyboard->seat_device->
389 sway_seat->im_relay.input_method;
390 struct wlr_virtual_keyboard_v1 *virtual_keyboard =
391 wlr_input_device_get_virtual_keyboard(keyboard->seat_device->input_device->wlr_device);
392 if (!input_method || !input_method->keyboard_grab || (virtual_keyboard &&
393 wl_resource_get_client(virtual_keyboard->resource) ==
394 wl_resource_get_client(input_method->keyboard_grab->resource))) {
395 return NULL;
396 }
397 return input_method->keyboard_grab;
398}
399
382static void handle_key_event(struct sway_keyboard *keyboard, 400static void handle_key_event(struct sway_keyboard *keyboard,
383 struct wlr_event_keyboard_key *event) { 401 struct wlr_keyboard_key_event *event) {
384 struct sway_seat *seat = keyboard->seat_device->sway_seat; 402 struct sway_seat *seat = keyboard->seat_device->sway_seat;
385 struct wlr_seat *wlr_seat = seat->wlr_seat; 403 struct wlr_seat *wlr_seat = seat->wlr_seat;
386 struct wlr_input_device *wlr_device = 404 struct wlr_input_device *wlr_device =
387 keyboard->seat_device->input_device->wlr_device; 405 keyboard->seat_device->input_device->wlr_device;
388 char *device_identifier = input_device_get_identifier(wlr_device); 406 char *device_identifier = input_device_get_identifier(wlr_device);
389 bool exact_identifier = wlr_device->keyboard->group != NULL; 407 bool exact_identifier = keyboard->wlr->group != NULL;
390 seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD); 408 seat_idle_notify_activity(seat, IDLE_SOURCE_KEYBOARD);
391 bool input_inhibited = seat->exclusive_client != NULL; 409 bool locked = server.session_lock.lock;
392 struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor = 410 struct sway_keyboard_shortcuts_inhibitor *sway_inhibitor =
393 keyboard_shortcuts_inhibitor_get_for_focused_surface(seat); 411 keyboard_shortcuts_inhibitor_get_for_focused_surface(seat);
394 bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active; 412 bool shortcuts_inhibited = sway_inhibitor && sway_inhibitor->inhibitor->active;
@@ -406,17 +424,17 @@ static void handle_key_event(struct sway_keyboard *keyboard,
406 struct sway_binding *binding_released = NULL; 424 struct sway_binding *binding_released = NULL;
407 get_active_binding(&keyboard->state_keycodes, 425 get_active_binding(&keyboard->state_keycodes,
408 config->current_mode->keycode_bindings, &binding_released, 426 config->current_mode->keycode_bindings, &binding_released,
409 keyinfo.code_modifiers, true, input_inhibited, 427 keyinfo.code_modifiers, true, locked,
410 shortcuts_inhibited, device_identifier, 428 shortcuts_inhibited, device_identifier,
411 exact_identifier, keyboard->effective_layout); 429 exact_identifier, keyboard->effective_layout);
412 get_active_binding(&keyboard->state_keysyms_raw, 430 get_active_binding(&keyboard->state_keysyms_raw,
413 config->current_mode->keysym_bindings, &binding_released, 431 config->current_mode->keysym_bindings, &binding_released,
414 keyinfo.raw_modifiers, true, input_inhibited, 432 keyinfo.raw_modifiers, true, locked,
415 shortcuts_inhibited, device_identifier, 433 shortcuts_inhibited, device_identifier,
416 exact_identifier, keyboard->effective_layout); 434 exact_identifier, keyboard->effective_layout);
417 get_active_binding(&keyboard->state_keysyms_translated, 435 get_active_binding(&keyboard->state_keysyms_translated,
418 config->current_mode->keysym_bindings, &binding_released, 436 config->current_mode->keysym_bindings, &binding_released,
419 keyinfo.translated_modifiers, true, input_inhibited, 437 keyinfo.translated_modifiers, true, locked,
420 shortcuts_inhibited, device_identifier, 438 shortcuts_inhibited, device_identifier,
421 exact_identifier, keyboard->effective_layout); 439 exact_identifier, keyboard->effective_layout);
422 440
@@ -438,17 +456,17 @@ static void handle_key_event(struct sway_keyboard *keyboard,
438 if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { 456 if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
439 get_active_binding(&keyboard->state_keycodes, 457 get_active_binding(&keyboard->state_keycodes,
440 config->current_mode->keycode_bindings, &binding, 458 config->current_mode->keycode_bindings, &binding,
441 keyinfo.code_modifiers, false, input_inhibited, 459 keyinfo.code_modifiers, false, locked,
442 shortcuts_inhibited, device_identifier, 460 shortcuts_inhibited, device_identifier,
443 exact_identifier, keyboard->effective_layout); 461 exact_identifier, keyboard->effective_layout);
444 get_active_binding(&keyboard->state_keysyms_raw, 462 get_active_binding(&keyboard->state_keysyms_raw,
445 config->current_mode->keysym_bindings, &binding, 463 config->current_mode->keysym_bindings, &binding,
446 keyinfo.raw_modifiers, false, input_inhibited, 464 keyinfo.raw_modifiers, false, locked,
447 shortcuts_inhibited, device_identifier, 465 shortcuts_inhibited, device_identifier,
448 exact_identifier, keyboard->effective_layout); 466 exact_identifier, keyboard->effective_layout);
449 get_active_binding(&keyboard->state_keysyms_translated, 467 get_active_binding(&keyboard->state_keysyms_translated,
450 config->current_mode->keysym_bindings, &binding, 468 config->current_mode->keysym_bindings, &binding,
451 keyinfo.translated_modifiers, false, input_inhibited, 469 keyinfo.translated_modifiers, false, locked,
452 shortcuts_inhibited, device_identifier, 470 shortcuts_inhibited, device_identifier,
453 exact_identifier, keyboard->effective_layout); 471 exact_identifier, keyboard->effective_layout);
454 } 472 }
@@ -456,10 +474,10 @@ static void handle_key_event(struct sway_keyboard *keyboard,
456 // Set up (or clear) keyboard repeat for a pressed binding. Since the 474 // 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 475 // binding may remove the keyboard, the timer needs to be updated first
458 if (binding && !(binding->flags & BINDING_NOREPEAT) && 476 if (binding && !(binding->flags & BINDING_NOREPEAT) &&
459 wlr_device->keyboard->repeat_info.delay > 0) { 477 keyboard->wlr->repeat_info.delay > 0) {
460 keyboard->repeat_binding = binding; 478 keyboard->repeat_binding = binding;
461 if (wl_event_source_timer_update(keyboard->key_repeat_source, 479 if (wl_event_source_timer_update(keyboard->key_repeat_source,
462 wlr_device->keyboard->repeat_info.delay) < 0) { 480 keyboard->wlr->repeat_info.delay) < 0) {
463 sway_log(SWAY_DEBUG, "failed to set key repeat timer"); 481 sway_log(SWAY_DEBUG, "failed to set key repeat timer");
464 } 482 }
465 } else if (keyboard->repeat_binding) { 483 } else if (keyboard->repeat_binding) {
@@ -471,7 +489,7 @@ static void handle_key_event(struct sway_keyboard *keyboard,
471 handled = true; 489 handled = true;
472 } 490 }
473 491
474 if (!handled && wlr_device->keyboard->group) { 492 if (!handled && keyboard->wlr->group) {
475 // Only handle device specific bindings for keyboards in a group 493 // Only handle device specific bindings for keyboards in a group
476 free(device_identifier); 494 free(device_identifier);
477 return; 495 return;
@@ -489,18 +507,41 @@ static void handle_key_event(struct sway_keyboard *keyboard,
489 keyinfo.raw_keysyms_len); 507 keyinfo.raw_keysyms_len);
490 } 508 }
491 509
492 if (!handled || event->state == WL_KEYBOARD_KEY_STATE_RELEASED) { 510 if (event->state == WL_KEYBOARD_KEY_STATE_RELEASED) {
511 // If the pressed event was sent to a client, also send the released
512 // event. In particular, don't send the released event to the IM grab.
493 bool pressed_sent = update_shortcut_state( 513 bool pressed_sent = update_shortcut_state(
494 &keyboard->state_pressed_sent, event->keycode, event->state, 514 &keyboard->state_pressed_sent, event->keycode,
495 keyinfo.keycode, 0); 515 event->state, keyinfo.keycode, 0);
496 if (pressed_sent || event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { 516 if (pressed_sent) {
497 wlr_seat_set_keyboard(wlr_seat, wlr_device); 517 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
498 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, 518 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
499 event->keycode, event->state); 519 event->keycode, event->state);
520 handled = true;
521 }
522 }
523
524 if (!handled) {
525 struct wlr_input_method_keyboard_grab_v2 *kb_grab = keyboard_get_im_grab(keyboard);
526
527 if (kb_grab) {
528 wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr);
529 wlr_input_method_keyboard_grab_v2_send_key(kb_grab,
530 event->time_msec, event->keycode, event->state);
531 handled = true;
500 } 532 }
501 } 533 }
502 534
503 transaction_commit_dirty(); 535 if (!handled && event->state != WL_KEYBOARD_KEY_STATE_RELEASED) {
536 // If a released event failed pressed sent test, and not in sent to
537 // keyboard grab, it is still not handled. Don't handle released here.
538 update_shortcut_state(
539 &keyboard->state_pressed_sent, event->keycode, event->state,
540 keyinfo.keycode, 0);
541 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
542 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
543 event->keycode, event->state);
544 }
504 545
505 free(device_identifier); 546 free(device_identifier);
506} 547}
@@ -573,21 +614,18 @@ static void handle_keyboard_group_leave(struct wl_listener *listener,
573} 614}
574 615
575static int handle_keyboard_repeat(void *data) { 616static int handle_keyboard_repeat(void *data) {
576 struct sway_keyboard *keyboard = (struct sway_keyboard *)data; 617 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) { 618 if (keyboard->repeat_binding) {
580 if (wlr_device->repeat_info.rate > 0) { 619 if (keyboard->wlr->repeat_info.rate > 0) {
581 // We queue the next event first, as the command might cancel it 620 // We queue the next event first, as the command might cancel it
582 if (wl_event_source_timer_update(keyboard->key_repeat_source, 621 if (wl_event_source_timer_update(keyboard->key_repeat_source,
583 1000 / wlr_device->repeat_info.rate) < 0) { 622 1000 / keyboard->wlr->repeat_info.rate) < 0) {
584 sway_log(SWAY_DEBUG, "failed to update key repeat timer"); 623 sway_log(SWAY_DEBUG, "failed to update key repeat timer");
585 } 624 }
586 } 625 }
587 626
588 seat_execute_command(keyboard->seat_device->sway_seat, 627 seat_execute_command(keyboard->seat_device->sway_seat,
589 keyboard->repeat_binding); 628 keyboard->repeat_binding);
590 transaction_commit_dirty();
591 } 629 }
592 return 0; 630 return 0;
593} 631}
@@ -614,22 +652,28 @@ static void determine_bar_visibility(uint32_t modifiers) {
614} 652}
615 653
616static void handle_modifier_event(struct sway_keyboard *keyboard) { 654static void handle_modifier_event(struct sway_keyboard *keyboard) {
617 struct wlr_input_device *wlr_device = 655 if (!keyboard->wlr->group) {
618 keyboard->seat_device->input_device->wlr_device; 656 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 657
625 uint32_t modifiers = wlr_keyboard_get_modifiers(wlr_device->keyboard); 658 if (kb_grab) {
659 wlr_input_method_keyboard_grab_v2_set_keyboard(kb_grab, keyboard->wlr);
660 wlr_input_method_keyboard_grab_v2_send_modifiers(kb_grab,
661 &keyboard->wlr->modifiers);
662 } else {
663 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
664 wlr_seat_set_keyboard(wlr_seat, keyboard->wlr);
665 wlr_seat_keyboard_notify_modifiers(wlr_seat,
666 &keyboard->wlr->modifiers);
667 }
668
669 uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard->wlr);
626 determine_bar_visibility(modifiers); 670 determine_bar_visibility(modifiers);
627 } 671 }
628 672
629 if (wlr_device->keyboard->modifiers.group != keyboard->effective_layout) { 673 if (keyboard->wlr->modifiers.group != keyboard->effective_layout) {
630 keyboard->effective_layout = wlr_device->keyboard->modifiers.group; 674 keyboard->effective_layout = keyboard->wlr->modifiers.group;
631 675
632 if (!wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard)) { 676 if (!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr)) {
633 ipc_event_input("xkb_layout", keyboard->seat_device->input_device); 677 ipc_event_input("xkb_layout", keyboard->seat_device->input_device);
634 } 678 }
635 } 679 }
@@ -658,6 +702,7 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
658 } 702 }
659 703
660 keyboard->seat_device = device; 704 keyboard->seat_device = device;
705 keyboard->wlr = wlr_keyboard_from_input_device(device->input_device->wlr_device);
661 device->keyboard = keyboard; 706 device->keyboard = keyboard;
662 707
663 wl_list_init(&keyboard->keyboard_key.link); 708 wl_list_init(&keyboard->keyboard_key.link);
@@ -671,23 +716,11 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
671 716
672static void handle_xkb_context_log(struct xkb_context *context, 717static void handle_xkb_context_log(struct xkb_context *context,
673 enum xkb_log_level level, const char *format, va_list args) { 718 enum xkb_log_level level, const char *format, va_list args) {
674 va_list args_copy; 719 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 720
689 if (error[length - 2] == '\n') { 721 size_t len = strlen(error);
690 error[length - 2] = '\0'; 722 if (error[len - 1] == '\n') {
723 error[len - 1] = '\0';
691 } 724 }
692 725
693 sway_log_importance_t importance = SWAY_DEBUG; 726 sway_log_importance_t importance = SWAY_DEBUG;
@@ -708,7 +741,7 @@ static void handle_xkb_context_log(struct xkb_context *context,
708 741
709struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic, 742struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic,
710 char **error) { 743 char **error) {
711 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); 744 struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_SECURE_GETENV);
712 if (!sway_assert(context, "cannot create XKB context")) { 745 if (!sway_assert(context, "cannot create XKB context")) {
713 return NULL; 746 return NULL;
714 } 747 }
@@ -722,13 +755,8 @@ struct xkb_keymap *sway_keyboard_compile_keymap(struct input_config *ic,
722 if (!keymap_file) { 755 if (!keymap_file) {
723 sway_log_errno(SWAY_ERROR, "cannot read xkb file %s", ic->xkb_file); 756 sway_log_errno(SWAY_ERROR, "cannot read xkb file %s", ic->xkb_file);
724 if (error) { 757 if (error) {
725 size_t len = snprintf(NULL, 0, "cannot read xkb file %s: %s", 758 *error = format_str("cannot read xkb file %s: %s",
726 ic->xkb_file, strerror(errno)) + 1; 759 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 } 760 }
733 goto cleanup; 761 goto cleanup;
734 } 762 }
@@ -766,13 +794,12 @@ static void destroy_empty_wlr_keyboard_group(void *data) {
766 794
767static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) { 795static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) {
768 struct sway_input_device *device = keyboard->seat_device->input_device; 796 struct sway_input_device *device = keyboard->seat_device->input_device;
769 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard; 797 struct wlr_keyboard_group *wlr_group = keyboard->wlr->group;
770 struct wlr_keyboard_group *wlr_group = wlr_keyboard->group;
771 798
772 sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p", 799 sway_log(SWAY_DEBUG, "Removing keyboard %s from group %p",
773 device->identifier, wlr_group); 800 device->identifier, wlr_group);
774 801
775 wlr_keyboard_group_remove_keyboard(wlr_keyboard->group, wlr_keyboard); 802 wlr_keyboard_group_remove_keyboard(keyboard->wlr->group, keyboard->wlr);
776 803
777 if (wl_list_empty(&wlr_group->devices)) { 804 if (wl_list_empty(&wlr_group->devices)) {
778 sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p", 805 sway_log(SWAY_DEBUG, "Destroying empty keyboard group %p",
@@ -797,9 +824,7 @@ static void sway_keyboard_group_remove(struct sway_keyboard *keyboard) {
797} 824}
798 825
799static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) { 826static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
800 struct sway_input_device *device = keyboard->seat_device->input_device; 827 if (!keyboard->wlr->group) {
801 struct wlr_keyboard *wlr_keyboard = device->wlr_device->keyboard;
802 if (!wlr_keyboard->group) {
803 return; 828 return;
804 } 829 }
805 830
@@ -815,7 +840,7 @@ static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
815 break; 840 break;
816 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */ 841 case KEYBOARD_GROUP_DEFAULT: /* fallthrough */
817 case KEYBOARD_GROUP_SMART:; 842 case KEYBOARD_GROUP_SMART:;
818 struct wlr_keyboard_group *group = wlr_keyboard->group; 843 struct wlr_keyboard_group *group = keyboard->wlr->group;
819 if (!wlr_keyboard_keymaps_match(keyboard->keymap, group->keyboard.keymap) || 844 if (!wlr_keyboard_keymaps_match(keyboard->keymap, group->keyboard.keymap) ||
820 !repeat_info_match(keyboard, &group->keyboard)) { 845 !repeat_info_match(keyboard, &group->keyboard)) {
821 sway_keyboard_group_remove(keyboard); 846 sway_keyboard_group_remove(keyboard);
@@ -826,7 +851,6 @@ static void sway_keyboard_group_remove_invalid(struct sway_keyboard *keyboard) {
826 851
827static void sway_keyboard_group_add(struct sway_keyboard *keyboard) { 852static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
828 struct sway_input_device *device = keyboard->seat_device->input_device; 853 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; 854 struct sway_seat *seat = keyboard->seat_device->sway_seat;
831 struct seat_config *sc = seat_get_config(seat); 855 struct seat_config *sc = seat_get_config(seat);
832 856
@@ -858,7 +882,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
858 repeat_info_match(keyboard, &wlr_group->keyboard)) { 882 repeat_info_match(keyboard, &wlr_group->keyboard)) {
859 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", 883 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
860 device->identifier, wlr_group); 884 device->identifier, wlr_group);
861 wlr_keyboard_group_add_keyboard(wlr_group, wlr_keyboard); 885 wlr_keyboard_group_add_keyboard(wlr_group, keyboard->wlr);
862 return; 886 return;
863 } 887 }
864 break; 888 break;
@@ -897,7 +921,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
897 goto cleanup; 921 goto cleanup;
898 } 922 }
899 sway_group->seat_device->input_device->wlr_device = 923 sway_group->seat_device->input_device->wlr_device =
900 sway_group->wlr_group->input_device; 924 &sway_group->wlr_group->keyboard.base;
901 925
902 if (!sway_keyboard_create(seat, sway_group->seat_device)) { 926 if (!sway_keyboard_create(seat, sway_group->seat_device)) {
903 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group"); 927 sway_log(SWAY_ERROR, "Failed to allocate sway_keyboard for group");
@@ -906,7 +930,7 @@ static void sway_keyboard_group_add(struct sway_keyboard *keyboard) {
906 930
907 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p", 931 sway_log(SWAY_DEBUG, "Adding keyboard %s to group %p",
908 device->identifier, sway_group->wlr_group); 932 device->identifier, sway_group->wlr_group);
909 wlr_keyboard_group_add_keyboard(sway_group->wlr_group, wlr_keyboard); 933 wlr_keyboard_group_add_keyboard(sway_group->wlr_group, keyboard->wlr);
910 934
911 wl_list_insert(&seat->keyboard_groups, &sway_group->link); 935 wl_list_insert(&seat->keyboard_groups, &sway_group->link);
912 936
@@ -938,10 +962,8 @@ cleanup:
938void sway_keyboard_configure(struct sway_keyboard *keyboard) { 962void sway_keyboard_configure(struct sway_keyboard *keyboard) {
939 struct input_config *input_config = 963 struct input_config *input_config =
940 input_device_get_config(keyboard->seat_device->input_device); 964 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 965
944 if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(wlr_device->keyboard), 966 if (!sway_assert(!wlr_keyboard_group_from_wlr_keyboard(keyboard->wlr),
945 "sway_keyboard_configure should not be called with a " 967 "sway_keyboard_configure should not be called with a "
946 "keyboard group's keyboard")) { 968 "keyboard group's keyboard")) {
947 return; 969 return;
@@ -983,11 +1005,11 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
983 1005
984 sway_keyboard_group_remove_invalid(keyboard); 1006 sway_keyboard_group_remove_invalid(keyboard);
985 1007
986 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); 1008 wlr_keyboard_set_keymap(keyboard->wlr, keyboard->keymap);
987 wlr_keyboard_set_repeat_info(wlr_device->keyboard, 1009 wlr_keyboard_set_repeat_info(keyboard->wlr,
988 keyboard->repeat_rate, keyboard->repeat_delay); 1010 keyboard->repeat_rate, keyboard->repeat_delay);
989 1011
990 if (!wlr_device->keyboard->group) { 1012 if (!keyboard->wlr->group) {
991 sway_keyboard_group_add(keyboard); 1013 sway_keyboard_group_add(keyboard);
992 } 1014 }
993 1015
@@ -1007,40 +1029,42 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
1007 } 1029 }
1008 } 1030 }
1009 if (locked_mods) { 1031 if (locked_mods) {
1010 wlr_keyboard_notify_modifiers(wlr_device->keyboard, 0, 0, 1032 wlr_keyboard_notify_modifiers(keyboard->wlr, 0, 0,
1011 locked_mods, 0); 1033 locked_mods, 0);
1012 uint32_t leds = 0; 1034 uint32_t leds = 0;
1013 for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) { 1035 for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) {
1014 if (xkb_state_led_index_is_active( 1036 if (xkb_state_led_index_is_active(keyboard->wlr->xkb_state,
1015 wlr_device->keyboard->xkb_state, 1037 keyboard->wlr->led_indexes[i])) {
1016 wlr_device->keyboard->led_indexes[i])) {
1017 leds |= (1 << i); 1038 leds |= (1 << i);
1018 } 1039 }
1019 } 1040 }
1020 if (wlr_device->keyboard->group) { 1041 if (keyboard->wlr->group) {
1021 wlr_keyboard_led_update( 1042 wlr_keyboard_led_update(&keyboard->wlr->group->keyboard, leds);
1022 &wlr_device->keyboard->group->keyboard, leds);
1023 } else { 1043 } else {
1024 wlr_keyboard_led_update(wlr_device->keyboard, leds); 1044 wlr_keyboard_led_update(keyboard->wlr, leds);
1025 } 1045 }
1026 } 1046 }
1027 } else { 1047 } else {
1028 xkb_keymap_unref(keymap); 1048 xkb_keymap_unref(keymap);
1029 sway_keyboard_group_remove_invalid(keyboard); 1049 sway_keyboard_group_remove_invalid(keyboard);
1030 if (!wlr_device->keyboard->group) { 1050 if (!keyboard->wlr->group) {
1031 sway_keyboard_group_add(keyboard); 1051 sway_keyboard_group_add(keyboard);
1032 } 1052 }
1033 } 1053 }
1034 1054
1055 // If the seat has no active keyboard, set this one
1035 struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat; 1056 struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat;
1036 wlr_seat_set_keyboard(seat, wlr_device); 1057 struct wlr_keyboard *current_keyboard = seat->keyboard_state.keyboard;
1058 if (current_keyboard == NULL) {
1059 wlr_seat_set_keyboard(seat, keyboard->wlr);
1060 }
1037 1061
1038 wl_list_remove(&keyboard->keyboard_key.link); 1062 wl_list_remove(&keyboard->keyboard_key.link);
1039 wl_signal_add(&wlr_device->keyboard->events.key, &keyboard->keyboard_key); 1063 wl_signal_add(&keyboard->wlr->events.key, &keyboard->keyboard_key);
1040 keyboard->keyboard_key.notify = handle_keyboard_key; 1064 keyboard->keyboard_key.notify = handle_keyboard_key;
1041 1065
1042 wl_list_remove(&keyboard->keyboard_modifiers.link); 1066 wl_list_remove(&keyboard->keyboard_modifiers.link);
1043 wl_signal_add(&wlr_device->keyboard->events.modifiers, 1067 wl_signal_add(&keyboard->wlr->events.modifiers,
1044 &keyboard->keyboard_modifiers); 1068 &keyboard->keyboard_modifiers);
1045 keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; 1069 keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers;
1046 1070
@@ -1057,12 +1081,11 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
1057 if (!keyboard) { 1081 if (!keyboard) {
1058 return; 1082 return;
1059 } 1083 }
1060 if (keyboard->seat_device->input_device->wlr_device->keyboard->group) { 1084 if (keyboard->wlr->group) {
1061 sway_keyboard_group_remove(keyboard); 1085 sway_keyboard_group_remove(keyboard);
1062 } 1086 }
1063 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; 1087 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat;
1064 struct sway_input_device *device = keyboard->seat_device->input_device; 1088 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); 1089 wlr_seat_set_keyboard(wlr_seat, NULL);
1067 } 1090 }
1068 if (keyboard->keymap) { 1091 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..0c5672bc 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1,25 +1,27 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
4#include <string.h> 3#include <string.h>
5#include <strings.h> 4#include <strings.h>
6#include <time.h> 5#include <time.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_data_device.h> 8#include <wlr/types/wlr_data_device.h>
9#include <wlr/types/wlr_idle.h> 9#include <wlr/types/wlr_idle_notify_v1.h>
10#include <wlr/types/wlr_keyboard_group.h> 10#include <wlr/types/wlr_keyboard_group.h>
11#include <wlr/types/wlr_output_layout.h> 11#include <wlr/types/wlr_output_layout.h>
12#include <wlr/types/wlr_primary_selection.h> 12#include <wlr/types/wlr_primary_selection.h>
13#include <wlr/types/wlr_tablet_v2.h> 13#include <wlr/types/wlr_tablet_v2.h>
14#include <wlr/types/wlr_touch.h>
14#include <wlr/types/wlr_xcursor_manager.h> 15#include <wlr/types/wlr_xcursor_manager.h>
15#include "config.h" 16#include "config.h"
16#include "list.h" 17#include "list.h"
17#include "log.h" 18#include "log.h"
18#include "sway/config.h" 19#include "sway/config.h"
19#include "sway/desktop.h" 20#include "sway/scene_descriptor.h"
20#include "sway/input/cursor.h" 21#include "sway/input/cursor.h"
21#include "sway/input/input-manager.h" 22#include "sway/input/input-manager.h"
22#include "sway/input/keyboard.h" 23#include "sway/input/keyboard.h"
24#include "sway/input/libinput.h"
23#include "sway/input/seat.h" 25#include "sway/input/seat.h"
24#include "sway/input/switch.h" 26#include "sway/input/switch.h"
25#include "sway/input/tablet.h" 27#include "sway/input/tablet.h"
@@ -41,6 +43,7 @@ static void seat_device_destroy(struct sway_seat_device *seat_device) {
41 sway_keyboard_destroy(seat_device->keyboard); 43 sway_keyboard_destroy(seat_device->keyboard);
42 sway_tablet_destroy(seat_device->tablet); 44 sway_tablet_destroy(seat_device->tablet);
43 sway_tablet_pad_destroy(seat_device->tablet_pad); 45 sway_tablet_pad_destroy(seat_device->tablet_pad);
46 sway_switch_destroy(seat_device->switch_device);
44 wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor, 47 wlr_cursor_detach_input_device(seat_device->sway_seat->cursor->cursor,
45 seat_device->input_device->wlr_device); 48 seat_device->input_device->wlr_device);
46 wl_list_remove(&seat_device->link); 49 wl_list_remove(&seat_device->link);
@@ -50,10 +53,26 @@ static void seat_device_destroy(struct sway_seat_device *seat_device) {
50static void seat_node_destroy(struct sway_seat_node *seat_node) { 53static void seat_node_destroy(struct sway_seat_node *seat_node) {
51 wl_list_remove(&seat_node->destroy.link); 54 wl_list_remove(&seat_node->destroy.link);
52 wl_list_remove(&seat_node->link); 55 wl_list_remove(&seat_node->link);
56
57 /*
58 * This is the only time we remove items from the focus stack without
59 * immediately re-adding them. If we just removed the last thing,
60 * mark that nothing has focus anymore.
61 */
62 if (wl_list_empty(&seat_node->seat->focus_stack)) {
63 seat_node->seat->has_focus = false;
64 }
65
53 free(seat_node); 66 free(seat_node);
54} 67}
55 68
56void seat_destroy(struct sway_seat *seat) { 69void seat_destroy(struct sway_seat *seat) {
70 wlr_seat_destroy(seat->wlr_seat);
71}
72
73static void handle_seat_destroy(struct wl_listener *listener, void *data) {
74 struct sway_seat *seat = wl_container_of(listener, seat, destroy);
75
57 if (seat == config->handler_context.seat) { 76 if (seat == config->handler_context.seat) {
58 config->handler_context.seat = input_manager_get_default_seat(); 77 config->handler_context.seat = input_manager_get_default_seat();
59 } 78 }
@@ -74,10 +93,11 @@ void seat_destroy(struct sway_seat *seat) {
74 wl_list_remove(&seat->request_set_selection.link); 93 wl_list_remove(&seat->request_set_selection.link);
75 wl_list_remove(&seat->request_set_primary_selection.link); 94 wl_list_remove(&seat->request_set_primary_selection.link);
76 wl_list_remove(&seat->link); 95 wl_list_remove(&seat->link);
77 wlr_seat_destroy(seat->wlr_seat); 96 wl_list_remove(&seat->destroy.link);
78 for (int i = 0; i < seat->deferred_bindings->length; i++) { 97 for (int i = 0; i < seat->deferred_bindings->length; i++) {
79 free_sway_binding(seat->deferred_bindings->items[i]); 98 free_sway_binding(seat->deferred_bindings->items[i]);
80 } 99 }
100 wlr_scene_node_destroy(&seat->scene_tree->node);
81 list_free(seat->deferred_bindings); 101 list_free(seat->deferred_bindings);
82 free(seat->prev_workspace_name); 102 free(seat->prev_workspace_name);
83 free(seat); 103 free(seat);
@@ -85,21 +105,10 @@ void seat_destroy(struct sway_seat *seat) {
85 105
86void seat_idle_notify_activity(struct sway_seat *seat, 106void seat_idle_notify_activity(struct sway_seat *seat,
87 enum sway_input_idle_source source) { 107 enum sway_input_idle_source source) {
88 uint32_t mask = seat->idle_inhibit_sources; 108 if ((source & seat->idle_inhibit_sources) == 0) {
89 struct wlr_idle_timeout *timeout; 109 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 } 110 }
111 wlr_idle_notifier_v1_notify_activity(server.idle_notifier_v1, seat->wlr_seat);
103} 112}
104 113
105/** 114/**
@@ -129,7 +138,7 @@ static struct sway_keyboard *sway_keyboard_for_wlr_keyboard(
129 if (input_device->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) { 138 if (input_device->wlr_device->type != WLR_INPUT_DEVICE_KEYBOARD) {
130 continue; 139 continue;
131 } 140 }
132 if (input_device->wlr_device->keyboard == wlr_keyboard) { 141 if (input_device->wlr_device == &wlr_keyboard->base) {
133 return seat_device->keyboard; 142 return seat_device->keyboard;
134 } 143 }
135 } 144 }
@@ -137,7 +146,7 @@ static struct sway_keyboard *sway_keyboard_for_wlr_keyboard(
137 wl_list_for_each(group, &seat->keyboard_groups, link) { 146 wl_list_for_each(group, &seat->keyboard_groups, link) {
138 struct sway_input_device *input_device = 147 struct sway_input_device *input_device =
139 group->seat_device->input_device; 148 group->seat_device->input_device;
140 if (input_device->wlr_device->keyboard == wlr_keyboard) { 149 if (input_device->wlr_device == &wlr_keyboard->base) {
141 return group->seat_device->keyboard; 150 return group->seat_device->keyboard;
142 } 151 }
143 } 152 }
@@ -161,11 +170,11 @@ static void seat_keyboard_notify_enter(struct sway_seat *seat,
161 state->pressed_keycodes, state->npressed, &keyboard->modifiers); 170 state->pressed_keycodes, state->npressed, &keyboard->modifiers);
162} 171}
163 172
164static void seat_tablet_pads_notify_enter(struct sway_seat *seat, 173static void seat_tablet_pads_set_focus(struct sway_seat *seat,
165 struct wlr_surface *surface) { 174 struct wlr_surface *surface) {
166 struct sway_seat_device *seat_device; 175 struct sway_seat_device *seat_device;
167 wl_list_for_each(seat_device, &seat->devices, link) { 176 wl_list_for_each(seat_device, &seat->devices, link) {
168 sway_tablet_pad_notify_enter(seat_device->tablet_pad, surface); 177 sway_tablet_pad_set_focus(seat_device->tablet_pad, surface);
169 } 178 }
170} 179}
171 180
@@ -189,7 +198,7 @@ static void seat_send_focus(struct sway_node *node, struct sway_seat *seat) {
189#endif 198#endif
190 199
191 seat_keyboard_notify_enter(seat, view->surface); 200 seat_keyboard_notify_enter(seat, view->surface);
192 seat_tablet_pads_notify_enter(seat, view->surface); 201 seat_tablet_pads_set_focus(seat, view->surface);
193 sway_input_method_relay_set_focus(&seat->im_relay, view->surface); 202 sway_input_method_relay_set_focus(&seat->im_relay, view->surface);
194 203
195 struct wlr_pointer_constraint_v1 *constraint = 204 struct wlr_pointer_constraint_v1 *constraint =
@@ -209,14 +218,13 @@ void seat_for_each_node(struct sway_seat *seat,
209 218
210struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, 219struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
211 struct sway_node *ancestor) { 220 struct sway_node *ancestor) {
212 if (ancestor->type == N_CONTAINER && ancestor->sway_container->view) { 221 if (node_is_view(ancestor)) {
213 return ancestor->sway_container; 222 return ancestor->sway_container;
214 } 223 }
215 struct sway_seat_node *current; 224 struct sway_seat_node *current;
216 wl_list_for_each(current, &seat->focus_stack, link) { 225 wl_list_for_each(current, &seat->focus_stack, link) {
217 struct sway_node *node = current->node; 226 struct sway_node *node = current->node;
218 if (node->type == N_CONTAINER && node->sway_container->view && 227 if (node_is_view(node) && node_has_ancestor(node, ancestor)) {
219 node_has_ancestor(node, ancestor)) {
220 return node->sway_container; 228 return node->sway_container;
221 } 229 }
222 } 230 }
@@ -235,7 +243,7 @@ static void handle_seat_node_destroy(struct wl_listener *listener, void *data) {
235 seat_node_destroy(seat_node); 243 seat_node_destroy(seat_node);
236 // If an unmanaged or layer surface is focused when an output gets 244 // 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 245 // 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 246 // seat, the seat needs to refocus its focus inactive to update the
239 // value of seat->workspace. 247 // value of seat->workspace.
240 if (seat->workspace == node->sway_workspace) { 248 if (seat->workspace == node->sway_workspace) {
241 struct sway_node *node = seat_get_focus_inactive(seat, &root->node); 249 struct sway_node *node = seat_get_focus_inactive(seat, &root->node);
@@ -309,8 +317,8 @@ static void handle_seat_node_destroy(struct wl_listener *listener, void *data) {
309 // Setting focus_inactive 317 // Setting focus_inactive
310 focus = seat_get_focus_inactive(seat, &root->node); 318 focus = seat_get_focus_inactive(seat, &root->node);
311 seat_set_raw_focus(seat, next_focus); 319 seat_set_raw_focus(seat, next_focus);
312 if (focus->type == N_CONTAINER && focus->sway_container->workspace) { 320 if (focus->type == N_CONTAINER && focus->sway_container->pending.workspace) {
313 seat_set_raw_focus(seat, &focus->sway_container->workspace->node); 321 seat_set_raw_focus(seat, &focus->sway_container->pending.workspace->node);
314 } 322 }
315 seat_set_raw_focus(seat, focus); 323 seat_set_raw_focus(seat, focus);
316 } 324 }
@@ -351,25 +359,15 @@ static void handle_new_node(struct wl_listener *listener, void *data) {
351 seat_node_from_node(seat, node); 359 seat_node_from_node(seat, node);
352} 360}
353 361
354static void drag_icon_damage_whole(struct sway_drag_icon *icon) { 362static void drag_icon_update_position(struct sway_seat *seat, struct wlr_scene_node *node) {
355 if (!icon->wlr_drag_icon->mapped) { 363 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; 364 struct wlr_cursor *cursor = seat->cursor->cursor;
365
367 switch (wlr_icon->drag->grab_type) { 366 switch (wlr_icon->drag->grab_type) {
368 case WLR_DRAG_GRAB_KEYBOARD: 367 case WLR_DRAG_GRAB_KEYBOARD:
369 return; 368 return;
370 case WLR_DRAG_GRAB_KEYBOARD_POINTER: 369 case WLR_DRAG_GRAB_KEYBOARD_POINTER:
371 icon->x = cursor->x; 370 wlr_scene_node_set_position(node, cursor->x, cursor->y);
372 icon->y = cursor->y;
373 break; 371 break;
374 case WLR_DRAG_GRAB_KEYBOARD_TOUCH:; 372 case WLR_DRAG_GRAB_KEYBOARD_TOUCH:;
375 struct wlr_touch_point *point = 373 struct wlr_touch_point *point =
@@ -377,39 +375,15 @@ void drag_icon_update_position(struct sway_drag_icon *icon) {
377 if (point == NULL) { 375 if (point == NULL) {
378 return; 376 return;
379 } 377 }
380 icon->x = seat->touch_x; 378 wlr_scene_node_set_position(node, seat->touch_x, seat->touch_y);
381 icon->y = seat->touch_y;
382 } 379 }
383
384 drag_icon_damage_whole(icon);
385} 380}
386 381
387static void drag_icon_handle_surface_commit(struct wl_listener *listener, 382void drag_icons_update_position(struct sway_seat *seat) {
388 void *data) { 383 struct wlr_scene_node *node;
389 struct sway_drag_icon *icon = 384 wl_list_for_each(node, &seat->drag_icons->children, link) {
390 wl_container_of(listener, icon, surface_commit); 385 drag_icon_update_position(seat, node);
391 drag_icon_update_position(icon); 386 }
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}
403
404static void drag_icon_handle_destroy(struct wl_listener *listener, void *data) {
405 struct sway_drag_icon *icon = wl_container_of(listener, icon, destroy);
406 icon->wlr_drag_icon->data = NULL;
407 wl_list_remove(&icon->link);
408 wl_list_remove(&icon->surface_commit.link);
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} 387}
414 388
415static void drag_handle_destroy(struct wl_listener *listener, void *data) { 389static void drag_handle_destroy(struct wl_listener *listener, void *data) {
@@ -481,27 +455,20 @@ static void handle_start_drag(struct wl_listener *listener, void *data) {
481 455
482 struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon; 456 struct wlr_drag_icon *wlr_drag_icon = wlr_drag->icon;
483 if (wlr_drag_icon != NULL) { 457 if (wlr_drag_icon != NULL) {
484 struct sway_drag_icon *icon = calloc(1, sizeof(struct sway_drag_icon)); 458 struct wlr_scene_tree *tree = wlr_scene_drag_icon_create(seat->drag_icons, wlr_drag_icon);
485 if (icon == NULL) { 459 if (!tree) {
486 sway_log(SWAY_ERROR, "Allocation failed"); 460 sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene tree");
487 return; 461 return;
488 } 462 }
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 463
502 wl_list_insert(&root->drag_icons, &icon->link); 464 if (!scene_descriptor_assign(&tree->node, SWAY_SCENE_DESC_DRAG_ICON,
465 wlr_drag_icon)) {
466 sway_log(SWAY_ERROR, "Failed to allocate a drag icon scene descriptor");
467 wlr_scene_node_destroy(&tree->node);
468 return;
469 }
503 470
504 drag_icon_update_position(icon); 471 drag_icon_update_position(seat, &tree->node);
505 } 472 }
506 seatop_begin_default(seat); 473 seatop_begin_default(seat);
507} 474}
@@ -548,8 +515,18 @@ struct sway_seat *seat_create(const char *seat_name) {
548 return NULL; 515 return NULL;
549 } 516 }
550 517
518 bool failed = false;
519 seat->scene_tree = alloc_scene_tree(root->layers.seat, &failed);
520 seat->drag_icons = alloc_scene_tree(seat->scene_tree, &failed);
521 if (failed) {
522 wlr_scene_node_destroy(&seat->scene_tree->node);
523 free(seat);
524 return NULL;
525 }
526
551 seat->wlr_seat = wlr_seat_create(server.wl_display, seat_name); 527 seat->wlr_seat = wlr_seat_create(server.wl_display, seat_name);
552 if (!sway_assert(seat->wlr_seat, "could not allocate seat")) { 528 if (!sway_assert(seat->wlr_seat, "could not allocate seat")) {
529 wlr_scene_node_destroy(&seat->scene_tree->node);
553 free(seat); 530 free(seat);
554 return NULL; 531 return NULL;
555 } 532 }
@@ -557,11 +534,15 @@ struct sway_seat *seat_create(const char *seat_name) {
557 534
558 seat->cursor = sway_cursor_create(seat); 535 seat->cursor = sway_cursor_create(seat);
559 if (!seat->cursor) { 536 if (!seat->cursor) {
537 wlr_scene_node_destroy(&seat->scene_tree->node);
560 wlr_seat_destroy(seat->wlr_seat); 538 wlr_seat_destroy(seat->wlr_seat);
561 free(seat); 539 free(seat);
562 return NULL; 540 return NULL;
563 } 541 }
564 542
543 seat->destroy.notify = handle_seat_destroy;
544 wl_signal_add(&seat->wlr_seat->events.destroy, &seat->destroy);
545
565 seat->idle_inhibit_sources = seat->idle_wake_sources = 546 seat->idle_inhibit_sources = seat->idle_wake_sources =
566 IDLE_SOURCE_KEYBOARD | 547 IDLE_SOURCE_KEYBOARD |
567 IDLE_SOURCE_POINTER | 548 IDLE_SOURCE_POINTER |
@@ -635,7 +616,7 @@ static void seat_update_capabilities(struct sway_seat *seat) {
635 case WLR_INPUT_DEVICE_TOUCH: 616 case WLR_INPUT_DEVICE_TOUCH:
636 caps |= WL_SEAT_CAPABILITY_TOUCH; 617 caps |= WL_SEAT_CAPABILITY_TOUCH;
637 break; 618 break;
638 case WLR_INPUT_DEVICE_TABLET_TOOL: 619 case WLR_INPUT_DEVICE_TABLET:
639 caps |= WL_SEAT_CAPABILITY_POINTER; 620 caps |= WL_SEAT_CAPABILITY_POINTER;
640 break; 621 break;
641 case WLR_INPUT_DEVICE_SWITCH: 622 case WLR_INPUT_DEVICE_SWITCH:
@@ -653,7 +634,7 @@ static void seat_update_capabilities(struct sway_seat *seat) {
653 } else { 634 } else {
654 wlr_seat_set_capabilities(seat->wlr_seat, caps); 635 wlr_seat_set_capabilities(seat->wlr_seat, caps);
655 if ((previous_caps & WL_SEAT_CAPABILITY_POINTER) == 0) { 636 if ((previous_caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
656 cursor_set_image(seat->cursor, "left_ptr", NULL); 637 cursor_set_image(seat->cursor, "default", NULL);
657 } 638 }
658 } 639 }
659} 640}
@@ -666,12 +647,55 @@ static void seat_reset_input_config(struct sway_seat *seat,
666 sway_device->input_device->wlr_device, NULL); 647 sway_device->input_device->wlr_device, NULL);
667} 648}
668 649
669static void seat_apply_input_config(struct sway_seat *seat, 650static bool has_prefix(const char *str, const char *prefix) {
651 return strncmp(str, prefix, strlen(prefix)) == 0;
652}
653
654/**
655 * Get the name of the built-in output, if any. Returns NULL if there isn't
656 * exactly one built-in output.
657 */
658static const char *get_builtin_output_name(void) {
659 const char *match = NULL;
660 for (int i = 0; i < root->outputs->length; ++i) {
661 struct sway_output *output = root->outputs->items[i];
662 const char *name = output->wlr_output->name;
663 if (has_prefix(name, "eDP-") || has_prefix(name, "LVDS-") ||
664 has_prefix(name, "DSI-")) {
665 if (match != NULL) {
666 return NULL;
667 }
668 match = name;
669 }
670 }
671 return match;
672}
673
674static bool is_touch_or_tablet_tool(struct sway_seat_device *seat_device) {
675 switch (seat_device->input_device->wlr_device->type) {
676 case WLR_INPUT_DEVICE_TOUCH:
677 case WLR_INPUT_DEVICE_TABLET:
678 return true;
679 default:
680 return false;
681 }
682}
683
684static void seat_apply_input_mapping(struct sway_seat *seat,
670 struct sway_seat_device *sway_device) { 685 struct sway_seat_device *sway_device) {
671 struct input_config *ic = 686 struct input_config *ic =
672 input_device_get_config(sway_device->input_device); 687 input_device_get_config(sway_device->input_device);
673 688
674 sway_log(SWAY_DEBUG, "Applying input config to %s", 689 switch (sway_device->input_device->wlr_device->type) {
690 case WLR_INPUT_DEVICE_POINTER:
691 case WLR_INPUT_DEVICE_TOUCH:
692 case WLR_INPUT_DEVICE_TABLET:
693 break;
694 default:
695 return; // these devices don't support mappings
696 }
697
698 sway_log(SWAY_DEBUG, "Applying input mapping to %s",
675 sway_device->input_device->identifier); 699 sway_device->input_device->identifier);
676 700
677 const char *mapped_to_output = ic == NULL ? NULL : ic->mapped_to_output; 701 const char *mapped_to_output = ic == NULL ? NULL : ic->mapped_to_output;
@@ -680,8 +704,38 @@ static void seat_apply_input_config(struct sway_seat *seat,
680 ic == NULL ? MAPPED_TO_DEFAULT : ic->mapped_to; 704 ic == NULL ? MAPPED_TO_DEFAULT : ic->mapped_to;
681 705
682 switch (mapped_to) { 706 switch (mapped_to) {
683 case MAPPED_TO_DEFAULT: 707 case MAPPED_TO_DEFAULT:;
684 mapped_to_output = sway_device->input_device->wlr_device->output_name; 708 /*
709 * If the wlroots backend provides an output name, use that.
710 *
711 * Otherwise, try to map built-in touch and pointer devices to the
712 * built-in output.
713 */
714 struct wlr_input_device *dev = sway_device->input_device->wlr_device;
715 switch (dev->type) {
716 case WLR_INPUT_DEVICE_POINTER:
717 mapped_to_output = wlr_pointer_from_input_device(dev)->output_name;
718 break;
719 case WLR_INPUT_DEVICE_TOUCH:
720 mapped_to_output = wlr_touch_from_input_device(dev)->output_name;
721 break;
722 default:
723 mapped_to_output = NULL;
724 break;
725 }
726#if WLR_HAS_LIBINPUT_BACKEND
727 if (mapped_to_output == NULL && is_touch_or_tablet_tool(sway_device) &&
728 sway_libinput_device_is_builtin(sway_device->input_device)) {
729 mapped_to_output = get_builtin_output_name();
730 if (mapped_to_output) {
731 sway_log(SWAY_DEBUG, "Auto-detected output '%s' for device '%s'",
732 mapped_to_output, sway_device->input_device->identifier);
733 }
734 }
735#else
736 (void)is_touch_or_tablet_tool;
737 (void)get_builtin_output_name;
738#endif
685 if (mapped_to_output == NULL) { 739 if (mapped_to_output == NULL) {
686 return; 740 return;
687 } 741 }
@@ -725,12 +779,9 @@ static void seat_apply_input_config(struct sway_seat *seat,
725 779
726static void seat_configure_pointer(struct sway_seat *seat, 780static void seat_configure_pointer(struct sway_seat *seat,
727 struct sway_seat_device *sway_device) { 781 struct sway_seat_device *sway_device) {
728 if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { 782 seat_configure_xcursor(seat);
729 seat_configure_xcursor(seat);
730 }
731 wlr_cursor_attach_input_device(seat->cursor->cursor, 783 wlr_cursor_attach_input_device(seat->cursor->cursor,
732 sway_device->input_device->wlr_device); 784 sway_device->input_device->wlr_device);
733 seat_apply_input_config(seat, sway_device);
734 wl_event_source_timer_update( 785 wl_event_source_timer_update(
735 seat->cursor->hide_source, cursor_get_timeout(seat->cursor)); 786 seat->cursor->hide_source, cursor_get_timeout(seat->cursor));
736} 787}
@@ -741,13 +792,22 @@ static void seat_configure_keyboard(struct sway_seat *seat,
741 sway_keyboard_create(seat, seat_device); 792 sway_keyboard_create(seat, seat_device);
742 } 793 }
743 sway_keyboard_configure(seat_device->keyboard); 794 sway_keyboard_configure(seat_device->keyboard);
744 wlr_seat_set_keyboard(seat->wlr_seat, 795
745 seat_device->input_device->wlr_device); 796 // We only need to update the current keyboard, as the rest will be updated
746 struct sway_node *focus = seat_get_focus(seat); 797 // as they are activated.
747 if (focus && node_is_view(focus)) { 798 struct wlr_keyboard *wlr_keyboard =
748 // force notify reenter to pick up the new configuration 799 wlr_keyboard_from_input_device(seat_device->input_device->wlr_device);
800 struct wlr_keyboard *current_keyboard = seat->wlr_seat->keyboard_state.keyboard;
801 if (wlr_keyboard != current_keyboard) {
802 return;
803 }
804
805 // force notify reenter to pick up the new configuration. This reuses
806 // the current focused surface to avoid breaking input grabs.
807 struct wlr_surface *surface = seat->wlr_seat->keyboard_state.focused_surface;
808 if (surface) {
749 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); 809 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat);
750 seat_keyboard_notify_enter(seat, focus->sway_container->view->surface); 810 seat_keyboard_notify_enter(seat, surface);
751 } 811 }
752} 812}
753 813
@@ -756,7 +816,6 @@ static void seat_configure_switch(struct sway_seat *seat,
756 if (!seat_device->switch_device) { 816 if (!seat_device->switch_device) {
757 sway_switch_create(seat, seat_device); 817 sway_switch_create(seat, seat_device);
758 } 818 }
759 seat_apply_input_config(seat, seat_device);
760 sway_switch_configure(seat_device->switch_device); 819 sway_switch_configure(seat_device->switch_device);
761} 820}
762 821
@@ -764,7 +823,6 @@ static void seat_configure_touch(struct sway_seat *seat,
764 struct sway_seat_device *sway_device) { 823 struct sway_seat_device *sway_device) {
765 wlr_cursor_attach_input_device(seat->cursor->cursor, 824 wlr_cursor_attach_input_device(seat->cursor->cursor,
766 sway_device->input_device->wlr_device); 825 sway_device->input_device->wlr_device);
767 seat_apply_input_config(seat, sway_device);
768} 826}
769 827
770static void seat_configure_tablet_tool(struct sway_seat *seat, 828static void seat_configure_tablet_tool(struct sway_seat *seat,
@@ -775,7 +833,6 @@ static void seat_configure_tablet_tool(struct sway_seat *seat,
775 sway_configure_tablet(sway_device->tablet); 833 sway_configure_tablet(sway_device->tablet);
776 wlr_cursor_attach_input_device(seat->cursor->cursor, 834 wlr_cursor_attach_input_device(seat->cursor->cursor,
777 sway_device->input_device->wlr_device); 835 sway_device->input_device->wlr_device);
778 seat_apply_input_config(seat, sway_device);
779} 836}
780 837
781static void seat_configure_tablet_pad(struct sway_seat *seat, 838static void seat_configure_tablet_pad(struct sway_seat *seat,
@@ -825,13 +882,25 @@ void seat_configure_device(struct sway_seat *seat,
825 case WLR_INPUT_DEVICE_TOUCH: 882 case WLR_INPUT_DEVICE_TOUCH:
826 seat_configure_touch(seat, seat_device); 883 seat_configure_touch(seat, seat_device);
827 break; 884 break;
828 case WLR_INPUT_DEVICE_TABLET_TOOL: 885 case WLR_INPUT_DEVICE_TABLET:
829 seat_configure_tablet_tool(seat, seat_device); 886 seat_configure_tablet_tool(seat, seat_device);
830 break; 887 break;
831 case WLR_INPUT_DEVICE_TABLET_PAD: 888 case WLR_INPUT_DEVICE_TABLET_PAD:
832 seat_configure_tablet_pad(seat, seat_device); 889 seat_configure_tablet_pad(seat, seat_device);
833 break; 890 break;
834 } 891 }
892
893 seat_apply_input_mapping(seat, seat_device);
894}
895
896void seat_configure_device_mapping(struct sway_seat *seat,
897 struct sway_input_device *input_device) {
898 struct sway_seat_device *seat_device = seat_get_device(seat, input_device);
899 if (!seat_device) {
900 return;
901 }
902
903 seat_apply_input_mapping(seat, seat_device);
835} 904}
836 905
837void seat_reset_device(struct sway_seat *seat, 906void seat_reset_device(struct sway_seat *seat,
@@ -852,7 +921,7 @@ void seat_reset_device(struct sway_seat *seat,
852 case WLR_INPUT_DEVICE_TOUCH: 921 case WLR_INPUT_DEVICE_TOUCH:
853 seat_reset_input_config(seat, seat_device); 922 seat_reset_input_config(seat, seat_device);
854 break; 923 break;
855 case WLR_INPUT_DEVICE_TABLET_TOOL: 924 case WLR_INPUT_DEVICE_TABLET:
856 seat_reset_input_config(seat, seat_device); 925 seat_reset_input_config(seat, seat_device);
857 break; 926 break;
858 case WLR_INPUT_DEVICE_TABLET_PAD: 927 case WLR_INPUT_DEVICE_TABLET_PAD:
@@ -948,7 +1017,7 @@ void seat_configure_xcursor(struct sway_seat *seat) {
948 1017
949 wlr_xcursor_manager_load(server.xwayland.xcursor_manager, 1); 1018 wlr_xcursor_manager_load(server.xwayland.xcursor_manager, 1);
950 struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( 1019 struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
951 server.xwayland.xcursor_manager, "left_ptr", 1); 1020 server.xwayland.xcursor_manager, "default", 1);
952 if (xcursor != NULL) { 1021 if (xcursor != NULL) {
953 struct wlr_xcursor_image *image = xcursor->images[0]; 1022 struct wlr_xcursor_image *image = xcursor->images[0];
954 wlr_xwayland_set_cursor( 1023 wlr_xwayland_set_cursor(
@@ -974,32 +1043,35 @@ void seat_configure_xcursor(struct sway_seat *seat) {
974 sway_log(SWAY_ERROR, 1043 sway_log(SWAY_ERROR,
975 "Cannot create XCursor manager for theme '%s'", cursor_theme); 1044 "Cannot create XCursor manager for theme '%s'", cursor_theme);
976 } 1045 }
977 }
978 1046
979 for (int i = 0; i < root->outputs->length; ++i) { 1047
980 struct sway_output *sway_output = root->outputs->items[i]; 1048 for (int i = 0; i < root->outputs->length; ++i) {
981 struct wlr_output *output = sway_output->wlr_output; 1049 struct sway_output *sway_output = root->outputs->items[i];
982 bool result = 1050 struct wlr_output *output = sway_output->wlr_output;
983 wlr_xcursor_manager_load(seat->cursor->xcursor_manager, 1051 bool result =
984 output->scale); 1052 wlr_xcursor_manager_load(seat->cursor->xcursor_manager,
985 if (!result) { 1053 output->scale);
986 sway_log(SWAY_ERROR, 1054 if (!result) {
987 "Cannot load xcursor theme for output '%s' with scale %f", 1055 sway_log(SWAY_ERROR,
988 output->name, output->scale); 1056 "Cannot load xcursor theme for output '%s' with scale %f",
1057 output->name, output->scale);
1058 }
989 } 1059 }
990 }
991 1060
992 // Reset the cursor so that we apply it to outputs that just appeared 1061 // Reset the cursor so that we apply it to outputs that just appeared
993 cursor_set_image(seat->cursor, NULL, NULL); 1062 cursor_set_image(seat->cursor, NULL, NULL);
994 cursor_set_image(seat->cursor, "left_ptr", NULL); 1063 cursor_set_image(seat->cursor, "default", NULL);
995 wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x, 1064 wlr_cursor_warp(seat->cursor->cursor, NULL, seat->cursor->cursor->x,
996 seat->cursor->cursor->y); 1065 seat->cursor->cursor->y);
1066 }
997} 1067}
998 1068
999bool seat_is_input_allowed(struct sway_seat *seat, 1069bool seat_is_input_allowed(struct sway_seat *seat,
1000 struct wlr_surface *surface) { 1070 struct wlr_surface *surface) {
1001 struct wl_client *client = wl_resource_get_client(surface->resource); 1071 if (server.session_lock.lock) {
1002 return !seat->exclusive_client || seat->exclusive_client == client; 1072 return sway_session_lock_has_surface(server.session_lock.lock, surface);
1073 }
1074 return true;
1003} 1075}
1004 1076
1005static void send_unfocus(struct sway_container *con, void *data) { 1077static void send_unfocus(struct sway_container *con, void *data) {
@@ -1058,15 +1130,7 @@ void seat_set_raw_focus(struct sway_seat *seat, struct sway_node *node) {
1058 } 1130 }
1059} 1131}
1060 1132
1061void seat_set_focus(struct sway_seat *seat, struct sway_node *node) { 1133static 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); 1134 struct sway_node *last_focus = seat_get_focus(seat);
1071 if (last_focus == node) { 1135 if (last_focus == node) {
1072 return; 1136 return;
@@ -1086,30 +1150,19 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1086 } 1150 }
1087 1151
1088 struct sway_workspace *new_workspace = node->type == N_WORKSPACE ? 1152 struct sway_workspace *new_workspace = node->type == N_WORKSPACE ?
1089 node->sway_workspace : node->sway_container->workspace; 1153 node->sway_workspace : node->sway_container->pending.workspace;
1090 struct sway_container *container = node->type == N_CONTAINER ? 1154 struct sway_container *container = node->type == N_CONTAINER ?
1091 node->sway_container : NULL; 1155 node->sway_container : NULL;
1092 1156
1093 // Deny setting focus to a view which is hidden by a fullscreen container 1157 // Deny setting focus to a view which is hidden by a fullscreen container or global
1094 if (new_workspace && new_workspace->fullscreen && container && 1158 if (container && container_obstructing_fullscreen_container(container)) {
1095 !container_is_fullscreen_or_child(container)) { 1159 return;
1096 // Unless it's a transient container
1097 if (!container_is_transient_for(container, new_workspace->fullscreen)) {
1098 return;
1099 }
1100 } 1160 }
1161
1101 // Deny setting focus to a workspace node when using fullscreen global 1162 // Deny setting focus to a workspace node when using fullscreen global
1102 if (root->fullscreen_global && !container && new_workspace) { 1163 if (root->fullscreen_global && !container && new_workspace) {
1103 return; 1164 return;
1104 } 1165 }
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 1166
1114 struct sway_output *new_output = 1167 struct sway_output *new_output =
1115 new_workspace ? new_workspace->output : NULL; 1168 new_workspace ? new_workspace->output : NULL;
@@ -1135,10 +1188,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 1188 // Put the container parents on the focus stack, then the workspace, then
1136 // the focused container. 1189 // the focused container.
1137 if (container) { 1190 if (container) {
1138 struct sway_container *parent = container->parent; 1191 struct sway_container *parent = container->pending.parent;
1139 while (parent) { 1192 while (parent) {
1140 seat_set_raw_focus(seat, &parent->node); 1193 seat_set_raw_focus(seat, &parent->node);
1141 parent = parent->parent; 1194 parent = parent->pending.parent;
1142 } 1195 }
1143 } 1196 }
1144 if (new_workspace) { 1197 if (new_workspace) {
@@ -1210,6 +1263,24 @@ void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1210 } 1263 }
1211} 1264}
1212 1265
1266void seat_set_focus(struct sway_seat *seat, struct sway_node *node) {
1267 // Prevents the layer from losing focus if it has keyboard exclusivity
1268 if (seat->has_exclusive_layer) {
1269 struct wlr_layer_surface_v1 *layer = seat->focused_layer;
1270 seat_set_focus_layer(seat, NULL);
1271 seat_set_workspace_focus(seat, node);
1272 seat_set_focus_layer(seat, layer);
1273 } else if (seat->focused_layer) {
1274 seat_set_focus_layer(seat, NULL);
1275 seat_set_workspace_focus(seat, node);
1276 } else {
1277 seat_set_workspace_focus(seat, node);
1278 }
1279 if (server.session_lock.lock) {
1280 seat_set_focus_surface(seat, server.session_lock.lock->focused, false);
1281 }
1282}
1283
1213void seat_set_focus_container(struct sway_seat *seat, 1284void seat_set_focus_container(struct sway_seat *seat,
1214 struct sway_container *con) { 1285 struct sway_container *con) {
1215 seat_set_focus(seat, con ? &con->node : NULL); 1286 seat_set_focus(seat, con ? &con->node : NULL);
@@ -1234,7 +1305,8 @@ void seat_set_focus_surface(struct sway_seat *seat,
1234 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat); 1305 wlr_seat_keyboard_notify_clear_focus(seat->wlr_seat);
1235 } 1306 }
1236 1307
1237 seat_tablet_pads_notify_enter(seat, surface); 1308 sway_input_method_relay_set_focus(&seat->im_relay, surface);
1309 seat_tablet_pads_set_focus(seat, surface);
1238} 1310}
1239 1311
1240void seat_set_focus_layer(struct sway_seat *seat, 1312void seat_set_focus_layer(struct sway_seat *seat,
@@ -1248,28 +1320,23 @@ void seat_set_focus_layer(struct sway_seat *seat,
1248 seat_set_focus(seat, previous); 1320 seat_set_focus(seat, previous);
1249 } 1321 }
1250 return; 1322 return;
1251 } else if (!layer || seat->focused_layer == layer) { 1323 } else if (!layer) {
1252 return; 1324 return;
1253 } 1325 }
1254 assert(layer->mapped); 1326 assert(layer->surface->mapped);
1255 seat_set_focus_surface(seat, layer->surface, true); 1327 if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP &&
1256 if (layer->current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP) { 1328 layer->current.keyboard_interactive
1257 seat->focused_layer = layer; 1329 == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE) {
1330 seat->has_exclusive_layer = true;
1258 } 1331 }
1259} 1332 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; 1333 return;
1272 } 1334 }
1335 seat_set_focus_surface(seat, layer->surface, true);
1336 seat->focused_layer = layer;
1337}
1338
1339void seat_unfocus_unless_client(struct sway_seat *seat, struct wl_client *client) {
1273 if (seat->focused_layer) { 1340 if (seat->focused_layer) {
1274 if (wl_resource_get_client(seat->focused_layer->resource) != client) { 1341 if (wl_resource_get_client(seat->focused_layer->resource) != client) {
1275 seat_set_focus_layer(seat, NULL); 1342 seat_set_focus_layer(seat, NULL);
@@ -1296,7 +1363,6 @@ void seat_set_exclusive_client(struct sway_seat *seat,
1296 now.tv_nsec / 1000, point->touch_id); 1363 now.tv_nsec / 1000, point->touch_id);
1297 } 1364 }
1298 } 1365 }
1299 seat->exclusive_client = client;
1300} 1366}
1301 1367
1302struct sway_node *seat_get_focus_inactive(struct sway_seat *seat, 1368struct sway_node *seat_get_focus_inactive(struct sway_seat *seat,
@@ -1326,7 +1392,7 @@ struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat,
1326 struct sway_node *node = current->node; 1392 struct sway_node *node = current->node;
1327 if (node->type == N_CONTAINER && 1393 if (node->type == N_CONTAINER &&
1328 !container_is_floating_or_child(node->sway_container) && 1394 !container_is_floating_or_child(node->sway_container) &&
1329 node->sway_container->workspace == workspace) { 1395 node->sway_container->pending.workspace == workspace) {
1330 return node->sway_container; 1396 return node->sway_container;
1331 } 1397 }
1332 } 1398 }
@@ -1343,7 +1409,7 @@ struct sway_container *seat_get_focus_inactive_floating(struct sway_seat *seat,
1343 struct sway_node *node = current->node; 1409 struct sway_node *node = current->node;
1344 if (node->type == N_CONTAINER && 1410 if (node->type == N_CONTAINER &&
1345 container_is_floating_or_child(node->sway_container) && 1411 container_is_floating_or_child(node->sway_container) &&
1346 node->sway_container->workspace == workspace) { 1412 node->sway_container->pending.workspace == workspace) {
1347 return node->sway_container; 1413 return node->sway_container;
1348 } 1414 }
1349 } 1415 }
@@ -1377,9 +1443,8 @@ struct sway_node *seat_get_focus(struct sway_seat *seat) {
1377 if (!seat->has_focus) { 1443 if (!seat->has_focus) {
1378 return NULL; 1444 return NULL;
1379 } 1445 }
1380 if (wl_list_empty(&seat->focus_stack)) { 1446 sway_assert(!wl_list_empty(&seat->focus_stack),
1381 return NULL; 1447 "focus_stack is empty, but has_focus is true");
1382 }
1383 struct sway_seat_node *current = 1448 struct sway_seat_node *current =
1384 wl_container_of(seat->focus_stack.next, current, link); 1449 wl_container_of(seat->focus_stack.next, current, link);
1385 return current->node; 1450 return current->node;
@@ -1391,7 +1456,7 @@ struct sway_workspace *seat_get_focused_workspace(struct sway_seat *seat) {
1391 return NULL; 1456 return NULL;
1392 } 1457 }
1393 if (focus->type == N_CONTAINER) { 1458 if (focus->type == N_CONTAINER) {
1394 return focus->sway_container->workspace; 1459 return focus->sway_container->pending.workspace;
1395 } 1460 }
1396 if (focus->type == N_WORKSPACE) { 1461 if (focus->type == N_WORKSPACE) {
1397 return focus->sway_workspace; 1462 return focus->sway_workspace;
@@ -1404,8 +1469,8 @@ struct sway_workspace *seat_get_last_known_workspace(struct sway_seat *seat) {
1404 wl_list_for_each(current, &seat->focus_stack, link) { 1469 wl_list_for_each(current, &seat->focus_stack, link) {
1405 struct sway_node *node = current->node; 1470 struct sway_node *node = current->node;
1406 if (node->type == N_CONTAINER && 1471 if (node->type == N_CONTAINER &&
1407 node->sway_container->workspace) { 1472 node->sway_container->pending.workspace) {
1408 return node->sway_container->workspace; 1473 return node->sway_container->pending.workspace;
1409 } else if (node->type == N_WORKSPACE) { 1474 } else if (node->type == N_WORKSPACE) {
1410 return node->sway_workspace; 1475 return node->sway_workspace;
1411 } 1476 }
@@ -1464,7 +1529,7 @@ struct seat_config *seat_get_config_by_name(const char *name) {
1464} 1529}
1465 1530
1466void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec, 1531void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
1467 uint32_t button, enum wlr_button_state state) { 1532 uint32_t button, enum wl_pointer_button_state state) {
1468 seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat, 1533 seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat,
1469 time_msec, button, state); 1534 time_msec, button, state);
1470} 1535}
@@ -1501,7 +1566,7 @@ void seatop_unref(struct sway_seat *seat, struct sway_container *con) {
1501 1566
1502void seatop_button(struct sway_seat *seat, uint32_t time_msec, 1567void seatop_button(struct sway_seat *seat, uint32_t time_msec,
1503 struct wlr_input_device *device, uint32_t button, 1568 struct wlr_input_device *device, uint32_t button,
1504 enum wlr_button_state state) { 1569 enum wl_pointer_button_state state) {
1505 if (seat->seatop_impl->button) { 1570 if (seat->seatop_impl->button) {
1506 seat->seatop_impl->button(seat, time_msec, device, button, state); 1571 seat->seatop_impl->button(seat, time_msec, device, button, state);
1507 } 1572 }
@@ -1514,12 +1579,38 @@ void seatop_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
1514} 1579}
1515 1580
1516void seatop_pointer_axis(struct sway_seat *seat, 1581void seatop_pointer_axis(struct sway_seat *seat,
1517 struct wlr_event_pointer_axis *event) { 1582 struct wlr_pointer_axis_event *event) {
1518 if (seat->seatop_impl->pointer_axis) { 1583 if (seat->seatop_impl->pointer_axis) {
1519 seat->seatop_impl->pointer_axis(seat, event); 1584 seat->seatop_impl->pointer_axis(seat, event);
1520 } 1585 }
1521} 1586}
1522 1587
1588void seatop_touch_motion(struct sway_seat *seat, struct wlr_touch_motion_event *event,
1589 double lx, double ly) {
1590 if (seat->seatop_impl->touch_motion) {
1591 seat->seatop_impl->touch_motion(seat, event, lx, ly);
1592 }
1593}
1594
1595void seatop_touch_up(struct sway_seat *seat, struct wlr_touch_up_event *event) {
1596 if (seat->seatop_impl->touch_up) {
1597 seat->seatop_impl->touch_up(seat, event);
1598 }
1599}
1600
1601void seatop_touch_down(struct sway_seat *seat, struct wlr_touch_down_event *event,
1602 double lx, double ly) {
1603 if (seat->seatop_impl->touch_down) {
1604 seat->seatop_impl->touch_down(seat, event, lx, ly);
1605 }
1606}
1607
1608void seatop_touch_cancel(struct sway_seat *seat, struct wlr_touch_cancel_event *event) {
1609 if (seat->seatop_impl->touch_cancel) {
1610 seat->seatop_impl->touch_cancel(seat, event);
1611 }
1612}
1613
1523void seatop_tablet_tool_tip(struct sway_seat *seat, 1614void seatop_tablet_tool_tip(struct sway_seat *seat,
1524 struct sway_tablet_tool *tool, uint32_t time_msec, 1615 struct sway_tablet_tool *tool, uint32_t time_msec,
1525 enum wlr_tablet_tool_tip_state state) { 1616 enum wlr_tablet_tool_tip_state state) {
@@ -1537,6 +1628,62 @@ void seatop_tablet_tool_motion(struct sway_seat *seat,
1537 } 1628 }
1538} 1629}
1539 1630
1631void seatop_hold_begin(struct sway_seat *seat,
1632 struct wlr_pointer_hold_begin_event *event) {
1633 if (seat->seatop_impl->hold_begin) {
1634 seat->seatop_impl->hold_begin(seat, event);
1635 }
1636}
1637
1638void seatop_hold_end(struct sway_seat *seat,
1639 struct wlr_pointer_hold_end_event *event) {
1640 if (seat->seatop_impl->hold_end) {
1641 seat->seatop_impl->hold_end(seat, event);
1642 }
1643}
1644
1645void seatop_pinch_begin(struct sway_seat *seat,
1646 struct wlr_pointer_pinch_begin_event *event) {
1647 if (seat->seatop_impl->pinch_begin) {
1648 seat->seatop_impl->pinch_begin(seat, event);
1649 }
1650}
1651
1652void seatop_pinch_update(struct sway_seat *seat,
1653 struct wlr_pointer_pinch_update_event *event) {
1654 if (seat->seatop_impl->pinch_update) {
1655 seat->seatop_impl->pinch_update(seat, event);
1656 }
1657}
1658
1659void seatop_pinch_end(struct sway_seat *seat,
1660 struct wlr_pointer_pinch_end_event *event) {
1661 if (seat->seatop_impl->pinch_end) {
1662 seat->seatop_impl->pinch_end(seat, event);
1663 }
1664}
1665
1666void seatop_swipe_begin(struct sway_seat *seat,
1667 struct wlr_pointer_swipe_begin_event *event) {
1668 if (seat->seatop_impl->swipe_begin) {
1669 seat->seatop_impl->swipe_begin(seat, event);
1670 }
1671}
1672
1673void seatop_swipe_update(struct sway_seat *seat,
1674 struct wlr_pointer_swipe_update_event *event) {
1675 if (seat->seatop_impl->swipe_update) {
1676 seat->seatop_impl->swipe_update(seat, event);
1677 }
1678}
1679
1680void seatop_swipe_end(struct sway_seat *seat,
1681 struct wlr_pointer_swipe_end_event *event) {
1682 if (seat->seatop_impl->swipe_end) {
1683 seat->seatop_impl->swipe_end(seat, event);
1684 }
1685}
1686
1540void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) { 1687void seatop_rebase(struct sway_seat *seat, uint32_t time_msec) {
1541 if (seat->seatop_impl->rebase) { 1688 if (seat->seatop_impl->rebase) {
1542 seat->seatop_impl->rebase(seat, time_msec); 1689 seat->seatop_impl->rebase(seat, time_msec);
@@ -1552,13 +1699,6 @@ void seatop_end(struct sway_seat *seat) {
1552 seat->seatop_impl = NULL; 1699 seat->seatop_impl = NULL;
1553} 1700}
1554 1701
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) { 1702bool seatop_allows_set_cursor(struct sway_seat *seat) {
1563 return seat->seatop_impl->allow_set_cursor; 1703 return seat->seatop_impl->allow_set_cursor;
1564} 1704}
diff --git a/sway/input/seatop_default.c b/sway/input/seatop_default.c
index a583ed62..0c6f7c5e 100644
--- a/sway/input/seatop_default.c
+++ b/sway/input/seatop_default.c
@@ -1,13 +1,17 @@
1#define _POSIX_C_SOURCE 200809L
2#include <float.h> 1#include <float.h>
3#include <libevdev/libevdev.h> 2#include <libevdev/libevdev.h>
4#include <wlr/types/wlr_cursor.h> 3#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_subcompositor.h>
5#include <wlr/types/wlr_tablet_v2.h> 5#include <wlr/types/wlr_tablet_v2.h>
6#include <wlr/types/wlr_xcursor_manager.h> 6#include <wlr/types/wlr_xcursor_manager.h>
7#include "gesture.h"
8#include "sway/desktop/transaction.h"
7#include "sway/input/cursor.h" 9#include "sway/input/cursor.h"
8#include "sway/input/seat.h" 10#include "sway/input/seat.h"
9#include "sway/input/tablet.h" 11#include "sway/input/tablet.h"
12#include "sway/layers.h"
10#include "sway/output.h" 13#include "sway/output.h"
14#include "sway/scene_descriptor.h"
11#include "sway/tree/view.h" 15#include "sway/tree/view.h"
12#include "sway/tree/workspace.h" 16#include "sway/tree/workspace.h"
13#include "log.h" 17#include "log.h"
@@ -19,6 +23,7 @@ struct seatop_default_event {
19 struct sway_node *previous_node; 23 struct sway_node *previous_node;
20 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP]; 24 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
21 size_t pressed_button_count; 25 size_t pressed_button_count;
26 struct gesture_tracker gestures;
22}; 27};
23 28
24/*-----------------------------------------\ 29/*-----------------------------------------\
@@ -50,6 +55,9 @@ static bool edge_is_external(struct sway_container *cont, enum wlr_edges edge) {
50 while (cont) { 55 while (cont) {
51 if (container_parent_layout(cont) == layout) { 56 if (container_parent_layout(cont) == layout) {
52 list_t *siblings = container_get_siblings(cont); 57 list_t *siblings = container_get_siblings(cont);
58 if (!siblings) {
59 return false;
60 }
53 int index = list_find(siblings, cont); 61 int index = list_find(siblings, cont);
54 if (index > 0 && (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_TOP)) { 62 if (index > 0 && (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_TOP)) {
55 return false; 63 return false;
@@ -59,7 +67,7 @@ static bool edge_is_external(struct sway_container *cont, enum wlr_edges edge) {
59 return false; 67 return false;
60 } 68 }
61 } 69 }
62 cont = cont->parent; 70 cont = cont->pending.parent;
63 } 71 }
64 return true; 72 return true;
65} 73}
@@ -69,25 +77,25 @@ static enum wlr_edges find_edge(struct sway_container *cont,
69 if (!cont->view || (surface && cont->view->surface != surface)) { 77 if (!cont->view || (surface && cont->view->surface != surface)) {
70 return WLR_EDGE_NONE; 78 return WLR_EDGE_NONE;
71 } 79 }
72 if (cont->border == B_NONE || !cont->border_thickness || 80 if (cont->pending.border == B_NONE || !cont->pending.border_thickness ||
73 cont->border == B_CSD) { 81 cont->pending.border == B_CSD) {
74 return WLR_EDGE_NONE; 82 return WLR_EDGE_NONE;
75 } 83 }
76 if (cont->fullscreen_mode) { 84 if (cont->pending.fullscreen_mode) {
77 return WLR_EDGE_NONE; 85 return WLR_EDGE_NONE;
78 } 86 }
79 87
80 enum wlr_edges edge = 0; 88 enum wlr_edges edge = 0;
81 if (cursor->cursor->x < cont->x + cont->border_thickness) { 89 if (cursor->cursor->x < cont->pending.x + cont->pending.border_thickness) {
82 edge |= WLR_EDGE_LEFT; 90 edge |= WLR_EDGE_LEFT;
83 } 91 }
84 if (cursor->cursor->y < cont->y + cont->border_thickness) { 92 if (cursor->cursor->y < cont->pending.y + cont->pending.border_thickness) {
85 edge |= WLR_EDGE_TOP; 93 edge |= WLR_EDGE_TOP;
86 } 94 }
87 if (cursor->cursor->x >= cont->x + cont->width - cont->border_thickness) { 95 if (cursor->cursor->x >= cont->pending.x + cont->pending.width - cont->pending.border_thickness) {
88 edge |= WLR_EDGE_RIGHT; 96 edge |= WLR_EDGE_RIGHT;
89 } 97 }
90 if (cursor->cursor->y >= cont->y + cont->height - cont->border_thickness) { 98 if (cursor->cursor->y >= cont->pending.y + cont->pending.height - cont->pending.border_thickness) {
91 edge |= WLR_EDGE_BOTTOM; 99 edge |= WLR_EDGE_BOTTOM;
92 } 100 }
93 101
@@ -225,13 +233,15 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
225 struct sway_container *cont = node && node->type == N_CONTAINER ? 233 struct sway_container *cont = node && node->type == N_CONTAINER ?
226 node->sway_container : NULL; 234 node->sway_container : NULL;
227 235
228 if (wlr_surface_is_layer_surface(surface)) { 236 struct wlr_layer_surface_v1 *layer;
237#if HAVE_XWAYLAND
238 struct wlr_xwayland_surface *xsurface;
239#endif
240 if ((layer = wlr_layer_surface_v1_try_from_wlr_surface(surface)) &&
241 layer->current.keyboard_interactive) {
229 // Handle tapping a layer surface 242 // Handle tapping a layer surface
230 struct wlr_layer_surface_v1 *layer = 243 seat_set_focus_layer(seat, layer);
231 wlr_layer_surface_v1_from_wlr_surface(surface); 244 transaction_commit_dirty();
232 if (layer->current.keyboard_interactive) {
233 seat_set_focus_layer(seat, layer);
234 }
235 } else if (cont) { 245 } else if (cont) {
236 bool is_floating_or_child = container_is_floating_or_child(cont); 246 bool is_floating_or_child = container_is_floating_or_child(cont);
237 bool is_fullscreen_or_child = container_is_fullscreen_or_child(cont); 247 bool is_fullscreen_or_child = container_is_fullscreen_or_child(cont);
@@ -249,26 +259,24 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
249 259
250 // Handle moving a tiling container 260 // Handle moving a tiling container
251 if (config->tiling_drag && mod_pressed && !is_floating_or_child && 261 if (config->tiling_drag && mod_pressed && !is_floating_or_child &&
252 cont->fullscreen_mode == FULLSCREEN_NONE) { 262 cont->pending.fullscreen_mode == FULLSCREEN_NONE) {
253 seatop_begin_move_tiling(seat, cont); 263 seatop_begin_move_tiling(seat, cont);
254 return; 264 return;
255 } 265 }
256 266
257 // Handle tapping on a container surface 267 // Handle tapping on a container surface
258 seat_set_focus_container(seat, cont); 268 seat_set_focus_container(seat, cont);
259 seatop_begin_down(seat, node->sway_container, time_msec, sx, sy); 269 seatop_begin_down(seat, node->sway_container, sx, sy);
260 } 270 }
261#if HAVE_XWAYLAND 271#if HAVE_XWAYLAND
262 // Handle tapping on an xwayland unmanaged view 272 // Handle tapping on an xwayland unmanaged view
263 else if (wlr_surface_is_xwayland_surface(surface)) { 273 else if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) &&
264 struct wlr_xwayland_surface *xsurface = 274 xsurface->override_redirect &&
265 wlr_xwayland_surface_from_wlr_surface(surface); 275 wlr_xwayland_or_surface_wants_focus(xsurface)) {
266 if (xsurface->override_redirect && 276 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
267 wlr_xwayland_or_surface_wants_focus(xsurface)) { 277 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
268 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; 278 seat_set_focus_surface(seat, xsurface->surface, false);
269 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 279 transaction_commit_dirty();
270 seat_set_focus_surface(seat, xsurface->surface, false);
271 }
272 } 280 }
273#endif 281#endif
274 282
@@ -282,7 +290,7 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
282 290
283static bool trigger_pointer_button_binding(struct sway_seat *seat, 291static bool trigger_pointer_button_binding(struct sway_seat *seat,
284 struct wlr_input_device *device, uint32_t button, 292 struct wlr_input_device *device, uint32_t button,
285 enum wlr_button_state state, uint32_t modifiers, 293 enum wl_pointer_button_state state, uint32_t modifiers,
286 bool on_titlebar, bool on_border, bool on_contents, bool on_workspace) { 294 bool on_titlebar, bool on_border, bool on_contents, bool on_workspace) {
287 // We can reach this for non-pointer devices if we're currently emulating 295 // We can reach this for non-pointer devices if we're currently emulating
288 // pointer input for one. Emulated input should not trigger bindings. The 296 // pointer input for one. Emulated input should not trigger bindings. The
@@ -296,7 +304,7 @@ static bool trigger_pointer_button_binding(struct sway_seat *seat,
296 char *device_identifier = device ? input_device_get_identifier(device) 304 char *device_identifier = device ? input_device_get_identifier(device)
297 : strdup("*"); 305 : strdup("*");
298 struct sway_binding *binding = NULL; 306 struct sway_binding *binding = NULL;
299 if (state == WLR_BUTTON_PRESSED) { 307 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
300 state_add_button(e, button); 308 state_add_button(e, button);
301 binding = get_active_mouse_binding(e, 309 binding = get_active_mouse_binding(e,
302 config->current_mode->mouse_bindings, modifiers, false, 310 config->current_mode->mouse_bindings, modifiers, false,
@@ -321,7 +329,7 @@ static bool trigger_pointer_button_binding(struct sway_seat *seat,
321 329
322static void handle_button(struct sway_seat *seat, uint32_t time_msec, 330static void handle_button(struct sway_seat *seat, uint32_t time_msec,
323 struct wlr_input_device *device, uint32_t button, 331 struct wlr_input_device *device, uint32_t button,
324 enum wlr_button_state state) { 332 enum wl_pointer_button_state state) {
325 struct sway_cursor *cursor = seat->cursor; 333 struct sway_cursor *cursor = seat->cursor;
326 334
327 // Determine what's under the cursor 335 // Determine what's under the cursor
@@ -354,19 +362,23 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
354 362
355 // Handle clicking an empty workspace 363 // Handle clicking an empty workspace
356 if (node && node->type == N_WORKSPACE) { 364 if (node && node->type == N_WORKSPACE) {
357 if (state == WLR_BUTTON_PRESSED) { 365 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
358 seat_set_focus(seat, node); 366 seat_set_focus(seat, node);
367 transaction_commit_dirty();
359 } 368 }
360 seat_pointer_notify_button(seat, time_msec, button, state); 369 seat_pointer_notify_button(seat, time_msec, button, state);
361 return; 370 return;
362 } 371 }
363 372
364 // Handle clicking a layer surface 373 // Handle clicking a layer surface and its popups/subsurfaces
365 if (surface && wlr_surface_is_layer_surface(surface)) { 374 struct wlr_layer_surface_v1 *layer = NULL;
366 struct wlr_layer_surface_v1 *layer = 375 if ((layer = toplevel_layer_surface_from_surface(surface))) {
367 wlr_layer_surface_v1_from_wlr_surface(surface);
368 if (layer->current.keyboard_interactive) { 376 if (layer->current.keyboard_interactive) {
369 seat_set_focus_layer(seat, layer); 377 seat_set_focus_layer(seat, layer);
378 transaction_commit_dirty();
379 }
380 if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
381 seatop_begin_down_on_surface(seat, surface, sx, sy);
370 } 382 }
371 seat_pointer_notify_button(seat, time_msec, button, state); 383 seat_pointer_notify_button(seat, time_msec, button, state);
372 return; 384 return;
@@ -374,14 +386,14 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
374 386
375 // Handle tiling resize via border 387 // Handle tiling resize via border
376 if (cont && resize_edge && button == BTN_LEFT && 388 if (cont && resize_edge && button == BTN_LEFT &&
377 state == WLR_BUTTON_PRESSED && !is_floating) { 389 state == WL_POINTER_BUTTON_STATE_PRESSED && !is_floating) {
378 // If a resize is triggered on a tabbed or stacked container, change 390 // If a resize is triggered on a tabbed or stacked container, change
379 // focus to the tab which already had inactive focus -- otherwise, we'd 391 // focus to the tab which already had inactive focus -- otherwise, we'd
380 // change the active tab when the user probably just wanted to resize. 392 // change the active tab when the user probably just wanted to resize.
381 struct sway_container *cont_to_focus = cont; 393 struct sway_container *cont_to_focus = cont;
382 enum sway_container_layout layout = container_parent_layout(cont); 394 enum sway_container_layout layout = container_parent_layout(cont);
383 if (layout == L_TABBED || layout == L_STACKED) { 395 if (layout == L_TABBED || layout == L_STACKED) {
384 cont_to_focus = seat_get_focus_inactive_view(seat, &cont->parent->node); 396 cont_to_focus = seat_get_focus_inactive_view(seat, &cont->pending.parent->node);
385 } 397 }
386 398
387 seat_set_focus_container(seat, cont_to_focus); 399 seat_set_focus_container(seat, cont_to_focus);
@@ -392,14 +404,14 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
392 // Handle tiling resize via mod 404 // Handle tiling resize via mod
393 bool mod_pressed = modifiers & config->floating_mod; 405 bool mod_pressed = modifiers & config->floating_mod;
394 if (cont && !is_floating_or_child && mod_pressed && 406 if (cont && !is_floating_or_child && mod_pressed &&
395 state == WLR_BUTTON_PRESSED) { 407 state == WL_POINTER_BUTTON_STATE_PRESSED) {
396 uint32_t btn_resize = config->floating_mod_inverse ? 408 uint32_t btn_resize = config->floating_mod_inverse ?
397 BTN_LEFT : BTN_RIGHT; 409 BTN_LEFT : BTN_RIGHT;
398 if (button == btn_resize) { 410 if (button == btn_resize) {
399 edge = 0; 411 edge = 0;
400 edge |= cursor->cursor->x > cont->x + cont->width / 2 ? 412 edge |= cursor->cursor->x > cont->pending.x + cont->pending.width / 2 ?
401 WLR_EDGE_RIGHT : WLR_EDGE_LEFT; 413 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
402 edge |= cursor->cursor->y > cont->y + cont->height / 2 ? 414 edge |= cursor->cursor->y > cont->pending.y + cont->pending.height / 2 ?
403 WLR_EDGE_BOTTOM : WLR_EDGE_TOP; 415 WLR_EDGE_BOTTOM : WLR_EDGE_TOP;
404 416
405 const char *image = NULL; 417 const char *image = NULL;
@@ -419,13 +431,31 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
419 } 431 }
420 } 432 }
421 433
434 // Handle changing focus when clicking on a container
435 if (cont && state == WL_POINTER_BUTTON_STATE_PRESSED) {
436 // Default case: focus the container that was just clicked.
437 node = &cont->node;
438
439 // If the container is a tab/stacked container and the click happened
440 // on a tab, switch to the tab. If the tab contents were already
441 // focused, focus the tab container itself. If the tab container was
442 // already focused, cycle back to focusing the tab contents.
443 if (on_titlebar) {
444 struct sway_container *focus = seat_get_focused_container(seat);
445 if (focus == cont || !container_has_ancestor(focus, cont)) {
446 node = seat_get_focus_inactive(seat, &cont->node);
447 }
448 }
449
450 seat_set_focus(seat, node);
451 transaction_commit_dirty();
452 }
453
422 // Handle beginning floating move 454 // Handle beginning floating move
423 if (cont && is_floating_or_child && !is_fullscreen_or_child && 455 if (cont && is_floating_or_child && !is_fullscreen_or_child &&
424 state == WLR_BUTTON_PRESSED) { 456 state == WL_POINTER_BUTTON_STATE_PRESSED) {
425 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT; 457 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT;
426 if (button == btn_move && (mod_pressed || on_titlebar)) { 458 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)); 459 seatop_begin_move_floating(seat, container_toplevel_ancestor(cont));
430 return; 460 return;
431 } 461 }
@@ -433,9 +463,10 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
433 463
434 // Handle beginning floating resize 464 // Handle beginning floating resize
435 if (cont && is_floating_or_child && !is_fullscreen_or_child && 465 if (cont && is_floating_or_child && !is_fullscreen_or_child &&
436 state == WLR_BUTTON_PRESSED) { 466 state == WL_POINTER_BUTTON_STATE_PRESSED) {
437 // Via border 467 // Via border
438 if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) { 468 if (button == BTN_LEFT && resize_edge != WLR_EDGE_NONE) {
469 seat_set_focus_container(seat, cont);
439 seatop_begin_resize_floating(seat, cont, resize_edge); 470 seatop_begin_resize_floating(seat, cont, resize_edge);
440 return; 471 return;
441 } 472 }
@@ -446,10 +477,11 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
446 if (mod_pressed && button == btn_resize) { 477 if (mod_pressed && button == btn_resize) {
447 struct sway_container *floater = container_toplevel_ancestor(cont); 478 struct sway_container *floater = container_toplevel_ancestor(cont);
448 edge = 0; 479 edge = 0;
449 edge |= cursor->cursor->x > floater->x + floater->width / 2 ? 480 edge |= cursor->cursor->x > floater->pending.x + floater->pending.width / 2 ?
450 WLR_EDGE_RIGHT : WLR_EDGE_LEFT; 481 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
451 edge |= cursor->cursor->y > floater->y + floater->height / 2 ? 482 edge |= cursor->cursor->y > floater->pending.y + floater->pending.height / 2 ?
452 WLR_EDGE_BOTTOM : WLR_EDGE_TOP; 483 WLR_EDGE_BOTTOM : WLR_EDGE_TOP;
484 seat_set_focus_container(seat, floater);
453 seatop_begin_resize_floating(seat, floater, edge); 485 seatop_begin_resize_floating(seat, floater, edge);
454 return; 486 return;
455 } 487 }
@@ -457,53 +489,43 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
457 489
458 // Handle moving a tiling container 490 // Handle moving a tiling container
459 if (config->tiling_drag && (mod_pressed || on_titlebar) && 491 if (config->tiling_drag && (mod_pressed || on_titlebar) &&
460 state == WLR_BUTTON_PRESSED && !is_floating_or_child && 492 state == WL_POINTER_BUTTON_STATE_PRESSED && !is_floating_or_child &&
461 cont && cont->fullscreen_mode == FULLSCREEN_NONE) { 493 cont && cont->pending.fullscreen_mode == FULLSCREEN_NONE) {
462 struct sway_container *focus = seat_get_focused_container(seat); 494 // 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) { 495 if (!mod_pressed && config->tiling_drag_threshold > 0) {
471 seatop_begin_move_tiling_threshold(seat, cont); 496 seatop_begin_move_tiling_threshold(seat, cont);
472 } else { 497 } else {
473 seatop_begin_move_tiling(seat, cont); 498 seatop_begin_move_tiling(seat, cont);
474 } 499 }
500
475 return; 501 return;
476 } 502 }
477 503
478 // Handle mousedown on a container surface 504 // Handle mousedown on a container surface
479 if (surface && cont && state == WLR_BUTTON_PRESSED) { 505 if (surface && cont && state == WL_POINTER_BUTTON_STATE_PRESSED) {
480 seat_set_focus_container(seat, cont); 506 seatop_begin_down(seat, cont, sx, sy);
481 seatop_begin_down(seat, cont, time_msec, sx, sy); 507 seat_pointer_notify_button(seat, time_msec, button, WL_POINTER_BUTTON_STATE_PRESSED);
482 seat_pointer_notify_button(seat, time_msec, button, WLR_BUTTON_PRESSED);
483 return; 508 return;
484 } 509 }
485 510
486 // Handle clicking a container surface or decorations 511 // Handle clicking a container surface or decorations
487 if (cont && state == WLR_BUTTON_PRESSED) { 512 if (cont && state == WL_POINTER_BUTTON_STATE_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); 513 seat_pointer_notify_button(seat, time_msec, button, state);
491 return; 514 return;
492 } 515 }
493 516
494#if HAVE_XWAYLAND 517#if HAVE_XWAYLAND
495 // Handle clicking on xwayland unmanaged view 518 // Handle clicking on xwayland unmanaged view
496 if (surface && wlr_surface_is_xwayland_surface(surface)) { 519 struct wlr_xwayland_surface *xsurface;
497 struct wlr_xwayland_surface *xsurface = 520 if (surface &&
498 wlr_xwayland_surface_from_wlr_surface(surface); 521 (xsurface = wlr_xwayland_surface_try_from_wlr_surface(surface)) &&
499 if (xsurface->override_redirect && 522 xsurface->override_redirect &&
500 wlr_xwayland_or_surface_wants_focus(xsurface)) { 523 wlr_xwayland_or_surface_wants_focus(xsurface)) {
501 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland; 524 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
502 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 525 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
503 seat_set_focus_surface(seat, xsurface->surface, false); 526 seat_set_focus_surface(seat, xsurface->surface, false);
504 seat_pointer_notify_button(seat, time_msec, button, state); 527 transaction_commit_dirty();
505 return; 528 seat_pointer_notify_button(seat, time_msec, button, state);
506 }
507 } 529 }
508#endif 530#endif
509 531
@@ -526,10 +548,26 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
526 if (wlr_output == NULL) { 548 if (wlr_output == NULL) {
527 return; 549 return;
528 } 550 }
551
552 struct wlr_surface *surface = NULL;
553 double sx, sy;
554 node_at_coords(seat, seat->cursor->cursor->x, seat->cursor->cursor->y,
555 &surface, &sx, &sy);
556
557 // Focus topmost layer surface
558 struct wlr_layer_surface_v1 *layer = NULL;
559 if ((layer = toplevel_layer_surface_from_surface(surface)) &&
560 layer->current.keyboard_interactive) {
561 seat_set_focus_layer(seat, layer);
562 transaction_commit_dirty();
563 return;
564 }
565
529 struct sway_output *hovered_output = wlr_output->data; 566 struct sway_output *hovered_output = wlr_output->data;
530 if (focus && hovered_output != node_get_output(focus)) { 567 if (focus && hovered_output != node_get_output(focus)) {
531 struct sway_workspace *ws = output_get_active_workspace(hovered_output); 568 struct sway_workspace *ws = output_get_active_workspace(hovered_output);
532 seat_set_focus(seat, &ws->node); 569 seat_set_focus(seat, &ws->node);
570 transaction_commit_dirty();
533 } 571 }
534 return; 572 return;
535 } 573 }
@@ -541,6 +579,7 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
541 struct sway_output *hovered_output = node_get_output(hovered_node); 579 struct sway_output *hovered_output = node_get_output(hovered_node);
542 if (hovered_output != focused_output) { 580 if (hovered_output != focused_output) {
543 seat_set_focus(seat, seat_get_focus_inactive(seat, hovered_node)); 581 seat_set_focus(seat, seat_get_focus_inactive(seat, hovered_node));
582 transaction_commit_dirty();
544 } 583 }
545 return; 584 return;
546 } 585 }
@@ -556,6 +595,7 @@ static void check_focus_follows_mouse(struct sway_seat *seat,
556 if (hovered_node != e->previous_node || 595 if (hovered_node != e->previous_node ||
557 config->focus_follows_mouse == FOLLOWS_ALWAYS) { 596 config->focus_follows_mouse == FOLLOWS_ALWAYS) {
558 seat_set_focus(seat, hovered_node); 597 seat_set_focus(seat, hovered_node);
598 transaction_commit_dirty();
559 } 599 }
560 } 600 }
561} 601}
@@ -583,12 +623,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
583 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 623 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
584 } 624 }
585 625
586 struct sway_drag_icon *drag_icon; 626 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 627
593 e->previous_node = node; 628 e->previous_node = node;
594} 629}
@@ -618,25 +653,50 @@ static void handle_tablet_tool_motion(struct sway_seat *seat,
618 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool); 653 wlr_tablet_v2_tablet_tool_notify_proximity_out(tool->tablet_v2_tool);
619 } 654 }
620 655
621 struct sway_drag_icon *drag_icon; 656 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 657
628 e->previous_node = node; 658 e->previous_node = node;
629} 659}
630 660
661static void handle_touch_down(struct sway_seat *seat,
662 struct wlr_touch_down_event *event, double lx, double ly) {
663 struct wlr_surface *surface = NULL;
664 struct wlr_seat *wlr_seat = seat->wlr_seat;
665 struct sway_cursor *cursor = seat->cursor;
666 double sx, sy;
667 node_at_coords(seat, seat->touch_x, seat->touch_y, &surface, &sx, &sy);
668
669 if (surface && wlr_surface_accepts_touch(wlr_seat, surface)) {
670 if (seat_is_input_allowed(seat, surface)) {
671 cursor->simulating_pointer_from_touch = false;
672 seatop_begin_touch_down(seat, surface, event, sx, sy, lx, ly);
673 }
674 } else if (!cursor->simulating_pointer_from_touch &&
675 (!surface || seat_is_input_allowed(seat, surface))) {
676 // Fallback to cursor simulation.
677 // The pointer_touch_id state is needed, so drags are not aborted when over
678 // a surface supporting touch and multi touch events don't interfere.
679 cursor->simulating_pointer_from_touch = true;
680 cursor->pointer_touch_id = seat->touch_id;
681 double dx, dy;
682 dx = seat->touch_x - cursor->cursor->x;
683 dy = seat->touch_y - cursor->cursor->y;
684 pointer_motion(cursor, event->time_msec, &event->touch->base, dx, dy,
685 dx, dy);
686 dispatch_cursor_button(cursor, &event->touch->base, event->time_msec,
687 BTN_LEFT, WL_POINTER_BUTTON_STATE_PRESSED);
688 }
689}
690
631/*----------------------------------------\ 691/*----------------------------------------\
632 * Functions used by handle_pointer_axis / 692 * Functions used by handle_pointer_axis /
633 *--------------------------------------*/ 693 *--------------------------------------*/
634 694
635static uint32_t wl_axis_to_button(struct wlr_event_pointer_axis *event) { 695static uint32_t wl_axis_to_button(struct wlr_pointer_axis_event *event) {
636 switch (event->orientation) { 696 switch (event->orientation) {
637 case WLR_AXIS_ORIENTATION_VERTICAL: 697 case WL_POINTER_AXIS_VERTICAL_SCROLL:
638 return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN; 698 return event->delta < 0 ? SWAY_SCROLL_UP : SWAY_SCROLL_DOWN;
639 case WLR_AXIS_ORIENTATION_HORIZONTAL: 699 case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
640 return event->delta < 0 ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT; 700 return event->delta < 0 ? SWAY_SCROLL_LEFT : SWAY_SCROLL_RIGHT;
641 default: 701 default:
642 sway_log(SWAY_DEBUG, "Unknown axis orientation"); 702 sway_log(SWAY_DEBUG, "Unknown axis orientation");
@@ -645,9 +705,9 @@ static uint32_t wl_axis_to_button(struct wlr_event_pointer_axis *event) {
645} 705}
646 706
647static void handle_pointer_axis(struct sway_seat *seat, 707static void handle_pointer_axis(struct sway_seat *seat,
648 struct wlr_event_pointer_axis *event) { 708 struct wlr_pointer_axis_event *event) {
649 struct sway_input_device *input_device = 709 struct sway_input_device *input_device =
650 event->device ? event->device->data : NULL; 710 event->pointer ? event->pointer->base.data : NULL;
651 struct input_config *ic = 711 struct input_config *ic =
652 input_device ? input_device_get_config(input_device) : NULL; 712 input_device ? input_device_get_config(input_device) : NULL;
653 struct sway_cursor *cursor = seat->cursor; 713 struct sway_cursor *cursor = seat->cursor;
@@ -664,7 +724,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
664 bool on_border = edge != WLR_EDGE_NONE; 724 bool on_border = edge != WLR_EDGE_NONE;
665 bool on_titlebar = cont && !on_border && !surface; 725 bool on_titlebar = cont && !on_border && !surface;
666 bool on_titlebar_border = cont && on_border && 726 bool on_titlebar_border = cont && on_border &&
667 cursor->cursor->y < cont->content_y; 727 cursor->cursor->y < cont->pending.content_y;
668 bool on_contents = cont && !on_border && surface; 728 bool on_contents = cont && !on_border && surface;
669 bool on_workspace = node && node->type == N_WORKSPACE; 729 bool on_workspace = node && node->type == N_WORKSPACE;
670 float scroll_factor = 730 float scroll_factor =
@@ -693,6 +753,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
693 753
694 // Scrolling on a tabbed or stacked title bar (handled as press event) 754 // Scrolling on a tabbed or stacked title bar (handled as press event)
695 if (!handled && (on_titlebar || on_titlebar_border)) { 755 if (!handled && (on_titlebar || on_titlebar_border)) {
756 struct sway_node *new_focus;
696 enum sway_container_layout layout = container_parent_layout(cont); 757 enum sway_container_layout layout = container_parent_layout(cont);
697 if (layout == L_TABBED || layout == L_STACKED) { 758 if (layout == L_TABBED || layout == L_STACKED) {
698 struct sway_node *tabcontainer = node_get_parent(node); 759 struct sway_node *tabcontainer = node_get_parent(node);
@@ -700,7 +761,7 @@ static void handle_pointer_axis(struct sway_seat *seat,
700 seat_get_active_tiling_child(seat, tabcontainer); 761 seat_get_active_tiling_child(seat, tabcontainer);
701 list_t *siblings = container_get_siblings(cont); 762 list_t *siblings = container_get_siblings(cont);
702 int desired = list_find(siblings, active->sway_container) + 763 int desired = list_find(siblings, active->sway_container) +
703 round(scroll_factor * event->delta_discrete); 764 roundf(scroll_factor * event->delta_discrete / WLR_POINTER_AXIS_DISCRETE_STEP);
704 if (desired < 0) { 765 if (desired < 0) {
705 desired = 0; 766 desired = 0;
706 } else if (desired >= siblings->length) { 767 } else if (desired >= siblings->length) {
@@ -709,13 +770,16 @@ static void handle_pointer_axis(struct sway_seat *seat,
709 770
710 struct sway_container *new_sibling_con = siblings->items[desired]; 771 struct sway_container *new_sibling_con = siblings->items[desired];
711 struct sway_node *new_sibling = &new_sibling_con->node; 772 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 773 // Use the focused child of the tabbed/stacked container, not the
715 // container the user scrolled on. 774 // container the user scrolled on.
716 seat_set_focus(seat, new_focus); 775 new_focus = seat_get_focus_inactive(seat, new_sibling);
717 handled = true; 776 } else {
777 new_focus = seat_get_focus_inactive(seat, &cont->node);
718 } 778 }
779
780 seat_set_focus(seat, new_focus);
781 transaction_commit_dirty();
782 handled = true;
719 } 783 }
720 784
721 // Handle mouse bindings - x11 mouse buttons 4-7 - release event 785 // Handle mouse bindings - x11 mouse buttons 4-7 - release event
@@ -731,8 +795,307 @@ static void handle_pointer_axis(struct sway_seat *seat,
731 795
732 if (!handled) { 796 if (!handled) {
733 wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec, 797 wlr_seat_pointer_notify_axis(cursor->seat->wlr_seat, event->time_msec,
734 event->orientation, scroll_factor * event->delta, 798 event->orientation, scroll_factor * event->delta,
735 round(scroll_factor * event->delta_discrete), event->source); 799 roundf(scroll_factor * event->delta_discrete), event->source,
800 event->relative_direction);
801 }
802}
803
804/*------------------------------------\
805 * Functions used by gesture support /
806 *----------------------------------*/
807
808/**
809 * Check gesture binding for a specific gesture type and finger count.
810 * Returns true if binding is present, false otherwise
811 */
812static bool gesture_binding_check(list_t *bindings, enum gesture_type type,
813 uint8_t fingers, struct sway_input_device *device) {
814 char *input =
815 device ? input_device_get_identifier(device->wlr_device) : strdup("*");
816
817 for (int i = 0; i < bindings->length; ++i) {
818 struct sway_gesture_binding *binding = bindings->items[i];
819
820 // Check type and finger count
821 if (!gesture_check(&binding->gesture, type, fingers)) {
822 continue;
823 }
824
825 // Check that input matches
826 if (strcmp(binding->input, "*") != 0 &&
827 strcmp(binding->input, input) != 0) {
828 continue;
829 }
830
831 free(input);
832
833 return true;
834 }
835
836 free(input);
837
838 return false;
839}
840
841/**
842 * Return the gesture binding which matches gesture type, finger count
843 * and direction, otherwise return null.
844 */
845static struct sway_gesture_binding* gesture_binding_match(
846 list_t *bindings, struct gesture *gesture, const char *input) {
847 struct sway_gesture_binding *current = NULL;
848
849 // Find best matching binding
850 for (int i = 0; i < bindings->length; ++i) {
851 struct sway_gesture_binding *binding = bindings->items[i];
852 bool exact = binding->flags & BINDING_EXACT;
853
854 // Check gesture matching
855 if (!gesture_match(&binding->gesture, gesture, exact)) {
856 continue;
857 }
858
859 // Check input matching
860 if (strcmp(binding->input, "*") != 0 &&
861 strcmp(binding->input, input) != 0) {
862 continue;
863 }
864
865 // If we already have a match ...
866 if (current) {
867 // ... check if input matching is equivalent
868 if (strcmp(current->input, binding->input) == 0) {
869
870 // ... - do not override an exact binding
871 if (!exact && current->flags & BINDING_EXACT) {
872 continue;
873 }
874
875 // ... - and ensure direction matching is better or equal
876 if (gesture_compare(&current->gesture, &binding->gesture) > 0) {
877 continue;
878 }
879 } else if (strcmp(binding->input, "*") == 0) {
880 // ... do not accept worse input match
881 continue;
882 }
883 }
884
885 // Accept newer or better match
886 current = binding;
887
888 // If exact binding and input is found, quit search
889 if (strcmp(current->input, input) == 0 &&
890 gesture_compare(&current->gesture, gesture) == 0) {
891 break;
892 }
893 } // for all gesture bindings
894
895 return current;
896}
897
898// Wrapper around gesture_tracker_end to use tracker with sway bindings
899static struct sway_gesture_binding* gesture_tracker_end_and_match(
900 struct gesture_tracker *tracker, struct sway_input_device* device) {
901 // Determine name of input that received gesture
902 char *input = device
903 ? input_device_get_identifier(device->wlr_device)
904 : strdup("*");
905
906 // Match tracking result to binding
907 struct gesture *gesture = gesture_tracker_end(tracker);
908 struct sway_gesture_binding *binding = gesture_binding_match(
909 config->current_mode->gesture_bindings, gesture, input);
910 free(gesture);
911 free(input);
912
913 return binding;
914}
915
916// Small wrapper around seat_execute_command to work on gesture bindings
917static void gesture_binding_execute(struct sway_seat *seat,
918 struct sway_gesture_binding *binding) {
919 struct sway_binding *dummy_binding =
920 calloc(1, sizeof(struct sway_binding));
921 dummy_binding->type = BINDING_GESTURE;
922 dummy_binding->command = binding->command;
923
924 char *description = gesture_to_string(&binding->gesture);
925 sway_log(SWAY_DEBUG, "executing gesture binding: %s", description);
926 free(description);
927
928 seat_execute_command(seat, dummy_binding);
929
930 free(dummy_binding);
931}
932
933static void handle_hold_begin(struct sway_seat *seat,
934 struct wlr_pointer_hold_begin_event *event) {
935 // Start tracking gesture if there is a matching binding ...
936 struct sway_input_device *device =
937 event->pointer ? event->pointer->base.data : NULL;
938 list_t *bindings = config->current_mode->gesture_bindings;
939 if (gesture_binding_check(bindings, GESTURE_TYPE_HOLD, event->fingers, device)) {
940 struct seatop_default_event *seatop = seat->seatop_data;
941 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_HOLD, event->fingers);
942 } else {
943 // ... otherwise forward to client
944 struct sway_cursor *cursor = seat->cursor;
945 wlr_pointer_gestures_v1_send_hold_begin(
946 server.input->pointer_gestures, cursor->seat->wlr_seat,
947 event->time_msec, event->fingers);
948 }
949}
950
951static void handle_hold_end(struct sway_seat *seat,
952 struct wlr_pointer_hold_end_event *event) {
953 // Ensure that gesture is being tracked and was not cancelled
954 struct seatop_default_event *seatop = seat->seatop_data;
955 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_HOLD)) {
956 struct sway_cursor *cursor = seat->cursor;
957 wlr_pointer_gestures_v1_send_hold_end(
958 server.input->pointer_gestures, cursor->seat->wlr_seat,
959 event->time_msec, event->cancelled);
960 return;
961 }
962 if (event->cancelled) {
963 gesture_tracker_cancel(&seatop->gestures);
964 return;
965 }
966
967 // End gesture tracking and execute matched binding
968 struct sway_input_device *device =
969 event->pointer ? event->pointer->base.data : NULL;
970 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
971 &seatop->gestures, device);
972
973 if (binding) {
974 gesture_binding_execute(seat, binding);
975 }
976}
977
978static void handle_pinch_begin(struct sway_seat *seat,
979 struct wlr_pointer_pinch_begin_event *event) {
980 // Start tracking gesture if there is a matching binding ...
981 struct sway_input_device *device =
982 event->pointer ? event->pointer->base.data : NULL;
983 list_t *bindings = config->current_mode->gesture_bindings;
984 if (gesture_binding_check(bindings, GESTURE_TYPE_PINCH, event->fingers, device)) {
985 struct seatop_default_event *seatop = seat->seatop_data;
986 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_PINCH, event->fingers);
987 } else {
988 // ... otherwise forward to client
989 struct sway_cursor *cursor = seat->cursor;
990 wlr_pointer_gestures_v1_send_pinch_begin(
991 server.input->pointer_gestures, cursor->seat->wlr_seat,
992 event->time_msec, event->fingers);
993 }
994}
995
996static void handle_pinch_update(struct sway_seat *seat,
997 struct wlr_pointer_pinch_update_event *event) {
998 // Update any ongoing tracking ...
999 struct seatop_default_event *seatop = seat->seatop_data;
1000 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
1001 gesture_tracker_update(&seatop->gestures, event->dx, event->dy,
1002 event->scale, event->rotation);
1003 } else {
1004 // ... otherwise forward to client
1005 struct sway_cursor *cursor = seat->cursor;
1006 wlr_pointer_gestures_v1_send_pinch_update(
1007 server.input->pointer_gestures,
1008 cursor->seat->wlr_seat,
1009 event->time_msec, event->dx, event->dy,
1010 event->scale, event->rotation);
1011 }
1012}
1013
1014static void handle_pinch_end(struct sway_seat *seat,
1015 struct wlr_pointer_pinch_end_event *event) {
1016 // Ensure that gesture is being tracked and was not cancelled
1017 struct seatop_default_event *seatop = seat->seatop_data;
1018 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_PINCH)) {
1019 struct sway_cursor *cursor = seat->cursor;
1020 wlr_pointer_gestures_v1_send_pinch_end(
1021 server.input->pointer_gestures, cursor->seat->wlr_seat,
1022 event->time_msec, event->cancelled);
1023 return;
1024 }
1025 if (event->cancelled) {
1026 gesture_tracker_cancel(&seatop->gestures);
1027 return;
1028 }
1029
1030 // End gesture tracking and execute matched binding
1031 struct sway_input_device *device =
1032 event->pointer ? event->pointer->base.data : NULL;
1033 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
1034 &seatop->gestures, device);
1035
1036 if (binding) {
1037 gesture_binding_execute(seat, binding);
1038 }
1039}
1040
1041static void handle_swipe_begin(struct sway_seat *seat,
1042 struct wlr_pointer_swipe_begin_event *event) {
1043 // Start tracking gesture if there is a matching binding ...
1044 struct sway_input_device *device =
1045 event->pointer ? event->pointer->base.data : NULL;
1046 list_t *bindings = config->current_mode->gesture_bindings;
1047 if (gesture_binding_check(bindings, GESTURE_TYPE_SWIPE, event->fingers, device)) {
1048 struct seatop_default_event *seatop = seat->seatop_data;
1049 gesture_tracker_begin(&seatop->gestures, GESTURE_TYPE_SWIPE, event->fingers);
1050 } else {
1051 // ... otherwise forward to client
1052 struct sway_cursor *cursor = seat->cursor;
1053 wlr_pointer_gestures_v1_send_swipe_begin(
1054 server.input->pointer_gestures, cursor->seat->wlr_seat,
1055 event->time_msec, event->fingers);
1056 }
1057}
1058
1059static void handle_swipe_update(struct sway_seat *seat,
1060 struct wlr_pointer_swipe_update_event *event) {
1061
1062 // Update any ongoing tracking ...
1063 struct seatop_default_event *seatop = seat->seatop_data;
1064 if (gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1065 gesture_tracker_update(&seatop->gestures,
1066 event->dx, event->dy, NAN, NAN);
1067 } else {
1068 // ... otherwise forward to client
1069 struct sway_cursor *cursor = seat->cursor;
1070 wlr_pointer_gestures_v1_send_swipe_update(
1071 server.input->pointer_gestures, cursor->seat->wlr_seat,
1072 event->time_msec, event->dx, event->dy);
1073 }
1074}
1075
1076static void handle_swipe_end(struct sway_seat *seat,
1077 struct wlr_pointer_swipe_end_event *event) {
1078 // Ensure gesture is being tracked and was not cancelled
1079 struct seatop_default_event *seatop = seat->seatop_data;
1080 if (!gesture_tracker_check(&seatop->gestures, GESTURE_TYPE_SWIPE)) {
1081 struct sway_cursor *cursor = seat->cursor;
1082 wlr_pointer_gestures_v1_send_swipe_end(server.input->pointer_gestures,
1083 cursor->seat->wlr_seat, event->time_msec, event->cancelled);
1084 return;
1085 }
1086 if (event->cancelled) {
1087 gesture_tracker_cancel(&seatop->gestures);
1088 return;
1089 }
1090
1091 // End gesture tracking and execute matched binding
1092 struct sway_input_device *device =
1093 event->pointer ? event->pointer->base.data : NULL;
1094 struct sway_gesture_binding *binding = gesture_tracker_end_and_match(
1095 &seatop->gestures, device);
1096
1097 if (binding) {
1098 gesture_binding_execute(seat, binding);
736 } 1099 }
737} 1100}
738 1101
@@ -765,6 +1128,15 @@ static const struct sway_seatop_impl seatop_impl = {
765 .pointer_axis = handle_pointer_axis, 1128 .pointer_axis = handle_pointer_axis,
766 .tablet_tool_tip = handle_tablet_tool_tip, 1129 .tablet_tool_tip = handle_tablet_tool_tip,
767 .tablet_tool_motion = handle_tablet_tool_motion, 1130 .tablet_tool_motion = handle_tablet_tool_motion,
1131 .hold_begin = handle_hold_begin,
1132 .hold_end = handle_hold_end,
1133 .pinch_begin = handle_pinch_begin,
1134 .pinch_update = handle_pinch_update,
1135 .pinch_end = handle_pinch_end,
1136 .swipe_begin = handle_swipe_begin,
1137 .swipe_update = handle_swipe_update,
1138 .swipe_end = handle_swipe_end,
1139 .touch_down = handle_touch_down,
768 .rebase = handle_rebase, 1140 .rebase = handle_rebase,
769 .allow_set_cursor = true, 1141 .allow_set_cursor = true,
770}; 1142};
@@ -775,8 +1147,8 @@ void seatop_begin_default(struct sway_seat *seat) {
775 struct seatop_default_event *e = 1147 struct seatop_default_event *e =
776 calloc(1, sizeof(struct seatop_default_event)); 1148 calloc(1, sizeof(struct seatop_default_event));
777 sway_assert(e, "Unable to allocate seatop_default_event"); 1149 sway_assert(e, "Unable to allocate seatop_default_event");
1150
778 seat->seatop_impl = &seatop_impl; 1151 seat->seatop_impl = &seatop_impl;
779 seat->seatop_data = e; 1152 seat->seatop_data = e;
780
781 seatop_rebase(seat, 0); 1153 seatop_rebase(seat, 0);
782} 1154}
diff --git a/sway/input/seatop_down.c b/sway/input/seatop_down.c
index 17f619e3..340e334b 100644
--- a/sway/input/seatop_down.c
+++ b/sway/input/seatop_down.c
@@ -1,22 +1,138 @@
1#define _POSIX_C_SOURCE 200809L
2#include <float.h> 1#include <float.h>
3#include <wlr/types/wlr_cursor.h> 2#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_tablet_v2.h> 3#include <wlr/types/wlr_tablet_v2.h>
4#include <wlr/types/wlr_touch.h>
5#include "sway/input/cursor.h" 5#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 6#include "sway/input/seat.h"
7#include "sway/tree/view.h" 7#include "sway/tree/view.h"
8#include "sway/desktop/transaction.h"
8#include "log.h" 9#include "log.h"
9 10
11struct seatop_touch_point_event {
12 double ref_lx, ref_ly; // touch's x/y at start of op
13 double ref_con_lx, ref_con_ly; // container's x/y at start of op
14 int32_t touch_id;
15 struct wl_list link;
16};
17
10struct seatop_down_event { 18struct seatop_down_event {
11 struct sway_container *con; 19 struct sway_container *con;
20 struct sway_seat *seat;
21 struct wl_listener surface_destroy;
22 struct wlr_surface *surface;
12 double ref_lx, ref_ly; // cursor's x/y at start of op 23 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 24 double ref_con_lx, ref_con_ly; // container's x/y at start of op
25 struct wl_list point_events; // seatop_touch_point_event::link
14}; 26};
15 27
28static void handle_touch_motion(struct sway_seat *seat,
29 struct wlr_touch_motion_event *event, double lx, double ly) {
30 struct seatop_down_event *e = seat->seatop_data;
31
32 struct seatop_touch_point_event *point_event;
33 bool found = false;
34 wl_list_for_each(point_event, &e->point_events, link) {
35 if (point_event->touch_id == event->touch_id) {
36 found = true;
37 break;
38 }
39 }
40 if (!found) {
41 return; // Probably not a point_event from this seatop_down
42 }
43
44 double moved_x = lx - point_event->ref_lx;
45 double moved_y = ly - point_event->ref_ly;
46 double sx = point_event->ref_con_lx + moved_x;
47 double sy = point_event->ref_con_ly + moved_y;
48
49 wlr_seat_touch_notify_motion(seat->wlr_seat, event->time_msec,
50 event->touch_id, sx, sy);
51}
52
53static void handle_touch_up(struct sway_seat *seat,
54 struct wlr_touch_up_event *event) {
55 struct seatop_down_event *e = seat->seatop_data;
56 struct seatop_touch_point_event *point_event, *tmp;
57
58 wl_list_for_each_safe(point_event, tmp, &e->point_events, link) {
59 if (point_event->touch_id == event->touch_id) {
60 wl_list_remove(&point_event->link);
61 free(point_event);
62 break;
63 }
64 }
65
66 wlr_seat_touch_notify_up(seat->wlr_seat, event->time_msec, event->touch_id);
67
68 if (wl_list_empty(&e->point_events)) {
69 seatop_begin_default(seat);
70 }
71}
72
73static void handle_touch_down(struct sway_seat *seat,
74 struct wlr_touch_down_event *event, double lx, double ly) {
75 struct seatop_down_event *e = seat->seatop_data;
76 double sx, sy;
77 struct wlr_surface *surface = NULL;
78 struct sway_node *focused_node = node_at_coords(seat, seat->touch_x,
79 seat->touch_y, &surface, &sx, &sy);
80
81 if (!surface || surface != e->surface) { // Must start from the initial surface
82 return;
83 }
84
85 struct seatop_touch_point_event *point_event =
86 calloc(1, sizeof(struct seatop_touch_point_event));
87 if (!sway_assert(point_event, "Unable to allocate point_event")) {
88 return;
89 }
90 point_event->touch_id = event->touch_id;
91 point_event->ref_lx = lx;
92 point_event->ref_ly = ly;
93 point_event->ref_con_lx = sx;
94 point_event->ref_con_ly = sy;
95
96 wl_list_insert(&e->point_events, &point_event->link);
97
98 wlr_seat_touch_notify_down(seat->wlr_seat, surface, event->time_msec,
99 event->touch_id, sx, sy);
100
101 if (focused_node) {
102 seat_set_focus(seat, focused_node);
103 }
104}
105
106static void handle_touch_cancel(struct sway_seat *seat,
107 struct wlr_touch_cancel_event *event) {
108 struct seatop_down_event *e = seat->seatop_data;
109 struct seatop_touch_point_event *point_event, *tmp;
110
111 wl_list_for_each_safe(point_event, tmp, &e->point_events, link) {
112 if (point_event->touch_id == event->touch_id) {
113 wl_list_remove(&point_event->link);
114 free(point_event);
115 break;
116 }
117 }
118
119 if (e->surface) {
120 struct wl_client *client = wl_resource_get_client(e->surface->resource);
121 struct wlr_seat_client *seat_client = wlr_seat_client_for_wl_client(seat->wlr_seat, client);
122 if (seat_client != NULL) {
123 wlr_seat_touch_notify_cancel(seat->wlr_seat, seat_client);
124 }
125 }
126
127 if (wl_list_empty(&e->point_events)) {
128 seatop_begin_default(seat);
129 }
130}
131
16static void handle_pointer_axis(struct sway_seat *seat, 132static void handle_pointer_axis(struct sway_seat *seat,
17 struct wlr_event_pointer_axis *event) { 133 struct wlr_pointer_axis_event *event) {
18 struct sway_input_device *input_device = 134 struct sway_input_device *input_device =
19 event->device ? event->device->data : NULL; 135 event->pointer ? event->pointer->base.data : NULL;
20 struct input_config *ic = 136 struct input_config *ic =
21 input_device ? input_device_get_config(input_device) : NULL; 137 input_device ? input_device_get_config(input_device) : NULL;
22 float scroll_factor = 138 float scroll_factor =
@@ -24,12 +140,13 @@ static void handle_pointer_axis(struct sway_seat *seat,
24 140
25 wlr_seat_pointer_notify_axis(seat->wlr_seat, event->time_msec, 141 wlr_seat_pointer_notify_axis(seat->wlr_seat, event->time_msec,
26 event->orientation, scroll_factor * event->delta, 142 event->orientation, scroll_factor * event->delta,
27 round(scroll_factor * event->delta_discrete), event->source); 143 roundf(scroll_factor * event->delta_discrete), event->source,
144 event->relative_direction);
28} 145}
29 146
30static void handle_button(struct sway_seat *seat, uint32_t time_msec, 147static void handle_button(struct sway_seat *seat, uint32_t time_msec,
31 struct wlr_input_device *device, uint32_t button, 148 struct wlr_input_device *device, uint32_t button,
32 enum wlr_button_state state) { 149 enum wl_pointer_button_state state) {
33 seat_pointer_notify_button(seat, time_msec, button, state); 150 seat_pointer_notify_button(seat, time_msec, button, state);
34 151
35 if (seat->cursor->pressed_button_count == 0) { 152 if (seat->cursor->pressed_button_count == 0) {
@@ -39,8 +156,7 @@ static void handle_button(struct sway_seat *seat, uint32_t time_msec,
39 156
40static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 157static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
41 struct seatop_down_event *e = seat->seatop_data; 158 struct seatop_down_event *e = seat->seatop_data;
42 struct sway_container *con = e->con; 159 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; 160 double moved_x = seat->cursor->cursor->x - e->ref_lx;
45 double moved_y = seat->cursor->cursor->y - e->ref_ly; 161 double moved_y = seat->cursor->cursor->y - e->ref_ly;
46 double sx = e->ref_con_lx + moved_x; 162 double sx = e->ref_con_lx + moved_x;
@@ -61,8 +177,7 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
61static void handle_tablet_tool_motion(struct sway_seat *seat, 177static void handle_tablet_tool_motion(struct sway_seat *seat,
62 struct sway_tablet_tool *tool, uint32_t time_msec) { 178 struct sway_tablet_tool *tool, uint32_t time_msec) {
63 struct seatop_down_event *e = seat->seatop_data; 179 struct seatop_down_event *e = seat->seatop_data;
64 struct sway_container *con = e->con; 180 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; 181 double moved_x = seat->cursor->cursor->x - e->ref_lx;
67 double moved_y = seat->cursor->cursor->y - e->ref_ly; 182 double moved_y = seat->cursor->cursor->y - e->ref_ly;
68 double sx = e->ref_con_lx + moved_x; 183 double sx = e->ref_con_lx + moved_x;
@@ -71,6 +186,14 @@ static void handle_tablet_tool_motion(struct sway_seat *seat,
71 } 186 }
72} 187}
73 188
189static void handle_destroy(struct wl_listener *listener, void *data) {
190 struct seatop_down_event *e =
191 wl_container_of(listener, e, surface_destroy);
192 if (e) {
193 seatop_begin_default(e->seat);
194 }
195}
196
74static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 197static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
75 struct seatop_down_event *e = seat->seatop_data; 198 struct seatop_down_event *e = seat->seatop_data;
76 if (e->con == con) { 199 if (e->con == con) {
@@ -78,33 +201,63 @@ static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
78 } 201 }
79} 202}
80 203
204static void handle_end(struct sway_seat *seat) {
205 struct seatop_down_event *e = seat->seatop_data;
206 wl_list_remove(&e->surface_destroy.link);
207}
208
81static const struct sway_seatop_impl seatop_impl = { 209static const struct sway_seatop_impl seatop_impl = {
82 .button = handle_button, 210 .button = handle_button,
83 .pointer_motion = handle_pointer_motion, 211 .pointer_motion = handle_pointer_motion,
84 .pointer_axis = handle_pointer_axis, 212 .pointer_axis = handle_pointer_axis,
85 .tablet_tool_tip = handle_tablet_tool_tip, 213 .tablet_tool_tip = handle_tablet_tool_tip,
86 .tablet_tool_motion = handle_tablet_tool_motion, 214 .tablet_tool_motion = handle_tablet_tool_motion,
215 .touch_motion = handle_touch_motion,
216 .touch_up = handle_touch_up,
217 .touch_down = handle_touch_down,
218 .touch_cancel = handle_touch_cancel,
87 .unref = handle_unref, 219 .unref = handle_unref,
220 .end = handle_end,
88 .allow_set_cursor = true, 221 .allow_set_cursor = true,
89}; 222};
90 223
91void seatop_begin_down(struct sway_seat *seat, struct sway_container *con, 224void seatop_begin_down(struct sway_seat *seat, struct sway_container *con,
92 uint32_t time_msec, int sx, int sy) { 225 double sx, double sy) {
226 seatop_begin_down_on_surface(seat, con->view->surface, sx, sy);
227 struct seatop_down_event *e = seat->seatop_data;
228 e->con = con;
229
230 container_raise_floating(con);
231 transaction_commit_dirty();
232}
233
234void seatop_begin_touch_down(struct sway_seat *seat,
235 struct wlr_surface *surface, struct wlr_touch_down_event *event,
236 double sx, double sy, double lx, double ly) {
237 seatop_begin_down_on_surface(seat, surface, sx, sy);
238 handle_touch_down(seat, event, lx, ly);
239}
240
241void seatop_begin_down_on_surface(struct sway_seat *seat,
242 struct wlr_surface *surface, double sx, double sy) {
93 seatop_end(seat); 243 seatop_end(seat);
94 244
95 struct seatop_down_event *e = 245 struct seatop_down_event *e =
96 calloc(1, sizeof(struct seatop_down_event)); 246 calloc(1, sizeof(struct seatop_down_event));
97 if (!e) { 247 if (!sway_assert(e, "Unable to allocate e")) {
98 return; 248 return;
99 } 249 }
100 e->con = con; 250 e->con = NULL;
251 e->seat = seat;
252 e->surface = surface;
253 wl_signal_add(&e->surface->events.destroy, &e->surface_destroy);
254 e->surface_destroy.notify = handle_destroy;
101 e->ref_lx = seat->cursor->cursor->x; 255 e->ref_lx = seat->cursor->cursor->x;
102 e->ref_ly = seat->cursor->cursor->y; 256 e->ref_ly = seat->cursor->cursor->y;
103 e->ref_con_lx = sx; 257 e->ref_con_lx = sx;
104 e->ref_con_ly = sy; 258 e->ref_con_ly = sy;
259 wl_list_init(&e->point_events);
105 260
106 seat->seatop_impl = &seatop_impl; 261 seat->seatop_impl = &seatop_impl;
107 seat->seatop_data = e; 262 seat->seatop_data = e;
108
109 container_raise_floating(con);
110} 263}
diff --git a/sway/input/seatop_move_floating.c b/sway/input/seatop_move_floating.c
index 7f501fc9..83668d88 100644
--- a/sway/input/seatop_move_floating.c
+++ b/sway/input/seatop_move_floating.c
@@ -1,6 +1,5 @@
1#define _POSIX_C_SOURCE 200809L
2#include <wlr/types/wlr_cursor.h> 1#include <wlr/types/wlr_cursor.h>
3#include "sway/desktop.h" 2#include "sway/desktop/transaction.h"
4#include "sway/input/cursor.h" 3#include "sway/input/cursor.h"
5#include "sway/input/seat.h" 4#include "sway/input/seat.h"
6 5
@@ -14,14 +13,15 @@ static void finalize_move(struct sway_seat *seat) {
14 13
15 // We "move" the container to its own location 14 // We "move" the container to its own location
16 // so it discovers its output again. 15 // so it discovers its output again.
17 container_floating_move_to(e->con, e->con->x, e->con->y); 16 container_floating_move_to(e->con, e->con->pending.x, e->con->pending.y);
17 transaction_commit_dirty();
18 18
19 seatop_begin_default(seat); 19 seatop_begin_default(seat);
20} 20}
21 21
22static void handle_button(struct sway_seat *seat, uint32_t time_msec, 22static void handle_button(struct sway_seat *seat, uint32_t time_msec,
23 struct wlr_input_device *device, uint32_t button, 23 struct wlr_input_device *device, uint32_t button,
24 enum wlr_button_state state) { 24 enum wl_pointer_button_state state) {
25 if (seat->cursor->pressed_button_count == 0) { 25 if (seat->cursor->pressed_button_count == 0) {
26 finalize_move(seat); 26 finalize_move(seat);
27 } 27 }
@@ -37,9 +37,8 @@ static void handle_tablet_tool_tip(struct sway_seat *seat,
37static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 37static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
38 struct seatop_move_floating_event *e = seat->seatop_data; 38 struct seatop_move_floating_event *e = seat->seatop_data;
39 struct wlr_cursor *cursor = seat->cursor->cursor; 39 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); 40 container_floating_move_to(e->con, cursor->x - e->dx, cursor->y - e->dy);
42 desktop_damage_whole_container(e->con); 41 transaction_commit_dirty();
43} 42}
44 43
45static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 44static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -67,13 +66,14 @@ void seatop_begin_move_floating(struct sway_seat *seat,
67 return; 66 return;
68 } 67 }
69 e->con = con; 68 e->con = con;
70 e->dx = cursor->cursor->x - con->x; 69 e->dx = cursor->cursor->x - con->pending.x;
71 e->dy = cursor->cursor->y - con->y; 70 e->dy = cursor->cursor->y - con->pending.y;
72 71
73 seat->seatop_impl = &seatop_impl; 72 seat->seatop_impl = &seatop_impl;
74 seat->seatop_data = e; 73 seat->seatop_data = e;
75 74
76 container_raise_floating(con); 75 container_raise_floating(con);
76 transaction_commit_dirty();
77 77
78 cursor_set_image(cursor, "grab", NULL); 78 cursor_set_image(cursor, "grab", NULL);
79 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 79 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..c525b77a 100644
--- a/sway/input/seatop_move_tiling.c
+++ b/sway/input/seatop_move_tiling.c
@@ -1,8 +1,7 @@
1#define _POSIX_C_SOURCE 200809L
2#include <limits.h> 1#include <limits.h>
3#include <wlr/types/wlr_cursor.h> 2#include <wlr/types/wlr_cursor.h>
4#include <wlr/util/edges.h> 3#include <wlr/util/edges.h>
5#include "sway/desktop.h" 4#include "sway/desktop/transaction.h"
6#include "sway/input/cursor.h" 5#include "sway/input/cursor.h"
7#include "sway/input/seat.h" 6#include "sway/input/seat.h"
8#include "sway/ipc-server.h" 7#include "sway/ipc-server.h"
@@ -15,31 +14,25 @@
15// Thickness of the dropzone when dragging to the edge of a layout container 14// Thickness of the dropzone when dragging to the edge of a layout container
16#define DROP_LAYOUT_BORDER 30 15#define DROP_LAYOUT_BORDER 30
17 16
17// Thickness of indicator when dropping onto a titlebar. This should be a
18// multiple of 2.
19#define DROP_SPLIT_INDICATOR 10
20
18struct seatop_move_tiling_event { 21struct seatop_move_tiling_event {
19 struct sway_container *con; 22 struct sway_container *con;
20 struct sway_node *target_node; 23 struct sway_node *target_node;
21 enum wlr_edges target_edge; 24 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 25 double ref_lx, ref_ly; // cursor's x/y at start of op
24 bool threshold_reached; 26 bool threshold_reached;
27 bool split_target;
28 bool insert_after_target;
29 struct wlr_scene_rect *indicator_rect;
25}; 30};
26 31
27static void handle_render(struct sway_seat *seat, 32static 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; 33 struct seatop_move_tiling_event *e = seat->seatop_data;
30 if (!e->threshold_reached) { 34 wlr_scene_node_destroy(&e->indicator_rect->node);
31 return; 35 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} 36}
44 37
45static void handle_motion_prethreshold(struct sway_seat *seat) { 38static void handle_motion_prethreshold(struct sway_seat *seat) {
@@ -60,6 +53,7 @@ static void handle_motion_prethreshold(struct sway_seat *seat) {
60 53
61 // If the threshold has been exceeded, start the actual drag 54 // If the threshold has been exceeded, start the actual drag
62 if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) { 55 if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) {
56 wlr_scene_node_set_enabled(&e->indicator_rect->node, true);
63 e->threshold_reached = true; 57 e->threshold_reached = true;
64 cursor_set_image(seat->cursor, "grab", NULL); 58 cursor_set_image(seat->cursor, "grab", NULL);
65 } 59 }
@@ -91,15 +85,86 @@ static void resize_box(struct wlr_box *box, enum wlr_edges edge,
91 } 85 }
92} 86}
93 87
88static void split_border(double pos, int offset, int len, int n_children,
89 int avoid, int *out_pos, bool *out_after) {
90 int region = 2 * n_children * (pos - offset) / len;
91 // If the cursor is over the right side of a left-adjacent titlebar, or the
92 // left side of a right-adjacent titlebar, it's position when dropped will
93 // be the same. To avoid this, shift the region for adjacent containers.
94 if (avoid >= 0) {
95 if (region == 2 * avoid - 1 || region == 2 * avoid) {
96 region--;
97 } else if (region == 2 * avoid + 1 || region == 2 * avoid + 2) {
98 region++;
99 }
100 }
101
102 int child_index = (region + 1) / 2;
103 *out_after = region % 2;
104 // When dropping at the beginning or end of a container, show the drop
105 // region within the container boundary, otherwise show it on top of the
106 // border between two titlebars.
107 if (child_index == 0) {
108 *out_pos = offset;
109 } else if (child_index == n_children) {
110 *out_pos = offset + len - DROP_SPLIT_INDICATOR;
111 } else {
112 *out_pos = offset + child_index * len / n_children -
113 DROP_SPLIT_INDICATOR / 2;
114 }
115}
116
117static bool split_titlebar(struct sway_node *node, struct sway_container *avoid,
118 struct wlr_cursor *cursor, struct wlr_box *title_box, bool *after) {
119 struct sway_container *con = node->sway_container;
120 struct sway_node *parent = &con->pending.parent->node;
121 int title_height = container_titlebar_height();
122 struct wlr_box box;
123 int n_children, avoid_index;
124 enum sway_container_layout layout =
125 parent ? node_get_layout(parent) : L_NONE;
126 if (layout == L_TABBED || layout == L_STACKED) {
127 node_get_box(parent, &box);
128 n_children = node_get_children(parent)->length;
129 avoid_index = list_find(node_get_children(parent), avoid);
130 } else {
131 node_get_box(node, &box);
132 n_children = 1;
133 avoid_index = -1;
134 }
135 if (layout == L_STACKED && cursor->y < box.y + title_height * n_children) {
136 // Drop into stacked titlebars.
137 title_box->width = box.width;
138 title_box->height = DROP_SPLIT_INDICATOR;
139 title_box->x = box.x;
140 split_border(cursor->y, box.y, title_height * n_children,
141 n_children, avoid_index, &title_box->y, after);
142 return true;
143 } else if (layout != L_STACKED && cursor->y < box.y + title_height) {
144 // Drop into side-by-side titlebars.
145 title_box->width = DROP_SPLIT_INDICATOR;
146 title_box->height = title_height;
147 title_box->y = box.y;
148 split_border(cursor->x, box.x, box.width, n_children,
149 avoid_index, &title_box->x, after);
150 return true;
151 }
152 return false;
153}
154
155static void update_indicator(struct seatop_move_tiling_event *e, struct wlr_box *box) {
156 wlr_scene_node_set_position(&e->indicator_rect->node, box->x, box->y);
157 wlr_scene_rect_set_size(e->indicator_rect, box->width, box->height);
158}
159
94static void handle_motion_postthreshold(struct sway_seat *seat) { 160static void handle_motion_postthreshold(struct sway_seat *seat) {
95 struct seatop_move_tiling_event *e = seat->seatop_data; 161 struct seatop_move_tiling_event *e = seat->seatop_data;
162 e->split_target = false;
96 struct wlr_surface *surface = NULL; 163 struct wlr_surface *surface = NULL;
97 double sx, sy; 164 double sx, sy;
98 struct sway_cursor *cursor = seat->cursor; 165 struct sway_cursor *cursor = seat->cursor;
99 struct sway_node *node = node_at_coords(seat, 166 struct sway_node *node = node_at_coords(seat,
100 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); 167 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
101 // Damage the old location
102 desktop_damage_box(&e->drop_box);
103 168
104 if (!node) { 169 if (!node) {
105 // Eg. hovered over a layer surface such as swaybar 170 // Eg. hovered over a layer surface such as swaybar
@@ -112,41 +177,77 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
112 // Empty workspace 177 // Empty workspace
113 e->target_node = node; 178 e->target_node = node;
114 e->target_edge = WLR_EDGE_NONE; 179 e->target_edge = WLR_EDGE_NONE;
115 workspace_get_box(node->sway_workspace, &e->drop_box); 180
116 desktop_damage_box(&e->drop_box); 181 struct wlr_box drop_box;
182 workspace_get_box(node->sway_workspace, &drop_box);
183 update_indicator(e, &drop_box);
117 return; 184 return;
118 } 185 }
119 186
120 // Deny moving within own workspace if this is the only child 187 // Deny moving within own workspace if this is the only child
121 struct sway_container *con = node->sway_container; 188 struct sway_container *con = node->sway_container;
122 if (workspace_num_tiling_views(e->con->workspace) == 1 && 189 if (workspace_num_tiling_views(e->con->pending.workspace) == 1 &&
123 con->workspace == e->con->workspace) { 190 con->pending.workspace == e->con->pending.workspace) {
124 e->target_node = NULL; 191 e->target_node = NULL;
125 e->target_edge = WLR_EDGE_NONE; 192 e->target_edge = WLR_EDGE_NONE;
126 return; 193 return;
127 } 194 }
128 195
196 struct wlr_box drop_box = {
197 .x = con->pending.content_x,
198 .y = con->pending.content_y,
199 .width = con->pending.content_width,
200 .height = con->pending.content_height,
201 };
202
203 // Check if the cursor is over a tilebar only if the destination
204 // container is not a descendant of the source container.
205 if (!surface && !container_has_ancestor(con, e->con) &&
206 split_titlebar(node, e->con, cursor->cursor,
207 &drop_box, &e->insert_after_target)) {
208 // Don't allow dropping over the source container's titlebar
209 // to give users a chance to cancel a drag operation.
210 if (con == e->con) {
211 e->target_node = NULL;
212 } else {
213 e->target_node = node;
214 e->split_target = true;
215 }
216 e->target_edge = WLR_EDGE_NONE;
217 update_indicator(e, &drop_box);
218 return;
219 }
220
129 // Traverse the ancestors, trying to find a layout container perpendicular 221 // 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. 222 // to the edge. Eg. close to the top or bottom of a horiz layout.
223 int thresh_top = con->pending.content_y + DROP_LAYOUT_BORDER;
224 int thresh_bottom = con->pending.content_y +
225 con->pending.content_height - DROP_LAYOUT_BORDER;
226 int thresh_left = con->pending.content_x + DROP_LAYOUT_BORDER;
227 int thresh_right = con->pending.content_x +
228 con->pending.content_width - DROP_LAYOUT_BORDER;
131 while (con) { 229 while (con) {
132 enum wlr_edges edge = WLR_EDGE_NONE; 230 enum wlr_edges edge = WLR_EDGE_NONE;
133 enum sway_container_layout layout = container_parent_layout(con); 231 enum sway_container_layout layout = container_parent_layout(con);
134 struct wlr_box parent; 232 struct wlr_box box;
135 con->parent ? container_get_box(con->parent, &parent) : 233 node_get_box(node_get_parent(&con->node), &box);
136 workspace_get_box(con->workspace, &parent);
137 if (layout == L_HORIZ || layout == L_TABBED) { 234 if (layout == L_HORIZ || layout == L_TABBED) {
138 if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { 235 if (cursor->cursor->y < thresh_top) {
139 edge = WLR_EDGE_TOP; 236 edge = WLR_EDGE_TOP;
140 } else if (cursor->cursor->y > parent.y + parent.height 237 box.height = thresh_top - box.y;
141 - DROP_LAYOUT_BORDER) { 238 } else if (cursor->cursor->y > thresh_bottom) {
142 edge = WLR_EDGE_BOTTOM; 239 edge = WLR_EDGE_BOTTOM;
240 box.height = box.y + box.height - thresh_bottom;
241 box.y = thresh_bottom;
143 } 242 }
144 } else if (layout == L_VERT || layout == L_STACKED) { 243 } else if (layout == L_VERT || layout == L_STACKED) {
145 if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { 244 if (cursor->cursor->x < thresh_left) {
146 edge = WLR_EDGE_LEFT; 245 edge = WLR_EDGE_LEFT;
147 } else if (cursor->cursor->x > parent.x + parent.width 246 box.width = thresh_left - box.x;
148 - DROP_LAYOUT_BORDER) { 247 } else if (cursor->cursor->x > thresh_right) {
149 edge = WLR_EDGE_RIGHT; 248 edge = WLR_EDGE_RIGHT;
249 box.width = box.x + box.width - thresh_right;
250 box.x = thresh_right;
150 } 251 }
151 } 252 }
152 if (edge) { 253 if (edge) {
@@ -155,12 +256,10 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
155 e->target_node = node_get_parent(e->target_node); 256 e->target_node = node_get_parent(e->target_node);
156 } 257 }
157 e->target_edge = edge; 258 e->target_edge = edge;
158 node_get_box(e->target_node, &e->drop_box); 259 update_indicator(e, &box);
159 resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER);
160 desktop_damage_box(&e->drop_box);
161 return; 260 return;
162 } 261 }
163 con = con->parent; 262 con = con->pending.parent;
164 } 263 }
165 264
166 // Use the hovered view - but we must be over the actual surface 265 // Use the hovered view - but we must be over the actual surface
@@ -173,23 +272,23 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
173 } 272 }
174 273
175 // Find the closest edge 274 // Find the closest edge
176 size_t thickness = fmin(con->content_width, con->content_height) * 0.3; 275 size_t thickness = fmin(con->pending.content_width, con->pending.content_height) * 0.3;
177 size_t closest_dist = INT_MAX; 276 size_t closest_dist = INT_MAX;
178 size_t dist; 277 size_t dist;
179 e->target_edge = WLR_EDGE_NONE; 278 e->target_edge = WLR_EDGE_NONE;
180 if ((dist = cursor->cursor->y - con->y) < closest_dist) { 279 if ((dist = cursor->cursor->y - con->pending.y) < closest_dist) {
181 closest_dist = dist; 280 closest_dist = dist;
182 e->target_edge = WLR_EDGE_TOP; 281 e->target_edge = WLR_EDGE_TOP;
183 } 282 }
184 if ((dist = cursor->cursor->x - con->x) < closest_dist) { 283 if ((dist = cursor->cursor->x - con->pending.x) < closest_dist) {
185 closest_dist = dist; 284 closest_dist = dist;
186 e->target_edge = WLR_EDGE_LEFT; 285 e->target_edge = WLR_EDGE_LEFT;
187 } 286 }
188 if ((dist = con->x + con->width - cursor->cursor->x) < closest_dist) { 287 if ((dist = con->pending.x + con->pending.width - cursor->cursor->x) < closest_dist) {
189 closest_dist = dist; 288 closest_dist = dist;
190 e->target_edge = WLR_EDGE_RIGHT; 289 e->target_edge = WLR_EDGE_RIGHT;
191 } 290 }
192 if ((dist = con->y + con->height - cursor->cursor->y) < closest_dist) { 291 if ((dist = con->pending.y + con->pending.height - cursor->cursor->y) < closest_dist) {
193 closest_dist = dist; 292 closest_dist = dist;
194 e->target_edge = WLR_EDGE_BOTTOM; 293 e->target_edge = WLR_EDGE_BOTTOM;
195 } 294 }
@@ -199,12 +298,8 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
199 } 298 }
200 299
201 e->target_node = node; 300 e->target_node = node;
202 e->drop_box.x = con->content_x; 301 resize_box(&drop_box, e->target_edge, thickness);
203 e->drop_box.y = con->content_y; 302 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} 303}
209 304
210static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) { 305static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
@@ -214,6 +309,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
214 } else { 309 } else {
215 handle_motion_prethreshold(seat); 310 handle_motion_prethreshold(seat);
216 } 311 }
312 transaction_commit_dirty();
217} 313}
218 314
219static bool is_parallel(enum sway_container_layout layout, 315static bool is_parallel(enum sway_container_layout layout,
@@ -232,14 +328,15 @@ static void finalize_move(struct sway_seat *seat) {
232 } 328 }
233 329
234 struct sway_container *con = e->con; 330 struct sway_container *con = e->con;
235 struct sway_container *old_parent = con->parent; 331 struct sway_container *old_parent = con->pending.parent;
236 struct sway_workspace *old_ws = con->workspace; 332 struct sway_workspace *old_ws = con->pending.workspace;
237 struct sway_node *target_node = e->target_node; 333 struct sway_node *target_node = e->target_node;
238 struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? 334 struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ?
239 target_node->sway_workspace : target_node->sway_container->workspace; 335 target_node->sway_workspace : target_node->sway_container->pending.workspace;
240 enum wlr_edges edge = e->target_edge; 336 enum wlr_edges edge = e->target_edge;
241 int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; 337 int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT;
242 bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER; 338 bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER &&
339 !e->split_target;
243 340
244 if (!swap) { 341 if (!swap) {
245 container_detach(con); 342 container_detach(con);
@@ -248,6 +345,14 @@ static void finalize_move(struct sway_seat *seat) {
248 // Moving container into empty workspace 345 // Moving container into empty workspace
249 if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { 346 if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) {
250 con = workspace_add_tiling(new_ws, con); 347 con = workspace_add_tiling(new_ws, con);
348 } else if (e->split_target) {
349 struct sway_container *target = target_node->sway_container;
350 enum sway_container_layout layout = container_parent_layout(target);
351 if (layout != L_TABBED && layout != L_STACKED) {
352 container_split(target, L_TABBED);
353 }
354 container_add_sibling(target, con, e->insert_after_target);
355 ipc_event_window(con, "move");
251 } else if (target_node->type == N_CONTAINER) { 356 } else if (target_node->type == N_CONTAINER) {
252 // Moving container before/after another 357 // Moving container before/after another
253 struct sway_container *target = target_node->sway_container; 358 struct sway_container *target = target_node->sway_container;
@@ -283,8 +388,8 @@ static void finalize_move(struct sway_seat *seat) {
283 int index = list_find(siblings, con); 388 int index = list_find(siblings, con);
284 struct sway_container *sibling = index == 0 ? 389 struct sway_container *sibling = index == 0 ?
285 siblings->items[1] : siblings->items[index - 1]; 390 siblings->items[1] : siblings->items[index - 1];
286 con->width = sibling->width; 391 con->pending.width = sibling->pending.width;
287 con->height = sibling->height; 392 con->pending.height = sibling->pending.height;
288 con->width_fraction = sibling->width_fraction; 393 con->width_fraction = sibling->width_fraction;
289 con->height_fraction = sibling->height_fraction; 394 con->height_fraction = sibling->height_fraction;
290 } 395 }
@@ -294,12 +399,13 @@ static void finalize_move(struct sway_seat *seat) {
294 arrange_workspace(new_ws); 399 arrange_workspace(new_ws);
295 } 400 }
296 401
402 transaction_commit_dirty();
297 seatop_begin_default(seat); 403 seatop_begin_default(seat);
298} 404}
299 405
300static void handle_button(struct sway_seat *seat, uint32_t time_msec, 406static void handle_button(struct sway_seat *seat, uint32_t time_msec,
301 struct wlr_input_device *device, uint32_t button, 407 struct wlr_input_device *device, uint32_t button,
302 enum wlr_button_state state) { 408 enum wl_pointer_button_state state) {
303 if (seat->cursor->pressed_button_count == 0) { 409 if (seat->cursor->pressed_button_count == 0) {
304 finalize_move(seat); 410 finalize_move(seat);
305 } 411 }
@@ -328,7 +434,7 @@ static const struct sway_seatop_impl seatop_impl = {
328 .pointer_motion = handle_pointer_motion, 434 .pointer_motion = handle_pointer_motion,
329 .tablet_tool_tip = handle_tablet_tool_tip, 435 .tablet_tool_tip = handle_tablet_tool_tip,
330 .unref = handle_unref, 436 .unref = handle_unref,
331 .render = handle_render, 437 .end = handle_end,
332}; 438};
333 439
334void seatop_begin_move_tiling_threshold(struct sway_seat *seat, 440void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
@@ -340,6 +446,20 @@ void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
340 if (!e) { 446 if (!e) {
341 return; 447 return;
342 } 448 }
449
450 const float *indicator = config->border_colors.focused.indicator;
451 float color[4] = {
452 indicator[0] * .5,
453 indicator[1] * .5,
454 indicator[2] * .5,
455 indicator[3] * .5,
456 };
457 e->indicator_rect = wlr_scene_rect_create(seat->scene_tree, 0, 0, color);
458 if (!e->indicator_rect) {
459 free(e);
460 return;
461 }
462
343 e->con = con; 463 e->con = con;
344 e->ref_lx = seat->cursor->cursor->x; 464 e->ref_lx = seat->cursor->cursor->x;
345 e->ref_ly = seat->cursor->cursor->y; 465 e->ref_ly = seat->cursor->cursor->y;
@@ -348,6 +468,7 @@ void seatop_begin_move_tiling_threshold(struct sway_seat *seat,
348 seat->seatop_data = e; 468 seat->seatop_data = e;
349 469
350 container_raise_floating(con); 470 container_raise_floating(con);
471 transaction_commit_dirty();
351 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 472 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
352} 473}
353 474
diff --git a/sway/input/seatop_resize_floating.c b/sway/input/seatop_resize_floating.c
index 5da22e47..bec86e33 100644
--- a/sway/input/seatop_resize_floating.c
+++ b/sway/input/seatop_resize_floating.c
@@ -1,7 +1,7 @@
1#define _POSIX_C_SOURCE 200809L
2#include <limits.h> 1#include <limits.h>
3#include <wlr/types/wlr_cursor.h> 2#include <wlr/types/wlr_cursor.h>
4#include <wlr/types/wlr_xcursor_manager.h> 3#include <wlr/types/wlr_xcursor_manager.h>
4#include "sway/desktop/transaction.h"
5#include "sway/input/cursor.h" 5#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 6#include "sway/input/seat.h"
7#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
@@ -20,13 +20,14 @@ struct seatop_resize_floating_event {
20 20
21static void handle_button(struct sway_seat *seat, uint32_t time_msec, 21static void handle_button(struct sway_seat *seat, uint32_t time_msec,
22 struct wlr_input_device *device, uint32_t button, 22 struct wlr_input_device *device, uint32_t button,
23 enum wlr_button_state state) { 23 enum wl_pointer_button_state state) {
24 struct seatop_resize_floating_event *e = seat->seatop_data; 24 struct seatop_resize_floating_event *e = seat->seatop_data;
25 struct sway_container *con = e->con; 25 struct sway_container *con = e->con;
26 26
27 if (seat->cursor->pressed_button_count == 0) { 27 if (seat->cursor->pressed_button_count == 0) {
28 container_set_resizing(con, false); 28 container_set_resizing(con, false);
29 arrange_container(con); // Send configure w/o resizing hint 29 arrange_container(con); // Send configure w/o resizing hint
30 transaction_commit_dirty();
30 seatop_begin_default(seat); 31 seatop_begin_default(seat);
31 } 32 }
32} 33}
@@ -78,17 +79,25 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
78 double height = e->ref_height + grow_height; 79 double height = e->ref_height + grow_height;
79 int min_width, max_width, min_height, max_height; 80 int min_width, max_width, min_height, max_height;
80 floating_calculate_constraints(&min_width, &max_width, 81 floating_calculate_constraints(&min_width, &max_width,
81 &min_height, &max_height); 82 &min_height, &max_height);
82 width = fmax(min_width + border_width, fmin(width, max_width)); 83 width = fmin(width, max_width - border_width);
83 height = fmax(min_height + border_height, fmin(height, max_height)); 84 width = fmax(width, min_width + border_width);
85 width = fmax(width, 1);
86 height = fmin(height, max_height - border_height);
87 height = fmax(height, min_height + border_height);
88 height = fmax(height, 1);
84 89
85 // Apply the view's min/max size 90 // Apply the view's min/max size
86 if (con->view) { 91 if (con->view) {
87 double view_min_width, view_max_width, view_min_height, view_max_height; 92 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, 93 view_get_constraints(con->view, &view_min_width, &view_max_width,
89 &view_min_height, &view_max_height); 94 &view_min_height, &view_max_height);
90 width = fmax(view_min_width + border_width, fmin(width, view_max_width)); 95 width = fmin(width, view_max_width - border_width);
91 height = fmax(view_min_height + border_height, fmin(height, view_max_height)); 96 width = fmax(width, view_min_width + border_width);
97 width = fmax(width, 1);
98 height = fmin(height, view_max_height - border_height);
99 height = fmax(height, view_min_height + border_height);
100 height = fmax(height, 1);
92 101
93 } 102 }
94 103
@@ -116,23 +125,24 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
116 125
117 // Determine the amounts we need to bump everything relative to the current 126 // Determine the amounts we need to bump everything relative to the current
118 // size. 127 // size.
119 int relative_grow_width = width - con->width; 128 int relative_grow_width = width - con->pending.width;
120 int relative_grow_height = height - con->height; 129 int relative_grow_height = height - con->pending.height;
121 int relative_grow_x = (e->ref_con_lx + grow_x) - con->x; 130 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; 131 int relative_grow_y = (e->ref_con_ly + grow_y) - con->pending.y;
123 132
124 // Actually resize stuff 133 // Actually resize stuff
125 con->x += relative_grow_x; 134 con->pending.x += relative_grow_x;
126 con->y += relative_grow_y; 135 con->pending.y += relative_grow_y;
127 con->width += relative_grow_width; 136 con->pending.width += relative_grow_width;
128 con->height += relative_grow_height; 137 con->pending.height += relative_grow_height;
129 138
130 con->content_x += relative_grow_x; 139 con->pending.content_x += relative_grow_x;
131 con->content_y += relative_grow_y; 140 con->pending.content_y += relative_grow_y;
132 con->content_width += relative_grow_width; 141 con->pending.content_width += relative_grow_width;
133 con->content_height += relative_grow_height; 142 con->pending.content_height += relative_grow_height;
134 143
135 arrange_container(con); 144 arrange_container(con);
145 transaction_commit_dirty();
136} 146}
137 147
138static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 148static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -166,16 +176,17 @@ void seatop_begin_resize_floating(struct sway_seat *seat,
166 e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge; 176 e->edge = edge == WLR_EDGE_NONE ? WLR_EDGE_BOTTOM | WLR_EDGE_RIGHT : edge;
167 e->ref_lx = seat->cursor->cursor->x; 177 e->ref_lx = seat->cursor->cursor->x;
168 e->ref_ly = seat->cursor->cursor->y; 178 e->ref_ly = seat->cursor->cursor->y;
169 e->ref_con_lx = con->x; 179 e->ref_con_lx = con->pending.x;
170 e->ref_con_ly = con->y; 180 e->ref_con_ly = con->pending.y;
171 e->ref_width = con->width; 181 e->ref_width = con->pending.width;
172 e->ref_height = con->height; 182 e->ref_height = con->pending.height;
173 183
174 seat->seatop_impl = &seatop_impl; 184 seat->seatop_impl = &seatop_impl;
175 seat->seatop_data = e; 185 seat->seatop_data = e;
176 186
177 container_set_resizing(con, true); 187 container_set_resizing(con, true);
178 container_raise_floating(con); 188 container_raise_floating(con);
189 transaction_commit_dirty();
179 190
180 const char *image = edge == WLR_EDGE_NONE ? 191 const char *image = edge == WLR_EDGE_NONE ?
181 "se-resize" : wlr_xcursor_get_resize_name(edge); 192 "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..15fd333b 100644
--- a/sway/input/seatop_resize_tiling.c
+++ b/sway/input/seatop_resize_tiling.c
@@ -1,7 +1,7 @@
1#define _POSIX_C_SOURCE 200809L
2#include <wlr/types/wlr_cursor.h> 1#include <wlr/types/wlr_cursor.h>
3#include <wlr/util/edges.h> 2#include <wlr/util/edges.h>
4#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/desktop/transaction.h"
5#include "sway/input/cursor.h" 5#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 6#include "sway/input/seat.h"
7#include "sway/tree/arrange.h" 7#include "sway/tree/arrange.h"
@@ -45,28 +45,29 @@ static struct sway_container *container_get_resize_sibling(
45 45
46static void handle_button(struct sway_seat *seat, uint32_t time_msec, 46static void handle_button(struct sway_seat *seat, uint32_t time_msec,
47 struct wlr_input_device *device, uint32_t button, 47 struct wlr_input_device *device, uint32_t button,
48 enum wlr_button_state state) { 48 enum wl_pointer_button_state state) {
49 struct seatop_resize_tiling_event *e = seat->seatop_data; 49 struct seatop_resize_tiling_event *e = seat->seatop_data;
50 50
51 if (seat->cursor->pressed_button_count == 0) { 51 if (seat->cursor->pressed_button_count == 0) {
52 if (e->h_con) { 52 if (e->h_con) {
53 container_set_resizing(e->h_con, false); 53 container_set_resizing(e->h_con, false);
54 container_set_resizing(e->h_sib, false); 54 container_set_resizing(e->h_sib, false);
55 if (e->h_con->parent) { 55 if (e->h_con->pending.parent) {
56 arrange_container(e->h_con->parent); 56 arrange_container(e->h_con->pending.parent);
57 } else { 57 } else {
58 arrange_workspace(e->h_con->workspace); 58 arrange_workspace(e->h_con->pending.workspace);
59 } 59 }
60 } 60 }
61 if (e->v_con) { 61 if (e->v_con) {
62 container_set_resizing(e->v_con, false); 62 container_set_resizing(e->v_con, false);
63 container_set_resizing(e->v_sib, false); 63 container_set_resizing(e->v_sib, false);
64 if (e->v_con->parent) { 64 if (e->v_con->pending.parent) {
65 arrange_container(e->v_con->parent); 65 arrange_container(e->v_con->pending.parent);
66 } else { 66 } else {
67 arrange_workspace(e->v_con->workspace); 67 arrange_workspace(e->v_con->pending.workspace);
68 } 68 }
69 } 69 }
70 transaction_commit_dirty();
70 seatop_begin_default(seat); 71 seatop_begin_default(seat);
71 } 72 }
72} 73}
@@ -80,16 +81,16 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
80 81
81 if (e->h_con) { 82 if (e->h_con) {
82 if (e->edge & WLR_EDGE_LEFT) { 83 if (e->edge & WLR_EDGE_LEFT) {
83 amount_x = (e->h_con_orig_width - moved_x) - e->h_con->width; 84 amount_x = (e->h_con_orig_width - moved_x) - e->h_con->pending.width;
84 } else if (e->edge & WLR_EDGE_RIGHT) { 85 } else if (e->edge & WLR_EDGE_RIGHT) {
85 amount_x = (e->h_con_orig_width + moved_x) - e->h_con->width; 86 amount_x = (e->h_con_orig_width + moved_x) - e->h_con->pending.width;
86 } 87 }
87 } 88 }
88 if (e->v_con) { 89 if (e->v_con) {
89 if (e->edge & WLR_EDGE_TOP) { 90 if (e->edge & WLR_EDGE_TOP) {
90 amount_y = (e->v_con_orig_height - moved_y) - e->v_con->height; 91 amount_y = (e->v_con_orig_height - moved_y) - e->v_con->pending.height;
91 } else if (e->edge & WLR_EDGE_BOTTOM) { 92 } else if (e->edge & WLR_EDGE_BOTTOM) {
92 amount_y = (e->v_con_orig_height + moved_y) - e->v_con->height; 93 amount_y = (e->v_con_orig_height + moved_y) - e->v_con->pending.height;
93 } 94 }
94 } 95 }
95 96
@@ -99,6 +100,7 @@ static void handle_pointer_motion(struct sway_seat *seat, uint32_t time_msec) {
99 if (amount_y != 0) { 100 if (amount_y != 0) {
100 container_resize_tiled(e->v_con, e->edge_y, amount_y); 101 container_resize_tiled(e->v_con, e->edge_y, amount_y);
101 } 102 }
103 transaction_commit_dirty();
102} 104}
103 105
104static void handle_unref(struct sway_seat *seat, struct sway_container *con) { 106static void handle_unref(struct sway_seat *seat, struct sway_container *con) {
@@ -140,7 +142,7 @@ void seatop_begin_resize_tiling(struct sway_seat *seat,
140 if (e->h_con) { 142 if (e->h_con) {
141 container_set_resizing(e->h_con, true); 143 container_set_resizing(e->h_con, true);
142 container_set_resizing(e->h_sib, true); 144 container_set_resizing(e->h_sib, true);
143 e->h_con_orig_width = e->h_con->width; 145 e->h_con_orig_width = e->h_con->pending.width;
144 } 146 }
145 } 147 }
146 if (edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) { 148 if (edge & (WLR_EDGE_TOP | WLR_EDGE_BOTTOM)) {
@@ -151,12 +153,13 @@ void seatop_begin_resize_tiling(struct sway_seat *seat,
151 if (e->v_con) { 153 if (e->v_con) {
152 container_set_resizing(e->v_con, true); 154 container_set_resizing(e->v_con, true);
153 container_set_resizing(e->v_sib, true); 155 container_set_resizing(e->v_sib, true);
154 e->v_con_orig_height = e->v_con->height; 156 e->v_con_orig_height = e->v_con->pending.height;
155 } 157 }
156 } 158 }
157 159
158 seat->seatop_impl = &seatop_impl; 160 seat->seatop_impl = &seatop_impl;
159 seat->seatop_data = e; 161 seat->seatop_data = e;
160 162
163 transaction_commit_dirty();
161 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat); 164 wlr_seat_pointer_notify_clear_focus(seat->wlr_seat);
162} 165}
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..2863642a 100644
--- a/sway/input/tablet.c
+++ b/sway/input/tablet.c
@@ -1,6 +1,5 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <wlr/backend/libinput.h> 2#include <wlr/config.h>
4#include <wlr/types/wlr_tablet_v2.h> 3#include <wlr/types/wlr_tablet_v2.h>
5#include <wlr/types/wlr_tablet_tool.h> 4#include <wlr/types/wlr_tablet_tool.h>
6#include <wlr/types/wlr_tablet_pad.h> 5#include <wlr/types/wlr_tablet_pad.h>
@@ -9,6 +8,10 @@
9#include "sway/input/seat.h" 8#include "sway/input/seat.h"
10#include "sway/input/tablet.h" 9#include "sway/input/tablet.h"
11 10
11#if WLR_HAS_LIBINPUT_BACKEND
12#include <wlr/backend/libinput.h>
13#endif
14
12static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) { 15static void handle_pad_tablet_destroy(struct wl_listener *listener, void *data) {
13 struct sway_tablet_pad *pad = 16 struct sway_tablet_pad *pad =
14 wl_container_of(listener, pad, tablet_destroy); 17 wl_container_of(listener, pad, tablet_destroy);
@@ -54,15 +57,14 @@ void sway_configure_tablet(struct sway_tablet *tablet) {
54 tablet->seat_device->input_device->wlr_device; 57 tablet->seat_device->input_device->wlr_device;
55 struct sway_seat *seat = tablet->seat_device->sway_seat; 58 struct sway_seat *seat = tablet->seat_device->sway_seat;
56 59
57 if ((seat->wlr_seat->capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) { 60 seat_configure_xcursor(seat);
58 seat_configure_xcursor(seat);
59 }
60 61
61 if (!tablet->tablet_v2) { 62 if (!tablet->tablet_v2) {
62 tablet->tablet_v2 = 63 tablet->tablet_v2 =
63 wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device); 64 wlr_tablet_create(server.tablet_v2, seat->wlr_seat, device);
64 } 65 }
65 66
67#if WLR_HAS_LIBINPUT_BACKEND
66 /* Search for a sibling tablet pad */ 68 /* Search for a sibling tablet pad */
67 if (!wlr_input_device_is_libinput(device)) { 69 if (!wlr_input_device_is_libinput(device)) {
68 /* We can only do this on libinput devices */ 70 /* We can only do this on libinput devices */
@@ -87,6 +89,7 @@ void sway_configure_tablet(struct sway_tablet *tablet) {
87 break; 89 break;
88 } 90 }
89 } 91 }
92#endif
90} 93}
91 94
92void sway_tablet_destroy(struct sway_tablet *tablet) { 95void sway_tablet_destroy(struct sway_tablet *tablet) {
@@ -196,7 +199,7 @@ static void handle_tablet_pad_attach(struct wl_listener *listener,
196 199
197static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) { 200static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
198 struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring); 201 struct sway_tablet_pad *pad = wl_container_of(listener, pad, ring);
199 struct wlr_event_tablet_pad_ring *event = data; 202 struct wlr_tablet_pad_ring_event *event = data;
200 203
201 if (!pad->current_surface) { 204 if (!pad->current_surface) {
202 return; 205 return;
@@ -210,7 +213,7 @@ static void handle_tablet_pad_ring(struct wl_listener *listener, void *data) {
210 213
211static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) { 214static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
212 struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip); 215 struct sway_tablet_pad *pad = wl_container_of(listener, pad, strip);
213 struct wlr_event_tablet_pad_strip *event = data; 216 struct wlr_tablet_pad_strip_event *event = data;
214 217
215 if (!pad->current_surface) { 218 if (!pad->current_surface) {
216 return; 219 return;
@@ -224,7 +227,7 @@ static void handle_tablet_pad_strip(struct wl_listener *listener, void *data) {
224 227
225static void handle_tablet_pad_button(struct wl_listener *listener, void *data) { 228static void handle_tablet_pad_button(struct wl_listener *listener, void *data) {
226 struct sway_tablet_pad *pad = wl_container_of(listener, pad, button); 229 struct sway_tablet_pad *pad = wl_container_of(listener, pad, button);
227 struct wlr_event_tablet_pad_button *event = data; 230 struct wlr_tablet_pad_button_event *event = data;
228 231
229 if (!pad->current_surface) { 232 if (!pad->current_surface) {
230 return; 233 return;
@@ -246,6 +249,7 @@ struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
246 return NULL; 249 return NULL;
247 } 250 }
248 251
252 tablet_pad->wlr = wlr_tablet_pad_from_input_device(device->input_device->wlr_device);
249 tablet_pad->seat_device = device; 253 tablet_pad->seat_device = device;
250 wl_list_init(&tablet_pad->attach.link); 254 wl_list_init(&tablet_pad->attach.link);
251 wl_list_init(&tablet_pad->button.link); 255 wl_list_init(&tablet_pad->button.link);
@@ -260,40 +264,41 @@ struct sway_tablet_pad *sway_tablet_pad_create(struct sway_seat *seat,
260} 264}
261 265
262void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) { 266void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) {
263 struct wlr_input_device *device = 267 struct wlr_input_device *wlr_device =
264 tablet_pad->seat_device->input_device->wlr_device; 268 tablet_pad->seat_device->input_device->wlr_device;
265 struct sway_seat *seat = tablet_pad->seat_device->sway_seat; 269 struct sway_seat *seat = tablet_pad->seat_device->sway_seat;
266 270
267 if (!tablet_pad->tablet_v2_pad) { 271 if (!tablet_pad->tablet_v2_pad) {
268 tablet_pad->tablet_v2_pad = 272 tablet_pad->tablet_v2_pad =
269 wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, device); 273 wlr_tablet_pad_create(server.tablet_v2, seat->wlr_seat, wlr_device);
270 } 274 }
271 275
272 wl_list_remove(&tablet_pad->attach.link); 276 wl_list_remove(&tablet_pad->attach.link);
273 tablet_pad->attach.notify = handle_tablet_pad_attach; 277 tablet_pad->attach.notify = handle_tablet_pad_attach;
274 wl_signal_add(&device->tablet_pad->events.attach_tablet, 278 wl_signal_add(&tablet_pad->wlr->events.attach_tablet,
275 &tablet_pad->attach); 279 &tablet_pad->attach);
276 280
277 wl_list_remove(&tablet_pad->button.link); 281 wl_list_remove(&tablet_pad->button.link);
278 tablet_pad->button.notify = handle_tablet_pad_button; 282 tablet_pad->button.notify = handle_tablet_pad_button;
279 wl_signal_add(&device->tablet_pad->events.button, &tablet_pad->button); 283 wl_signal_add(&tablet_pad->wlr->events.button, &tablet_pad->button);
280 284
281 wl_list_remove(&tablet_pad->strip.link); 285 wl_list_remove(&tablet_pad->strip.link);
282 tablet_pad->strip.notify = handle_tablet_pad_strip; 286 tablet_pad->strip.notify = handle_tablet_pad_strip;
283 wl_signal_add(&device->tablet_pad->events.strip, &tablet_pad->strip); 287 wl_signal_add(&tablet_pad->wlr->events.strip, &tablet_pad->strip);
284 288
285 wl_list_remove(&tablet_pad->ring.link); 289 wl_list_remove(&tablet_pad->ring.link);
286 tablet_pad->ring.notify = handle_tablet_pad_ring; 290 tablet_pad->ring.notify = handle_tablet_pad_ring;
287 wl_signal_add(&device->tablet_pad->events.ring, &tablet_pad->ring); 291 wl_signal_add(&tablet_pad->wlr->events.ring, &tablet_pad->ring);
288 292
293#if WLR_HAS_LIBINPUT_BACKEND
289 /* Search for a sibling tablet */ 294 /* Search for a sibling tablet */
290 if (!wlr_input_device_is_libinput(device)) { 295 if (!wlr_input_device_is_libinput(wlr_device)) {
291 /* We can only do this on libinput devices */ 296 /* We can only do this on libinput devices */
292 return; 297 return;
293 } 298 }
294 299
295 struct libinput_device_group *group = 300 struct libinput_device_group *group =
296 libinput_device_get_device_group(wlr_libinput_get_device_handle(device)); 301 libinput_device_get_device_group(wlr_libinput_get_device_handle(wlr_device));
297 struct sway_tablet *tool; 302 struct sway_tablet *tool;
298 wl_list_for_each(tool, &seat->cursor->tablets, link) { 303 wl_list_for_each(tool, &seat->cursor->tablets, link) {
299 struct wlr_input_device *tablet = 304 struct wlr_input_device *tablet =
@@ -310,6 +315,7 @@ void sway_configure_tablet_pad(struct sway_tablet_pad *tablet_pad) {
310 break; 315 break;
311 } 316 }
312 } 317 }
318#endif
313} 319}
314 320
315void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) { 321void sway_tablet_pad_destroy(struct sway_tablet_pad *tablet_pad) {
@@ -333,14 +339,10 @@ static void handle_pad_tablet_surface_destroy(struct wl_listener *listener,
333 struct sway_tablet_pad *tablet_pad = 339 struct sway_tablet_pad *tablet_pad =
334 wl_container_of(listener, tablet_pad, surface_destroy); 340 wl_container_of(listener, tablet_pad, surface_destroy);
335 341
336 wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad->tablet_v2_pad, 342 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} 343}
342 344
343void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad, 345void sway_tablet_pad_set_focus(struct sway_tablet_pad *tablet_pad,
344 struct wlr_surface *surface) { 346 struct wlr_surface *surface) {
345 if (!tablet_pad || !tablet_pad->tablet) { 347 if (!tablet_pad || !tablet_pad->tablet) {
346 return; 348 return;
@@ -359,7 +361,8 @@ void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
359 tablet_pad->current_surface = NULL; 361 tablet_pad->current_surface = NULL;
360 } 362 }
361 363
362 if (!wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) { 364 if (surface == NULL ||
365 !wlr_surface_accepts_tablet_v2(tablet_pad->tablet->tablet_v2, surface)) {
363 return; 366 return;
364 } 367 }
365 368
@@ -367,7 +370,6 @@ void sway_tablet_pad_notify_enter(struct sway_tablet_pad *tablet_pad,
367 tablet_pad->tablet->tablet_v2, surface); 370 tablet_pad->tablet->tablet_v2, surface);
368 371
369 tablet_pad->current_surface = surface; 372 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; 373 tablet_pad->surface_destroy.notify = handle_pad_tablet_surface_destroy;
372 wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy); 374 wl_signal_add(&surface->events.destroy, &tablet_pad->surface_destroy);
373} 375}
diff --git a/sway/input/text_input.c b/sway/input/text_input.c
index f83726ee..c38a3bb2 100644
--- a/sway/input/text_input.c
+++ b/sway/input/text_input.c
@@ -2,7 +2,14 @@
2#include <stdlib.h> 2#include <stdlib.h>
3#include "log.h" 3#include "log.h"
4#include "sway/input/seat.h" 4#include "sway/input/seat.h"
5#include "sway/scene_descriptor.h"
6#include "sway/tree/root.h"
7#include "sway/tree/view.h"
8#include "sway/output.h"
5#include "sway/input/text_input.h" 9#include "sway/input/text_input.h"
10#include "sway/input/text_input_popup.h"
11#include "sway/layers.h"
12static void input_popup_update(struct sway_input_popup *popup);
6 13
7static struct sway_text_input *relay_get_focusable_text_input( 14static struct sway_text_input *relay_get_focusable_text_input(
8 struct sway_input_method_relay *relay) { 15 struct sway_input_method_relay *relay) {
@@ -55,6 +62,35 @@ static void handle_im_commit(struct wl_listener *listener, void *data) {
55 wlr_text_input_v3_send_done(text_input->input); 62 wlr_text_input_v3_send_done(text_input->input);
56} 63}
57 64
65static void handle_im_keyboard_grab_destroy(struct wl_listener *listener, void *data) {
66 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
67 input_method_keyboard_grab_destroy);
68 struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
69 wl_list_remove(&relay->input_method_keyboard_grab_destroy.link);
70
71 if (keyboard_grab->keyboard) {
72 // send modifier state to original client
73 wlr_seat_keyboard_notify_modifiers(keyboard_grab->input_method->seat,
74 &keyboard_grab->keyboard->modifiers);
75 }
76}
77
78static void handle_im_grab_keyboard(struct wl_listener *listener, void *data) {
79 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
80 input_method_grab_keyboard);
81 struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data;
82
83 // send modifier state to grab
84 struct wlr_keyboard *active_keyboard = wlr_seat_get_keyboard(relay->seat->wlr_seat);
85 wlr_input_method_keyboard_grab_v2_set_keyboard(keyboard_grab,
86 active_keyboard);
87
88 wl_signal_add(&keyboard_grab->events.destroy,
89 &relay->input_method_keyboard_grab_destroy);
90 relay->input_method_keyboard_grab_destroy.notify =
91 handle_im_keyboard_grab_destroy;
92}
93
58static void text_input_set_pending_focused_surface( 94static void text_input_set_pending_focused_surface(
59 struct sway_text_input *text_input, struct wlr_surface *surface) { 95 struct sway_text_input *text_input, struct wlr_surface *surface) {
60 wl_list_remove(&text_input->pending_focused_surface_destroy.link); 96 wl_list_remove(&text_input->pending_focused_surface_destroy.link);
@@ -73,6 +109,10 @@ static void handle_im_destroy(struct wl_listener *listener, void *data) {
73 input_method_destroy); 109 input_method_destroy);
74 struct wlr_input_method_v2 *context = data; 110 struct wlr_input_method_v2 *context = data;
75 assert(context == relay->input_method); 111 assert(context == relay->input_method);
112 wl_list_remove(&relay->input_method_commit.link);
113 wl_list_remove(&relay->input_method_grab_keyboard.link);
114 wl_list_remove(&relay->input_method_destroy.link);
115 wl_list_remove(&relay->input_method_new_popup_surface.link);
76 relay->input_method = NULL; 116 relay->input_method = NULL;
77 struct sway_text_input *text_input = relay_get_focused_text_input(relay); 117 struct sway_text_input *text_input = relay_get_focused_text_input(relay);
78 if (text_input) { 118 if (text_input) {
@@ -92,13 +132,23 @@ static void relay_send_im_state(struct sway_input_method_relay *relay,
92 return; 132 return;
93 } 133 }
94 // TODO: only send each of those if they were modified 134 // TODO: only send each of those if they were modified
95 wlr_input_method_v2_send_surrounding_text(input_method, 135 if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_SURROUNDING_TEXT) {
96 input->current.surrounding.text, input->current.surrounding.cursor, 136 wlr_input_method_v2_send_surrounding_text(input_method,
97 input->current.surrounding.anchor); 137 input->current.surrounding.text, input->current.surrounding.cursor,
138 input->current.surrounding.anchor);
139 }
98 wlr_input_method_v2_send_text_change_cause(input_method, 140 wlr_input_method_v2_send_text_change_cause(input_method,
99 input->current.text_change_cause); 141 input->current.text_change_cause);
100 wlr_input_method_v2_send_content_type(input_method, 142 if (input->active_features & WLR_TEXT_INPUT_V3_FEATURE_CONTENT_TYPE) {
101 input->current.content_type.hint, input->current.content_type.purpose); 143 wlr_input_method_v2_send_content_type(input_method,
144 input->current.content_type.hint,
145 input->current.content_type.purpose);
146 }
147 struct sway_input_popup *popup;
148 wl_list_for_each(popup, &relay->input_popups, link) {
149 // send_text_input_rectangle is called in this function
150 input_popup_update(popup);
151 }
102 wlr_input_method_v2_send_done(input_method); 152 wlr_input_method_v2_send_done(input_method);
103 // TODO: pass intent, display popup size 153 // TODO: pass intent, display popup size
104} 154}
@@ -144,6 +194,10 @@ static void handle_text_input_disable(struct wl_listener *listener,
144 void *data) { 194 void *data) {
145 struct sway_text_input *text_input = wl_container_of(listener, text_input, 195 struct sway_text_input *text_input = wl_container_of(listener, text_input,
146 text_input_disable); 196 text_input_disable);
197 if (text_input->input->focused_surface == NULL) {
198 sway_log(SWAY_DEBUG, "Disabling text input, but no longer focused");
199 return;
200 }
147 relay_disable_text_input(text_input->relay, text_input); 201 relay_disable_text_input(text_input->relay, text_input);
148} 202}
149 203
@@ -217,6 +271,211 @@ static void relay_handle_text_input(struct wl_listener *listener,
217 sway_text_input_create(relay, wlr_text_input); 271 sway_text_input_create(relay, wlr_text_input);
218} 272}
219 273
274static void input_popup_update(struct sway_input_popup *popup) {
275 struct sway_text_input *text_input =
276 relay_get_focused_text_input(popup->relay);
277
278 if (text_input == NULL || text_input->input->focused_surface == NULL) {
279 return;
280 }
281
282 if (popup->scene_tree != NULL) {
283 wlr_scene_node_destroy(&popup->scene_tree->node);
284 popup->scene_tree = NULL;
285 }
286 if (popup->desc.relative != NULL) {
287 wlr_scene_node_destroy(popup->desc.relative);
288 popup->desc.relative = NULL;
289 }
290 popup->desc.view = NULL;
291
292 if (!popup->popup_surface->surface->mapped) {
293 return;
294 }
295
296 bool cursor_rect = text_input->input->current.features
297 & WLR_TEXT_INPUT_V3_FEATURE_CURSOR_RECTANGLE;
298 struct wlr_surface *focused_surface = text_input->input->focused_surface;
299 struct wlr_box cursor_area = text_input->input->current.cursor_rectangle;
300
301 struct wlr_box output_box;
302 struct wlr_box parent = {0};
303 struct wlr_layer_surface_v1 *layer_surface =
304 wlr_layer_surface_v1_try_from_wlr_surface(focused_surface);
305 struct wlr_scene_tree *relative_parent;
306
307 struct wlr_box geo = {0};
308
309 popup->scene_tree = wlr_scene_subsurface_tree_create(root->layers.popup, popup->popup_surface->surface);
310 if (layer_surface != NULL) {
311 struct sway_layer_surface *layer =
312 layer_surface->data;
313 if (layer == NULL) {
314 return;
315 }
316
317 relative_parent = layer->scene->tree;
318 struct wlr_output *output = layer->layer_surface->output;
319 wlr_output_layout_get_box(root->output_layout, output, &output_box);
320 int lx, ly;
321 wlr_scene_node_coords(&layer->tree->node, &lx, &ly);
322 parent.x = lx;
323 parent.y = ly;
324 popup->desc.view = NULL;
325 } else {
326 struct sway_view *view = view_from_wlr_surface(focused_surface);
327 relative_parent = view->scene_tree;
328 geo = view->geometry;
329 int lx, ly;
330 wlr_scene_node_coords(&view->scene_tree->node, &lx, &ly);
331 struct wlr_output *output = wlr_output_layout_output_at(root->output_layout,
332 view->container->pending.content_x + view->geometry.x,
333 view->container->pending.content_y + view->geometry.y);
334 wlr_output_layout_get_box(root->output_layout, output, &output_box);
335 parent.x = lx;
336 parent.y = ly;
337
338 parent.width = view->geometry.width;
339 parent.height = view->geometry.height;
340 popup->desc.view = view;
341 }
342
343 struct wlr_scene_tree *relative = wlr_scene_tree_create(relative_parent);
344
345 popup->desc.relative = &relative->node;
346 if (!scene_descriptor_assign(&popup->scene_tree->node,
347 SWAY_SCENE_DESC_POPUP, &popup->desc)) {
348 wlr_scene_node_destroy(&popup->scene_tree->node);
349 popup->scene_tree = NULL;
350 return;
351 }
352
353 if (!cursor_rect) {
354 cursor_area.x = 0;
355 cursor_area.y = 0;
356 cursor_area.width = parent.width;
357 cursor_area.height = parent.height;
358 }
359
360 int popup_width = popup->popup_surface->surface->current.width;
361 int popup_height = popup->popup_surface->surface->current.height;
362 int x1 = parent.x + cursor_area.x;
363 int x2 = parent.x + cursor_area.x + cursor_area.width;
364 int y1 = parent.y + cursor_area.y;
365 int y2 = parent.y + cursor_area.y + cursor_area.height;
366 int x = x1;
367 int y = y2;
368
369 int available_right = output_box.x + output_box.width - x1;
370 int available_left = x2 - output_box.x;
371 if (available_right < popup_width && available_left > available_right) {
372 x = x2 - popup_width;
373 }
374
375 int available_down = output_box.y + output_box.height - y2;
376 int available_up = y1 - output_box.y;
377 if (available_down < popup_height && available_up > available_down) {
378 y = y1 - popup_height;
379 }
380
381 wlr_scene_node_set_position(&relative->node, x - parent.x - geo.x, y - parent.y - geo.y);
382 if (cursor_rect) {
383 struct wlr_box box = {
384 .x = x1 - x,
385 .y = y1 - y,
386 .width = cursor_area.width,
387 .height = cursor_area.height,
388 };
389 wlr_input_popup_surface_v2_send_text_input_rectangle(
390 popup->popup_surface, &box);
391 }
392 wlr_scene_node_set_position(&popup->scene_tree->node, x - geo.x, y - geo.y);
393}
394
395static void input_popup_set_focus(struct sway_input_popup *popup,
396 struct wlr_surface *surface) {
397 wl_list_remove(&popup->focused_surface_unmap.link);
398
399 if (surface == NULL) {
400 wl_list_init(&popup->focused_surface_unmap.link);
401 input_popup_update(popup);
402 return;
403 }
404 struct wlr_layer_surface_v1 *layer_surface =
405 wlr_layer_surface_v1_try_from_wlr_surface(surface);
406 if (layer_surface != NULL) {
407 wl_signal_add(
408 &layer_surface->surface->events.unmap, &popup->focused_surface_unmap);
409 input_popup_update(popup);
410 return;
411 }
412
413 struct sway_view *view = view_from_wlr_surface(surface);
414 wl_signal_add(&view->events.unmap, &popup->focused_surface_unmap);
415}
416
417static void handle_im_popup_destroy(struct wl_listener *listener, void *data) {
418 struct sway_input_popup *popup =
419 wl_container_of(listener, popup, popup_destroy);
420 wl_list_remove(&popup->focused_surface_unmap.link);
421 wl_list_remove(&popup->popup_surface_commit.link);
422 wl_list_remove(&popup->popup_destroy.link);
423 wl_list_remove(&popup->link);
424
425 free(popup);
426}
427
428static void handle_im_popup_surface_commit(struct wl_listener *listener,
429 void *data) {
430 struct sway_input_popup *popup =
431 wl_container_of(listener, popup, popup_surface_commit);
432 input_popup_update(popup);
433}
434
435static void handle_im_focused_surface_unmap(
436 struct wl_listener *listener, void *data) {
437 struct sway_input_popup *popup =
438 wl_container_of(listener, popup, focused_surface_unmap);
439 input_popup_update(popup);
440}
441
442static void handle_im_new_popup_surface(struct wl_listener *listener,
443 void *data) {
444 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
445 input_method_new_popup_surface);
446 struct sway_input_popup *popup = calloc(1, sizeof(*popup));
447 popup->relay = relay;
448 popup->popup_surface = data;
449 popup->popup_surface->data = popup;
450
451 wl_signal_add(
452 &popup->popup_surface->events.destroy, &popup->popup_destroy);
453 popup->popup_destroy.notify = handle_im_popup_destroy;
454 wl_signal_add(&popup->popup_surface->surface->events.commit,
455 &popup->popup_surface_commit);
456 popup->popup_surface_commit.notify = handle_im_popup_surface_commit;
457 wl_list_init(&popup->focused_surface_unmap.link);
458 popup->focused_surface_unmap.notify = handle_im_focused_surface_unmap;
459
460 struct sway_text_input *text_input = relay_get_focused_text_input(relay);
461 if (text_input != NULL) {
462 input_popup_set_focus(popup, text_input->input->focused_surface);
463 } else {
464 input_popup_set_focus(popup, NULL);
465 }
466
467 wl_list_insert(&relay->input_popups, &popup->link);
468}
469
470static void text_input_send_enter(struct sway_text_input *text_input,
471 struct wlr_surface *surface) {
472 wlr_text_input_v3_send_enter(text_input->input, surface);
473 struct sway_input_popup *popup;
474 wl_list_for_each(popup, &text_input->relay->input_popups, link) {
475 input_popup_set_focus(popup, surface);
476 }
477}
478
220static void relay_handle_input_method(struct wl_listener *listener, 479static void relay_handle_input_method(struct wl_listener *listener,
221 void *data) { 480 void *data) {
222 struct sway_input_method_relay *relay = wl_container_of(listener, relay, 481 struct sway_input_method_relay *relay = wl_container_of(listener, relay,
@@ -236,13 +495,19 @@ static void relay_handle_input_method(struct wl_listener *listener,
236 wl_signal_add(&relay->input_method->events.commit, 495 wl_signal_add(&relay->input_method->events.commit,
237 &relay->input_method_commit); 496 &relay->input_method_commit);
238 relay->input_method_commit.notify = handle_im_commit; 497 relay->input_method_commit.notify = handle_im_commit;
498 wl_signal_add(&relay->input_method->events.grab_keyboard,
499 &relay->input_method_grab_keyboard);
500 relay->input_method_grab_keyboard.notify = handle_im_grab_keyboard;
239 wl_signal_add(&relay->input_method->events.destroy, 501 wl_signal_add(&relay->input_method->events.destroy,
240 &relay->input_method_destroy); 502 &relay->input_method_destroy);
241 relay->input_method_destroy.notify = handle_im_destroy; 503 relay->input_method_destroy.notify = handle_im_destroy;
504 wl_signal_add(&relay->input_method->events.new_popup_surface,
505 &relay->input_method_new_popup_surface);
506 relay->input_method_new_popup_surface.notify = handle_im_new_popup_surface;
242 507
243 struct sway_text_input *text_input = relay_get_focusable_text_input(relay); 508 struct sway_text_input *text_input = relay_get_focusable_text_input(relay);
244 if (text_input) { 509 if (text_input) {
245 wlr_text_input_v3_send_enter(text_input->input, 510 text_input_send_enter(text_input,
246 text_input->pending_focused_surface); 511 text_input->pending_focused_surface);
247 text_input_set_pending_focused_surface(text_input, NULL); 512 text_input_set_pending_focused_surface(text_input, NULL);
248 } 513 }
@@ -252,6 +517,7 @@ void sway_input_method_relay_init(struct sway_seat *seat,
252 struct sway_input_method_relay *relay) { 517 struct sway_input_method_relay *relay) {
253 relay->seat = seat; 518 relay->seat = seat;
254 wl_list_init(&relay->text_inputs); 519 wl_list_init(&relay->text_inputs);
520 wl_list_init(&relay->input_popups);
255 521
256 relay->text_input_new.notify = relay_handle_text_input; 522 relay->text_input_new.notify = relay_handle_text_input;
257 wl_signal_add(&server.text_input->events.text_input, 523 wl_signal_add(&server.text_input->events.text_input,
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index fceee84d..81ca3483 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,68 @@ 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));
291 json_object_object_add(object, "floating", NULL);
292 json_object_object_add(object, "scratchpad_state", NULL);
226 293
227 return object; 294 return object;
228} 295}
229 296
230static void ipc_json_describe_root(struct sway_root *root, json_object *object) { 297static 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")); 298 json_object_object_add(object, "primary", json_object_new_boolean(false));
299 json_object_object_add(object, "make",
300 json_object_new_string(wlr_output->make ? wlr_output->make : "Unknown"));
301 json_object_object_add(object, "model",
302 json_object_new_string(wlr_output->model ? wlr_output->model : "Unknown"));
303 json_object_object_add(object, "serial",
304 json_object_new_string(wlr_output->serial ? wlr_output->serial : "Unknown"));
305
306 json_object *modes_array = json_object_new_array();
307 struct wlr_output_mode *mode;
308 wl_list_for_each(mode, &wlr_output->modes, link) {
309 json_object *mode_object = json_object_new_object();
310 json_object_object_add(mode_object, "width",
311 json_object_new_int(mode->width));
312 json_object_object_add(mode_object, "height",
313 json_object_new_int(mode->height));
314 json_object_object_add(mode_object, "refresh",
315 json_object_new_int(mode->refresh));
316 json_object_array_add(modes_array, mode_object);
317 }
318 json_object_object_add(object, "modes", modes_array);
232} 319}
233 320
234static void ipc_json_describe_output(struct sway_output *output, 321static void ipc_json_describe_output(struct sway_output *output,
235 json_object *object) { 322 json_object *object) {
323 ipc_json_describe_wlr_output(output->wlr_output, object);
324}
325
326static void ipc_json_describe_enabled_output(struct sway_output *output,
327 json_object *object) {
328 ipc_json_describe_output(output, object);
329
236 struct wlr_output *wlr_output = output->wlr_output; 330 struct wlr_output *wlr_output = output->wlr_output;
237 json_object_object_add(object, "type", json_object_new_string("output")); 331 json_object_object_add(object, "non_desktop", json_object_new_boolean(false));
238 json_object_object_add(object, "active", json_object_new_boolean(true)); 332 json_object_object_add(object, "active", json_object_new_boolean(true));
239 json_object_object_add(object, "dpms", 333 json_object_object_add(object, "dpms",
240 json_object_new_boolean(wlr_output->enabled)); 334 json_object_new_boolean(wlr_output->enabled));
241 json_object_object_add(object, "primary", json_object_new_boolean(false)); 335 json_object_object_add(object, "power",
336 json_object_new_boolean(wlr_output->enabled));
242 json_object_object_add(object, "layout", json_object_new_string("output")); 337 json_object_object_add(object, "layout", json_object_new_string("output"));
243 json_object_object_add(object, "orientation", 338 json_object_object_add(object, "orientation",
244 json_object_new_string( 339 json_object_new_string(
245 ipc_json_orientation_description(L_NONE))); 340 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", 341 json_object_object_add(object, "scale",
253 json_object_new_double(wlr_output->scale)); 342 json_object_new_double(wlr_output->scale));
254 json_object_object_add(object, "scale_filter", 343 json_object_object_add(object, "scale_filter",
@@ -273,25 +362,26 @@ static void ipc_json_describe_output(struct sway_output *output,
273 json_object *modes_array = json_object_new_array(); 362 json_object *modes_array = json_object_new_array();
274 struct wlr_output_mode *mode; 363 struct wlr_output_mode *mode;
275 wl_list_for_each(mode, &wlr_output->modes, link) { 364 wl_list_for_each(mode, &wlr_output->modes, link) {
276 json_object *mode_object = json_object_new_object(); 365 json_object *mode_object =
277 json_object_object_add(mode_object, "width", 366 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); 367 json_object_array_add(modes_array, mode_object);
284 } 368 }
285 369
286 json_object_object_add(object, "modes", modes_array); 370 json_object_object_add(object, "modes", modes_array);
287 371
288 json_object *current_mode_object = json_object_new_object(); 372 json_object *current_mode_object;
289 json_object_object_add(current_mode_object, "width", 373 if (wlr_output->current_mode != NULL) {
290 json_object_new_int(wlr_output->width)); 374 current_mode_object =
291 json_object_object_add(current_mode_object, "height", 375 ipc_json_output_mode_description(wlr_output->current_mode);
292 json_object_new_int(wlr_output->height)); 376 } else {
293 json_object_object_add(current_mode_object, "refresh", 377 current_mode_object = json_object_new_object();
294 json_object_new_int(wlr_output->refresh)); 378 json_object_object_add(current_mode_object, "width",
379 json_object_new_int(wlr_output->width));
380 json_object_object_add(current_mode_object, "height",
381 json_object_new_int(wlr_output->height));
382 json_object_object_add(current_mode_object, "refresh",
383 json_object_new_int(wlr_output->refresh));
384 }
295 json_object_object_add(object, "current_mode", current_mode_object); 385 json_object_object_add(object, "current_mode", current_mode_object);
296 386
297 struct sway_node *parent = node_get_parent(&output->node); 387 struct sway_node *parent = node_get_parent(&output->node);
@@ -315,33 +405,15 @@ json_object *ipc_json_describe_disabled_output(struct sway_output *output) {
315 405
316 json_object *object = json_object_new_object(); 406 json_object *object = json_object_new_object();
317 407
408 ipc_json_describe_output(output, object);
409
410 json_object_object_add(object, "non_desktop", json_object_new_boolean(false));
318 json_object_object_add(object, "type", json_object_new_string("output")); 411 json_object_object_add(object, "type", json_object_new_string("output"));
319 json_object_object_add(object, "name", 412 json_object_object_add(object, "name",
320 json_object_new_string(wlr_output->name)); 413 json_object_new_string(wlr_output->name));
321 json_object_object_add(object, "active", json_object_new_boolean(false)); 414 json_object_object_add(object, "active", json_object_new_boolean(false));
322 json_object_object_add(object, "dpms", json_object_new_boolean(false)); 415 json_object_object_add(object, "dpms", json_object_new_boolean(false));
323 json_object_object_add(object, "primary", json_object_new_boolean(false)); 416 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 417
346 json_object_object_add(object, "current_workspace", NULL); 418 json_object_object_add(object, "current_workspace", NULL);
347 419
@@ -357,6 +429,21 @@ json_object *ipc_json_describe_disabled_output(struct sway_output *output) {
357 return object; 429 return object;
358} 430}
359 431
432json_object *ipc_json_describe_non_desktop_output(struct sway_output_non_desktop *output) {
433 struct wlr_output *wlr_output = output->wlr_output;
434
435 json_object *object = json_object_new_object();
436
437 ipc_json_describe_wlr_output(wlr_output, object);
438
439 json_object_object_add(object, "non_desktop", json_object_new_boolean(true));
440 json_object_object_add(object, "type", json_object_new_string("output"));
441 json_object_object_add(object, "name",
442 json_object_new_string(wlr_output->name));
443
444 return object;
445}
446
360static json_object *ipc_json_describe_scratchpad_output(void) { 447static json_object *ipc_json_describe_scratchpad_output(void) {
361 struct wlr_box box; 448 struct wlr_box box;
362 root_get_box(root, &box); 449 root_get_box(root, &box);
@@ -369,11 +456,9 @@ static json_object *ipc_json_describe_scratchpad_output(void) {
369 json_object_new_int(container->node.id)); 456 json_object_new_int(container->node.id));
370 } 457 }
371 458
372 json_object *workspace = ipc_json_create_node(i3_scratch_id, 459 json_object *workspace = ipc_json_create_node(i3_scratch_id, "workspace",
373 "__i3_scratch", false, workspace_focus, &box); 460 "__i3_scratch", false, workspace_focus, &box);
374 json_object_object_add(workspace, "fullscreen_mode", json_object_new_int(1)); 461 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 462
378 // List all hidden scratchpad containers as floating nodes 463 // List all hidden scratchpad containers as floating nodes
379 json_object *floating_array = json_object_new_array(); 464 json_object *floating_array = json_object_new_array();
@@ -390,10 +475,8 @@ static json_object *ipc_json_describe_scratchpad_output(void) {
390 json_object *output_focus = json_object_new_array(); 475 json_object *output_focus = json_object_new_array();
391 json_object_array_add(output_focus, json_object_new_int(i3_scratch_id)); 476 json_object_array_add(output_focus, json_object_new_int(i3_scratch_id));
392 477
393 json_object *output = ipc_json_create_node(i3_output_id, 478 json_object *output = ipc_json_create_node(i3_output_id, "output",
394 "__i3", false, output_focus, &box); 479 "__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", 480 json_object_object_add(output, "layout",
398 json_object_new_string("output")); 481 json_object_new_string("output"));
399 482
@@ -423,7 +506,6 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace,
423 json_object_object_add(object, "fullscreen_mode", json_object_new_int(1)); 506 json_object_object_add(object, "fullscreen_mode", json_object_new_int(1));
424 json_object_object_add(object, "output", workspace->output ? 507 json_object_object_add(object, "output", workspace->output ?
425 json_object_new_string(workspace->output->wlr_output->name) : NULL); 508 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", 509 json_object_object_add(object, "urgent",
428 json_object_new_boolean(workspace->urgent)); 510 json_object_new_boolean(workspace->urgent));
429 json_object_object_add(object, "representation", workspace->representation ? 511 json_object_object_add(object, "representation", workspace->representation ?
@@ -448,30 +530,32 @@ static void ipc_json_describe_workspace(struct sway_workspace *workspace,
448 530
449static void get_deco_rect(struct sway_container *c, struct wlr_box *deco_rect) { 531static void get_deco_rect(struct sway_container *c, struct wlr_box *deco_rect) {
450 enum sway_container_layout parent_layout = container_parent_layout(c); 532 enum sway_container_layout parent_layout = container_parent_layout(c);
451 bool tab_or_stack = parent_layout == L_TABBED || parent_layout == L_STACKED; 533 list_t *siblings = container_get_siblings(c);
534 bool tab_or_stack = (parent_layout == L_TABBED || parent_layout == L_STACKED)
535 && ((siblings && siblings->length > 1) || !config->hide_lone_tab);
452 if (((!tab_or_stack || container_is_floating(c)) && 536 if (((!tab_or_stack || container_is_floating(c)) &&
453 c->current.border != B_NORMAL) || 537 c->current.border != B_NORMAL) ||
454 c->fullscreen_mode != FULLSCREEN_NONE || 538 c->pending.fullscreen_mode != FULLSCREEN_NONE ||
455 c->workspace == NULL) { 539 c->pending.workspace == NULL) {
456 deco_rect->x = deco_rect->y = deco_rect->width = deco_rect->height = 0; 540 deco_rect->x = deco_rect->y = deco_rect->width = deco_rect->height = 0;
457 return; 541 return;
458 } 542 }
459 543
460 if (c->parent) { 544 if (c->pending.parent) {
461 deco_rect->x = c->x - c->parent->x; 545 deco_rect->x = c->pending.x - c->pending.parent->pending.x;
462 deco_rect->y = c->y - c->parent->y; 546 deco_rect->y = c->pending.y - c->pending.parent->pending.y;
463 } else { 547 } else {
464 deco_rect->x = c->x - c->workspace->x; 548 deco_rect->x = c->pending.x - c->pending.workspace->x;
465 deco_rect->y = c->y - c->workspace->y; 549 deco_rect->y = c->pending.y - c->pending.workspace->y;
466 } 550 }
467 deco_rect->width = c->width; 551 deco_rect->width = c->pending.width;
468 deco_rect->height = container_titlebar_height(); 552 deco_rect->height = container_titlebar_height();
469 553
470 if (!container_is_floating(c)) { 554 if (!container_is_floating(c)) {
471 if (parent_layout == L_TABBED) { 555 if (parent_layout == L_TABBED) {
472 deco_rect->width = c->parent 556 deco_rect->width = c->pending.parent
473 ? c->parent->width / c->parent->children->length 557 ? c->pending.parent->pending.width / c->pending.parent->pending.children->length
474 : c->workspace->width / c->workspace->tiling->length; 558 : c->pending.workspace->width / c->pending.workspace->tiling->length;
475 deco_rect->x += deco_rect->width * container_sibling_index(c); 559 deco_rect->x += deco_rect->width * container_sibling_index(c);
476 } else if (parent_layout == L_STACKED) { 560 } else if (parent_layout == L_STACKED) {
477 if (!c->view) { 561 if (!c->view) {
@@ -494,10 +578,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)); 578 json_object_object_add(object, "visible", json_object_new_boolean(visible));
495 579
496 struct wlr_box window_box = { 580 struct wlr_box window_box = {
497 c->content_x - c->x, 581 c->pending.content_x - c->pending.x,
498 (c->current.border == B_PIXEL) ? c->current.border_thickness : 0, 582 (c->current.border == B_PIXEL) ? c->pending.content_y - c->pending.y : 0,
499 c->content_width, 583 c->pending.content_width,
500 c->content_height 584 c->pending.content_height
501 }; 585 };
502 586
503 json_object_object_add(object, "window_rect", ipc_json_create_rect(&window_box)); 587 json_object_object_add(object, "window_rect", ipc_json_create_rect(&window_box));
@@ -539,6 +623,16 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
539 623
540 json_object_object_add(object, "idle_inhibitors", idle_inhibitors); 624 json_object_object_add(object, "idle_inhibitors", idle_inhibitors);
541 625
626 enum wp_content_type_v1_type content_type = WP_CONTENT_TYPE_V1_TYPE_NONE;
627 if (c->view->surface != NULL) {
628 content_type = wlr_surface_get_content_type_v1(server.content_type_manager_v1,
629 c->view->surface);
630 }
631 if (content_type != WP_CONTENT_TYPE_V1_TYPE_NONE) {
632 json_object_object_add(object, "content_type",
633 json_object_new_string(ipc_json_content_type_description(content_type)));
634 }
635
542#if HAVE_XWAYLAND 636#if HAVE_XWAYLAND
543 if (c->view->type == SWAY_VIEW_XWAYLAND) { 637 if (c->view->type == SWAY_VIEW_XWAYLAND) {
544 json_object_object_add(object, "window", 638 json_object_object_add(object, "window",
@@ -583,24 +677,35 @@ 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) { 677static void ipc_json_describe_container(struct sway_container *c, json_object *object) {
584 json_object_object_add(object, "name", 678 json_object_object_add(object, "name",
585 c->title ? json_object_new_string(c->title) : NULL); 679 c->title ? json_object_new_string(c->title) : NULL);
586 json_object_object_add(object, "type", 680 bool floating = container_is_floating(c);
587 json_object_new_string(container_is_floating(c) ? "floating_con" : "con")); 681 if (floating) {
682 json_object_object_add(object, "type",
683 json_object_new_string("floating_con"));
684 }
588 685
589 json_object_object_add(object, "layout", 686 json_object_object_add(object, "layout",
590 json_object_new_string( 687 json_object_new_string(
591 ipc_json_layout_description(c->layout))); 688 ipc_json_layout_description(c->pending.layout)));
592 689
593 json_object_object_add(object, "orientation", 690 json_object_object_add(object, "orientation",
594 json_object_new_string( 691 json_object_new_string(
595 ipc_json_orientation_description(c->layout))); 692 ipc_json_orientation_description(c->pending.layout)));
596 693
597 bool urgent = c->view ? 694 bool urgent = c->view ?
598 view_is_urgent(c->view) : container_has_urgent_child(c); 695 view_is_urgent(c->view) : container_has_urgent_child(c);
599 json_object_object_add(object, "urgent", json_object_new_boolean(urgent)); 696 json_object_object_add(object, "urgent", json_object_new_boolean(urgent));
600 json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky)); 697 json_object_object_add(object, "sticky", json_object_new_boolean(c->is_sticky));
601 698
699 // sway doesn't track the floating reason, so we can't use "auto_on" or "user_off"
700 json_object_object_add(object, "floating",
701 json_object_new_string(floating ? "user_on" : "auto_off"));
702
602 json_object_object_add(object, "fullscreen_mode", 703 json_object_object_add(object, "fullscreen_mode",
603 json_object_new_int(c->fullscreen_mode)); 704 json_object_new_int(c->pending.fullscreen_mode));
705
706 // sway doesn't track if window was resized in scratchpad, so we can't use "changed"
707 json_object_object_add(object, "scratchpad_state",
708 json_object_new_string(!c->scratchpad ? "none" : "fresh"));
604 709
605 struct sway_node *parent = node_get_parent(&c->node); 710 struct sway_node *parent = node_get_parent(&c->node);
606 struct wlr_box parent_box = {0, 0, 0, 0}; 711 struct wlr_box parent_box = {0, 0, 0, 0};
@@ -610,8 +715,8 @@ static void ipc_json_describe_container(struct sway_container *c, json_object *o
610 } 715 }
611 716
612 if (parent_box.width != 0 && parent_box.height != 0) { 717 if (parent_box.width != 0 && parent_box.height != 0) {
613 double percent = ((double)c->width / parent_box.width) 718 double percent = ((double)c->pending.width / parent_box.width)
614 * ((double)c->height / parent_box.height); 719 * ((double)c->pending.height / parent_box.height);
615 json_object_object_add(object, "percent", json_object_new_double(percent)); 720 json_object_object_add(object, "percent", json_object_new_double(percent));
616 } 721 }
617 722
@@ -692,15 +797,14 @@ json_object *ipc_json_describe_node(struct sway_node *node) {
692 }; 797 };
693 seat_for_each_node(seat, focus_inactive_children_iterator, &data); 798 seat_for_each_node(seat, focus_inactive_children_iterator, &data);
694 799
695 json_object *object = ipc_json_create_node( 800 json_object *object = ipc_json_create_node((int)node->id,
696 (int)node->id, name, focused, focus, &box); 801 ipc_json_node_type_description(node->type), name, focused, focus, &box);
697 802
698 switch (node->type) { 803 switch (node->type) {
699 case N_ROOT: 804 case N_ROOT:
700 ipc_json_describe_root(root, object);
701 break; 805 break;
702 case N_OUTPUT: 806 case N_OUTPUT:
703 ipc_json_describe_output(node->sway_output, object); 807 ipc_json_describe_enabled_output(node->sway_output, object);
704 break; 808 break;
705 case N_CONTAINER: 809 case N_CONTAINER:
706 ipc_json_describe_container(node->sway_container, object); 810 ipc_json_describe_container(node->sway_container, object);
@@ -743,10 +847,10 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) {
743 } 847 }
744 break; 848 break;
745 case N_CONTAINER: 849 case N_CONTAINER:
746 if (node->sway_container->children) { 850 if (node->sway_container->pending.children) {
747 for (i = 0; i < node->sway_container->children->length; ++i) { 851 for (i = 0; i < node->sway_container->pending.children->length; ++i) {
748 struct sway_container *child = 852 struct sway_container *child =
749 node->sway_container->children->items[i]; 853 node->sway_container->pending.children->items[i];
750 json_object_array_add(children, 854 json_object_array_add(children,
751 ipc_json_describe_node_recursive(&child->node)); 855 ipc_json_describe_node_recursive(&child->node));
752 } 856 }
@@ -758,6 +862,7 @@ json_object *ipc_json_describe_node_recursive(struct sway_node *node) {
758 return object; 862 return object;
759} 863}
760 864
865#if WLR_HAS_LIBINPUT_BACKEND
761static json_object *describe_libinput_device(struct libinput_device *device) { 866static json_object *describe_libinput_device(struct libinput_device *device) {
762 json_object *object = json_object_new_object(); 867 json_object *object = json_object_new_object();
763 868
@@ -841,6 +946,11 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
841 case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE: 946 case LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE:
842 accel_profile = "adaptive"; 947 accel_profile = "adaptive";
843 break; 948 break;
949#if HAVE_LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM
950 case LIBINPUT_CONFIG_ACCEL_PROFILE_CUSTOM:
951 accel_profile = "custom";
952 break;
953#endif
844 } 954 }
845 json_object_object_add(object, "accel_profile", 955 json_object_object_add(object, "accel_profile",
846 json_object_new_string(accel_profile)); 956 json_object_new_string(accel_profile));
@@ -920,6 +1030,17 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
920 uint32_t button = libinput_device_config_scroll_get_button(device); 1030 uint32_t button = libinput_device_config_scroll_get_button(device);
921 json_object_object_add(object, "scroll_button", 1031 json_object_object_add(object, "scroll_button",
922 json_object_new_int(button)); 1032 json_object_new_int(button));
1033 const char *lock = "unknown";
1034 switch (libinput_device_config_scroll_get_button_lock(device)) {
1035 case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_ENABLED:
1036 lock = "enabled";
1037 break;
1038 case LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED:
1039 lock = "disabled";
1040 break;
1041 }
1042 json_object_object_add(object, "scroll_button_lock",
1043 json_object_new_string(lock));
923 } 1044 }
924 } 1045 }
925 1046
@@ -936,6 +1057,19 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
936 json_object_object_add(object, "dwt", json_object_new_string(dwt)); 1057 json_object_object_add(object, "dwt", json_object_new_string(dwt));
937 } 1058 }
938 1059
1060 if (libinput_device_config_dwtp_is_available(device)) {
1061 const char *dwtp = "unknown";
1062 switch (libinput_device_config_dwtp_get_enabled(device)) {
1063 case LIBINPUT_CONFIG_DWTP_ENABLED:
1064 dwtp = "enabled";
1065 break;
1066 case LIBINPUT_CONFIG_DWTP_DISABLED:
1067 dwtp = "disabled";
1068 break;
1069 }
1070 json_object_object_add(object, "dwtp", json_object_new_string(dwtp));
1071 }
1072
939 if (libinput_device_config_calibration_has_matrix(device)) { 1073 if (libinput_device_config_calibration_has_matrix(device)) {
940 float matrix[6]; 1074 float matrix[6];
941 libinput_device_config_calibration_get_matrix(device, matrix); 1075 libinput_device_config_calibration_get_matrix(device, matrix);
@@ -950,6 +1084,7 @@ static json_object *describe_libinput_device(struct libinput_device *device) {
950 1084
951 return object; 1085 return object;
952} 1086}
1087#endif
953 1088
954json_object *ipc_json_describe_input(struct sway_input_device *device) { 1089json_object *ipc_json_describe_input(struct sway_input_device *device) {
955 if (!(sway_assert(device, "Device must not be null"))) { 1090 if (!(sway_assert(device, "Device must not be null"))) {
@@ -962,19 +1097,21 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) {
962 json_object_new_string(device->identifier)); 1097 json_object_new_string(device->identifier));
963 json_object_object_add(object, "name", 1098 json_object_object_add(object, "name",
964 json_object_new_string(device->wlr_device->name)); 1099 json_object_new_string(device->wlr_device->name));
965 json_object_object_add(object, "vendor",
966 json_object_new_int(device->wlr_device->vendor));
967 json_object_object_add(object, "product",
968 json_object_new_int(device->wlr_device->product));
969 json_object_object_add(object, "type", 1100 json_object_object_add(object, "type",
970 json_object_new_string( 1101 json_object_new_string(
971 input_device_get_type(device))); 1102 input_device_get_type(device)));
972 1103
973 if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) { 1104 if (device->wlr_device->type == WLR_INPUT_DEVICE_KEYBOARD) {
974 struct wlr_keyboard *keyboard = device->wlr_device->keyboard; 1105 struct wlr_keyboard *keyboard =
1106 wlr_keyboard_from_input_device(device->wlr_device);
975 struct xkb_keymap *keymap = keyboard->keymap; 1107 struct xkb_keymap *keymap = keyboard->keymap;
976 struct xkb_state *state = keyboard->xkb_state; 1108 struct xkb_state *state = keyboard->xkb_state;
977 1109
1110 json_object_object_add(object, "repeat_delay",
1111 json_object_new_int(keyboard->repeat_info.delay));
1112 json_object_object_add(object, "repeat_rate",
1113 json_object_new_int(keyboard->repeat_info.rate));
1114
978 json_object *layouts_arr = json_object_new_array(); 1115 json_object *layouts_arr = json_object_new_array();
979 json_object_object_add(object, "xkb_layout_names", layouts_arr); 1116 json_object_object_add(object, "xkb_layout_names", layouts_arr);
980 1117
@@ -996,12 +1133,29 @@ json_object *ipc_json_describe_input(struct sway_input_device *device) {
996 } 1133 }
997 } 1134 }
998 1135
1136 if (device->wlr_device->type == WLR_INPUT_DEVICE_POINTER) {
1137 struct input_config *ic = input_device_get_config(device);
1138 float scroll_factor = 1.0f;
1139 if (ic != NULL && !isnan(ic->scroll_factor) &&
1140 ic->scroll_factor != FLT_MIN) {
1141 scroll_factor = ic->scroll_factor;
1142 }
1143 json_object_object_add(object, "scroll_factor",
1144 json_object_new_double(scroll_factor));
1145 }
1146
1147#if WLR_HAS_LIBINPUT_BACKEND
999 if (wlr_input_device_is_libinput(device->wlr_device)) { 1148 if (wlr_input_device_is_libinput(device->wlr_device)) {
1000 struct libinput_device *libinput_dev; 1149 struct libinput_device *libinput_dev;
1001 libinput_dev = wlr_libinput_get_device_handle(device->wlr_device); 1150 libinput_dev = wlr_libinput_get_device_handle(device->wlr_device);
1002 json_object_object_add(object, "libinput", 1151 json_object_object_add(object, "libinput",
1003 describe_libinput_device(libinput_dev)); 1152 describe_libinput_device(libinput_dev));
1153 json_object_object_add(object, "vendor",
1154 json_object_new_int(libinput_device_get_id_vendor(libinput_dev)));
1155 json_object_object_add(object, "product",
1156 json_object_new_int(libinput_device_get_id_product(libinput_dev)));
1004 } 1157 }
1158#endif
1005 1159
1006 return object; 1160 return object;
1007} 1161}
@@ -1109,7 +1263,9 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
1109 json_object_object_add(json, "verbose", 1263 json_object_object_add(json, "verbose",
1110 json_object_new_boolean(bar->verbose)); 1264 json_object_new_boolean(bar->verbose));
1111 json_object_object_add(json, "pango_markup", 1265 json_object_object_add(json, "pango_markup",
1112 json_object_new_boolean(bar->pango_markup)); 1266 json_object_new_boolean(bar->pango_markup == PANGO_MARKUP_DEFAULT
1267 ? config->pango_markup
1268 : bar->pango_markup));
1113 1269
1114 json_object *colors = json_object_new_object(); 1270 json_object *colors = json_object_new_object();
1115 json_object_object_add(colors, "background", 1271 json_object_object_add(colors, "background",
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index aad9a7b5..7f353c0e 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -1,5 +1,4 @@
1// See https://i3wm.org/docs/ipc.html for protocol information 1// See https://i3wm.org/docs/ipc.html for protocol information
2#define _POSIX_C_SOURCE 200112L
3#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
4#include <assert.h> 3#include <assert.h>
5#include <errno.h> 4#include <errno.h>
@@ -56,7 +55,6 @@ struct ipc_client {
56 enum ipc_command_type pending_type; 55 enum ipc_command_type pending_type;
57}; 56};
58 57
59struct sockaddr_un *ipc_user_sockaddr(void);
60int ipc_handle_connection(int fd, uint32_t mask, void *data); 58int ipc_handle_connection(int fd, uint32_t mask, void *data);
61int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data); 59int 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); 60int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data);
@@ -150,7 +148,6 @@ struct sockaddr_un *ipc_user_sockaddr(void) {
150int ipc_handle_connection(int fd, uint32_t mask, void *data) { 148int ipc_handle_connection(int fd, uint32_t mask, void *data) {
151 (void) fd; 149 (void) fd;
152 struct sway_server *server = data; 150 struct sway_server *server = data;
153 sway_log(SWAY_DEBUG, "Event on IPC listening socket");
154 assert(mask == WL_EVENT_READABLE); 151 assert(mask == WL_EVENT_READABLE);
155 152
156 int client_fd = accept(ipc_socket, NULL, NULL); 153 int client_fd = accept(ipc_socket, NULL, NULL);
@@ -211,13 +208,10 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
211 } 208 }
212 209
213 if (mask & WL_EVENT_HANGUP) { 210 if (mask & WL_EVENT_HANGUP) {
214 sway_log(SWAY_DEBUG, "Client %d hung up", client->fd);
215 ipc_client_disconnect(client); 211 ipc_client_disconnect(client);
216 return 0; 212 return 0;
217 } 213 }
218 214
219 sway_log(SWAY_DEBUG, "Client %d readable", client->fd);
220
221 int read_available; 215 int read_available;
222 if (ioctl(client_fd, FIONREAD, &read_available) == -1) { 216 if (ioctl(client_fd, FIONREAD, &read_available) == -1) {
223 sway_log_errno(SWAY_INFO, "Unable to read IPC socket buffer size"); 217 sway_log_errno(SWAY_INFO, "Unable to read IPC socket buffer size");
@@ -513,6 +507,20 @@ void ipc_event_input(const char *change, struct sway_input_device *device) {
513 json_object_put(json); 507 json_object_put(json);
514} 508}
515 509
510void ipc_event_output(void) {
511 if (!ipc_has_event_listeners(IPC_EVENT_OUTPUT)) {
512 return;
513 }
514 sway_log(SWAY_DEBUG, "Sending output event");
515
516 json_object *json = json_object_new_object();
517 json_object_object_add(json, "change", json_object_new_string("unspecified"));
518
519 const char *json_string = json_object_to_json_string(json);
520 ipc_send_event(json_string, IPC_EVENT_OUTPUT);
521 json_object_put(json);
522}
523
516int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { 524int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
517 struct ipc_client *client = data; 525 struct ipc_client *client = data;
518 526
@@ -523,7 +531,6 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
523 } 531 }
524 532
525 if (mask & WL_EVENT_HANGUP) { 533 if (mask & WL_EVENT_HANGUP) {
526 sway_log(SWAY_DEBUG, "Client %d hung up", client->fd);
527 ipc_client_disconnect(client); 534 ipc_client_disconnect(client);
528 return 0; 535 return 0;
529 } 536 }
@@ -532,8 +539,6 @@ int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
532 return 0; 539 return 0;
533 } 540 }
534 541
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); 542 ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len);
538 543
539 if (written == -1 && errno == EAGAIN) { 544 if (written == -1 && errno == EAGAIN) {
@@ -687,11 +692,17 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
687 } 692 }
688 struct sway_output *output; 693 struct sway_output *output;
689 wl_list_for_each(output, &root->all_outputs, link) { 694 wl_list_for_each(output, &root->all_outputs, link) {
690 if (!output->enabled && output != root->noop_output) { 695 if (!output->enabled && output != root->fallback_output) {
691 json_object_array_add(outputs, 696 json_object_array_add(outputs,
692 ipc_json_describe_disabled_output(output)); 697 ipc_json_describe_disabled_output(output));
693 } 698 }
694 } 699 }
700
701 for (int i = 0; i < root->non_desktop_outputs->length; i++) {
702 struct sway_output_non_desktop *non_desktop_output = root->non_desktop_outputs->items[i];
703 json_object_array_add(outputs, ipc_json_describe_non_desktop_output(non_desktop_output));
704 }
705
695 const char *json_string = json_object_to_json_string(outputs); 706 const char *json_string = json_object_to_json_string(outputs);
696 ipc_send_reply(client, payload_type, json_string, 707 ipc_send_reply(client, payload_type, json_string,
697 (uint32_t)strlen(json_string)); 708 (uint32_t)strlen(json_string));
@@ -727,6 +738,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)); 738 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i));
728 if (strcmp(event_type, "workspace") == 0) { 739 if (strcmp(event_type, "workspace") == 0) {
729 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE); 740 client->subscribed_events |= event_mask(IPC_EVENT_WORKSPACE);
741 } else if (strcmp(event_type, "output") == 0) {
742 client->subscribed_events |= event_mask(IPC_EVENT_OUTPUT);
730 } else if (strcmp(event_type, "barconfig_update") == 0) { 743 } else if (strcmp(event_type, "barconfig_update") == 0) {
731 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); 744 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE);
732 } else if (strcmp(event_type, "bar_state_update") == 0) { 745 } else if (strcmp(event_type, "bar_state_update") == 0) {
@@ -911,7 +924,6 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
911 924
912exit_cleanup: 925exit_cleanup:
913 free(buf); 926 free(buf);
914 return;
915} 927}
916 928
917bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type, 929bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type,
@@ -955,7 +967,5 @@ bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_typ
955 ipc_client_handle_writable, client); 967 ipc_client_handle_writable, client);
956 } 968 }
957 969
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; 970 return true;
961} 971}
diff --git a/sway/lock.c b/sway/lock.c
new file mode 100644
index 00000000..289e8ca4
--- /dev/null
+++ b/sway/lock.c
@@ -0,0 +1,354 @@
1#include <assert.h>
2#include <wlr/types/wlr_scene.h>
3#include <wlr/types/wlr_session_lock_v1.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..1c4939aa 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <getopt.h> 1#include <getopt.h>
3#include <pango/pangocairo.h> 2#include <pango/pangocairo.h>
4#include <signal.h> 3#include <signal.h>
@@ -6,12 +5,14 @@
6#include <stdio.h> 5#include <stdio.h>
7#include <stdlib.h> 6#include <stdlib.h>
8#include <string.h> 7#include <string.h>
8#include <sys/resource.h>
9#include <sys/stat.h> 9#include <sys/stat.h>
10#include <sys/types.h> 10#include <sys/types.h>
11#include <sys/wait.h> 11#include <sys/wait.h>
12#include <sys/un.h> 12#include <sys/un.h>
13#include <unistd.h> 13#include <unistd.h>
14#include <wlr/util/log.h> 14#include <wlr/util/log.h>
15#include <wlr/version.h>
15#include "sway/commands.h" 16#include "sway/commands.h"
16#include "sway/config.h" 17#include "sway/config.h"
17#include "sway/server.h" 18#include "sway/server.h"
@@ -26,6 +27,7 @@
26 27
27static bool terminate_request = false; 28static bool terminate_request = false;
28static int exit_value = 0; 29static int exit_value = 0;
30static struct rlimit original_nofile_rlimit = {0};
29struct sway_server server = {0}; 31struct sway_server server = {0};
30struct sway_debug debug = {0}; 32struct sway_debug debug = {0};
31 33
@@ -46,81 +48,6 @@ void sig_handler(int signal) {
46 sway_terminate(EXIT_SUCCESS); 48 sway_terminate(EXIT_SUCCESS);
47} 49}
48 50
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) { 51void run_as_ipc_client(char *command, char *socket_path) {
125 int socketfd = ipc_open_socket(socket_path); 52 int socketfd = ipc_open_socket(socket_path);
126 uint32_t len = strlen(command); 53 uint32_t len = strlen(command);
@@ -184,33 +111,49 @@ static void log_kernel(void) {
184 pclose(f); 111 pclose(f);
185} 112}
186 113
187 114static bool detect_suid(void) {
188static bool drop_permissions(void) { 115 if (geteuid() != 0 && getegid() != 0) {
189 if (getuid() != geteuid() || getgid() != getegid()) { 116 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 } 117 }
200 if (setgid(0) != -1 || setuid(0) != -1) { 118
201 sway_log(SWAY_ERROR, "Unable to drop root (we shouldn't be able to " 119 if (getuid() == geteuid() && getgid() == getegid()) {
202 "restore it after setuid), refusing to start");
203 return false; 120 return false;
204 } 121 }
122
123 sway_log(SWAY_ERROR, "SUID operation is no longer supported, refusing to start. "
124 "This check will be removed in a future release.");
205 return true; 125 return true;
206} 126}
207 127
128static void increase_nofile_limit(void) {
129 if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) {
130 sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: "
131 "getrlimit(NOFILE) failed");
132 return;
133 }
134
135 struct rlimit new_rlimit = original_nofile_rlimit;
136 new_rlimit.rlim_cur = new_rlimit.rlim_max;
137 if (setrlimit(RLIMIT_NOFILE, &new_rlimit) != 0) {
138 sway_log_errno(SWAY_ERROR, "Failed to bump max open files limit: "
139 "setrlimit(NOFILE) failed");
140 sway_log(SWAY_INFO, "Running with %d max open files",
141 (int)original_nofile_rlimit.rlim_cur);
142 }
143}
144
145void restore_nofile_limit(void) {
146 if (original_nofile_rlimit.rlim_cur == 0) {
147 return;
148 }
149 if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) {
150 sway_log_errno(SWAY_ERROR, "Failed to restore max open files limit: "
151 "setrlimit(NOFILE) failed");
152 }
153}
154
208void enable_debug_flag(const char *flag) { 155void enable_debug_flag(const char *flag) {
209 if (strcmp(flag, "damage=highlight") == 0) { 156 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; 157 debug.noatomic = true;
215 } else if (strcmp(flag, "txn-wait") == 0) { 158 } else if (strcmp(flag, "txn-wait") == 0) {
216 debug.txn_wait = true; 159 debug.txn_wait = true;
@@ -218,6 +161,8 @@ void enable_debug_flag(const char *flag) {
218 debug.txn_timings = true; 161 debug.txn_timings = true;
219 } else if (strncmp(flag, "txn-timeout=", 12) == 0) { 162 } else if (strncmp(flag, "txn-timeout=", 12) == 0) {
220 server.txn_timeout_ms = atoi(&flag[12]); 163 server.txn_timeout_ms = atoi(&flag[12]);
164 } else if (strcmp(flag, "legacy-wl-drm") == 0) {
165 debug.legacy_wl_drm = true;
221 } else { 166 } else {
222 sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag); 167 sway_log(SWAY_ERROR, "Unknown debug flag: %s", flag);
223 } 168 }
@@ -242,36 +187,35 @@ static void handle_wlr_log(enum wlr_log_importance importance,
242 _sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args); 187 _sway_vlog(convert_wlr_log_importance(importance), sway_fmt, args);
243} 188}
244 189
190static const struct option long_options[] = {
191 {"help", no_argument, NULL, 'h'},
192 {"config", required_argument, NULL, 'c'},
193 {"validate", no_argument, NULL, 'C'},
194 {"debug", no_argument, NULL, 'd'},
195 {"version", no_argument, NULL, 'v'},
196 {"verbose", no_argument, NULL, 'V'},
197 {"get-socketpath", no_argument, NULL, 'p'},
198 {"unsupported-gpu", no_argument, NULL, 'u'},
199 {0, 0, 0, 0}
200};
201
202static const char usage[] =
203 "Usage: sway [options] [command]\n"
204 "\n"
205 " -h, --help Show help message and quit.\n"
206 " -c, --config <config> Specify a config file.\n"
207 " -C, --validate Check the validity of the config file, then exit.\n"
208 " -d, --debug Enables full logging, including debug information.\n"
209 " -v, --version Show the version number and quit.\n"
210 " -V, --verbose Enables more verbose logging.\n"
211 " --get-socketpath Gets the IPC socket path and prints it, then exits.\n"
212 "\n";
213
245int main(int argc, char **argv) { 214int main(int argc, char **argv) {
246 static int verbose = 0, debug = 0, validate = 0, allow_unsupported_gpu = 0; 215 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 216
261 char *config_path = NULL; 217 char *config_path = NULL;
262 218
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; 219 int c;
276 while (1) { 220 while (1) {
277 int option_index = 0; 221 int option_index = 0;
@@ -289,25 +233,25 @@ int main(int argc, char **argv) {
289 config_path = strdup(optarg); 233 config_path = strdup(optarg);
290 break; 234 break;
291 case 'C': // validate 235 case 'C': // validate
292 validate = 1; 236 validate = true;
293 break; 237 break;
294 case 'd': // debug 238 case 'd': // debug
295 debug = 1; 239 debug = true;
296 break; 240 break;
297 case 'D': // extended debug options 241 case 'D': // extended debug options
298 enable_debug_flag(optarg); 242 enable_debug_flag(optarg);
299 break; 243 break;
300 case 'u': 244 case 'u':
301 allow_unsupported_gpu = 1; 245 allow_unsupported_gpu = true;
302 break; 246 break;
303 case 'v': // version 247 case 'v': // version
304 printf("sway version " SWAY_VERSION "\n"); 248 printf("sway version " SWAY_VERSION "\n");
305 exit(EXIT_SUCCESS); 249 exit(EXIT_SUCCESS);
306 break; 250 break;
307 case 'V': // verbose 251 case 'V': // verbose
308 verbose = 1; 252 verbose = true;
309 break; 253 break;
310 case 'p': ; // --get-socketpath 254 case 'p': // --get-socketpath
311 if (getenv("SWAYSOCK")) { 255 if (getenv("SWAYSOCK")) {
312 printf("%s\n", getenv("SWAYSOCK")); 256 printf("%s\n", getenv("SWAYSOCK"));
313 exit(EXIT_SUCCESS); 257 exit(EXIT_SUCCESS);
@@ -322,6 +266,11 @@ int main(int argc, char **argv) {
322 } 266 }
323 } 267 }
324 268
269 // SUID operation is deprecated, so block it for now.
270 if (detect_suid()) {
271 exit(EXIT_FAILURE);
272 }
273
325 // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the 274 // Since wayland requires XDG_RUNTIME_DIR to be set, abort with just the
326 // clear error message (when not running as an IPC client). 275 // clear error message (when not running as an IPC client).
327 if (!getenv("XDG_RUNTIME_DIR") && optind == argc) { 276 if (!getenv("XDG_RUNTIME_DIR") && optind == argc) {
@@ -344,11 +293,10 @@ int main(int argc, char **argv) {
344 } 293 }
345 294
346 sway_log(SWAY_INFO, "Sway version " SWAY_VERSION); 295 sway_log(SWAY_INFO, "Sway version " SWAY_VERSION);
296 sway_log(SWAY_INFO, "wlroots version " WLR_VERSION_STR);
347 log_kernel(); 297 log_kernel();
348 log_distro(); 298 log_distro();
349 log_env(); 299 log_env();
350 detect_proprietary(allow_unsupported_gpu);
351 detect_raspi();
352 300
353 if (optind < argc) { // Behave as IPC client 301 if (optind < argc) { // Behave as IPC client
354 if (optind != 1) { 302 if (optind != 1) {
@@ -361,9 +309,6 @@ int main(int argc, char **argv) {
361 "`sway -d 2>sway.log`."); 309 "`sway -d 2>sway.log`.");
362 exit(EXIT_FAILURE); 310 exit(EXIT_FAILURE);
363 } 311 }
364 if (!drop_permissions()) {
365 exit(EXIT_FAILURE);
366 }
367 char *socket_path = getenv("SWAYSOCK"); 312 char *socket_path = getenv("SWAYSOCK");
368 if (!socket_path) { 313 if (!socket_path) {
369 sway_log(SWAY_ERROR, "Unable to retrieve socket path"); 314 sway_log(SWAY_ERROR, "Unable to retrieve socket path");
@@ -375,14 +320,7 @@ int main(int argc, char **argv) {
375 return 0; 320 return 0;
376 } 321 }
377 322
378 if (!server_privileged_prepare(&server)) { 323 increase_nofile_limit();
379 return 1;
380 }
381
382 if (!drop_permissions()) {
383 server_fini(&server);
384 exit(EXIT_FAILURE);
385 }
386 324
387 // handle SIGTERM signals 325 // handle SIGTERM signals
388 signal(SIGTERM, sig_handler); 326 signal(SIGTERM, sig_handler);
@@ -393,12 +331,14 @@ int main(int argc, char **argv) {
393 331
394 sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION); 332 sway_log(SWAY_INFO, "Starting sway version " SWAY_VERSION);
395 333
396 root = root_create();
397
398 if (!server_init(&server)) { 334 if (!server_init(&server)) {
399 return 1; 335 return 1;
400 } 336 }
401 337
338 if (server.linux_dmabuf_v1) {
339 wlr_scene_set_linux_dmabuf_v1(root->root_scene, server.linux_dmabuf_v1);
340 }
341
402 if (validate) { 342 if (validate) {
403 bool valid = load_main_config(config_path, false, true); 343 bool valid = load_main_config(config_path, false, true);
404 free(config_path); 344 free(config_path);
@@ -413,6 +353,8 @@ int main(int argc, char **argv) {
413 goto shutdown; 353 goto shutdown;
414 } 354 }
415 355
356 set_rr_scheduling();
357
416 if (!server_start(&server)) { 358 if (!server_start(&server)) {
417 sway_terminate(EXIT_FAILURE); 359 sway_terminate(EXIT_FAILURE);
418 goto shutdown; 360 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..180d3a6b 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -7,25 +6,45 @@
7#include <wlr/backend.h> 6#include <wlr/backend.h>
8#include <wlr/backend/headless.h> 7#include <wlr/backend/headless.h>
9#include <wlr/backend/multi.h> 8#include <wlr/backend/multi.h>
10#include <wlr/backend/noop.h> 9#include <wlr/config.h>
11#include <wlr/backend/session.h> 10#include <wlr/render/allocator.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_data_device.h>
17#include <wlr/types/wlr_drm.h>
15#include <wlr/types/wlr_export_dmabuf_v1.h> 18#include <wlr/types/wlr_export_dmabuf_v1.h>
19#include <wlr/types/wlr_ext_foreign_toplevel_list_v1.h>
20#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
21#include <wlr/types/wlr_fractional_scale_v1.h>
16#include <wlr/types/wlr_gamma_control_v1.h> 22#include <wlr/types/wlr_gamma_control_v1.h>
17#include <wlr/types/wlr_idle.h> 23#include <wlr/types/wlr_idle_notify_v1.h>
18#include <wlr/types/wlr_layer_shell_v1.h> 24#include <wlr/types/wlr_layer_shell_v1.h>
25#include <wlr/types/wlr_linux_dmabuf_v1.h>
26#include <wlr/types/wlr_output_management_v1.h>
27#include <wlr/types/wlr_output_power_management_v1.h>
19#include <wlr/types/wlr_pointer_constraints_v1.h> 28#include <wlr/types/wlr_pointer_constraints_v1.h>
29#include <wlr/types/wlr_presentation_time.h>
20#include <wlr/types/wlr_primary_selection_v1.h> 30#include <wlr/types/wlr_primary_selection_v1.h>
21#include <wlr/types/wlr_relative_pointer_v1.h> 31#include <wlr/types/wlr_relative_pointer_v1.h>
22#include <wlr/types/wlr_screencopy_v1.h> 32#include <wlr/types/wlr_screencopy_v1.h>
33#include <wlr/types/wlr_security_context_v1.h>
23#include <wlr/types/wlr_server_decoration.h> 34#include <wlr/types/wlr_server_decoration.h>
35#include <wlr/types/wlr_session_lock_v1.h>
36#include <wlr/types/wlr_single_pixel_buffer_v1.h>
37#include <wlr/types/wlr_subcompositor.h>
24#include <wlr/types/wlr_tablet_v2.h> 38#include <wlr/types/wlr_tablet_v2.h>
25#include <wlr/types/wlr_viewporter.h> 39#include <wlr/types/wlr_viewporter.h>
26#include <wlr/types/wlr_xcursor_manager.h> 40#include <wlr/types/wlr_xcursor_manager.h>
41#include <wlr/types/wlr_xdg_activation_v1.h>
27#include <wlr/types/wlr_xdg_decoration_v1.h> 42#include <wlr/types/wlr_xdg_decoration_v1.h>
43#include <wlr/types/wlr_xdg_foreign_registry.h>
44#include <wlr/types/wlr_xdg_foreign_v1.h>
45#include <wlr/types/wlr_xdg_foreign_v2.h>
28#include <wlr/types/wlr_xdg_output_v1.h> 46#include <wlr/types/wlr_xdg_output_v1.h>
47#include <xf86drm.h>
29#include "config.h" 48#include "config.h"
30#include "list.h" 49#include "list.h"
31#include "log.h" 50#include "log.h"
@@ -34,41 +53,221 @@
34#include "sway/input/input-manager.h" 53#include "sway/input/input-manager.h"
35#include "sway/output.h" 54#include "sway/output.h"
36#include "sway/server.h" 55#include "sway/server.h"
56#include "sway/input/cursor.h"
37#include "sway/tree/root.h" 57#include "sway/tree/root.h"
58
38#if HAVE_XWAYLAND 59#if HAVE_XWAYLAND
60#include <wlr/xwayland/shell.h>
39#include "sway/xwayland.h" 61#include "sway/xwayland.h"
40#endif 62#endif
41 63
42bool server_privileged_prepare(struct sway_server *server) { 64#if WLR_HAS_DRM_BACKEND
43 sway_log(SWAY_DEBUG, "Preparing Wayland server initialization"); 65#include <wlr/types/wlr_drm_lease_v1.h>
66#endif
67
68#define SWAY_XDG_SHELL_VERSION 5
69#define SWAY_LAYER_SHELL_VERSION 4
70#define SWAY_FOREIGN_TOPLEVEL_LIST_VERSION 1
71
72bool allow_unsupported_gpu = false;
73
74#if WLR_HAS_DRM_BACKEND
75static void handle_drm_lease_request(struct wl_listener *listener, void *data) {
76 /* We only offer non-desktop outputs, but in the future we might want to do
77 * more logic here. */
78
79 struct wlr_drm_lease_request_v1 *req = data;
80 struct wlr_drm_lease_v1 *lease = wlr_drm_lease_request_v1_grant(req);
81 if (!lease) {
82 sway_log(SWAY_ERROR, "Failed to grant lease request");
83 wlr_drm_lease_request_v1_reject(req);
84 }
85}
86#endif
87
88static bool is_privileged(const struct wl_global *global) {
89#if WLR_HAS_DRM_BACKEND
90 if (server.drm_lease_manager != NULL) {
91 struct wlr_drm_lease_device_v1 *drm_lease_dev;
92 wl_list_for_each(drm_lease_dev, &server.drm_lease_manager->devices, link) {
93 if (drm_lease_dev->global == global) {
94 return true;
95 }
96 }
97 }
98#endif
99
100 return
101 global == server.output_manager_v1->global ||
102 global == server.output_power_manager_v1->global ||
103 global == server.input_method->global ||
104 global == server.foreign_toplevel_list->global ||
105 global == server.foreign_toplevel_manager->global ||
106 global == server.data_control_manager_v1->global ||
107 global == server.screencopy_manager_v1->global ||
108 global == server.export_dmabuf_manager_v1->global ||
109 global == server.security_context_manager_v1->global ||
110 global == server.gamma_control_manager_v1->global ||
111 global == server.layer_shell->global ||
112 global == server.session_lock.manager->global ||
113 global == server.input->keyboard_shortcuts_inhibit->global ||
114 global == server.input->virtual_keyboard->global ||
115 global == server.input->virtual_pointer->global ||
116 global == server.input->transient_seat_manager->global;
117}
118
119static bool filter_global(const struct wl_client *client,
120 const struct wl_global *global, void *data) {
121#if HAVE_XWAYLAND
122 struct wlr_xwayland *xwayland = server.xwayland.wlr_xwayland;
123 if (xwayland && global == xwayland->shell_v1->global) {
124 return xwayland->server != NULL && client == xwayland->server->client;
125 }
126#endif
127
128 // Restrict usage of privileged protocols to unsandboxed clients
129 // TODO: add a way for users to configure an allow-list
130 const struct wlr_security_context_v1_state *security_context =
131 wlr_security_context_manager_v1_lookup_client(
132 server.security_context_manager_v1, (struct wl_client *)client);
133 if (is_privileged(global)) {
134 return security_context == NULL;
135 }
136
137 return true;
138}
139
140static void detect_proprietary(struct wlr_backend *backend, void *data) {
141 int drm_fd = wlr_backend_get_drm_fd(backend);
142 if (drm_fd < 0) {
143 return;
144 }
145
146 drmVersion *version = drmGetVersion(drm_fd);
147 if (version == NULL) {
148 sway_log(SWAY_ERROR, "drmGetVersion() failed");
149 return;
150 }
151
152 bool is_unsupported = false;
153 if (strcmp(version->name, "nvidia-drm") == 0) {
154 is_unsupported = true;
155 sway_log(SWAY_ERROR, "!!! Proprietary Nvidia drivers are in use !!!");
156 if (!allow_unsupported_gpu) {
157 sway_log(SWAY_ERROR, "Use Nouveau instead");
158 }
159 }
160
161 if (strcmp(version->name, "evdi") == 0) {
162 is_unsupported = true;
163 sway_log(SWAY_ERROR, "!!! Proprietary DisplayLink drivers are in use !!!");
164 }
165
166 if (!allow_unsupported_gpu && is_unsupported) {
167 sway_log(SWAY_ERROR,
168 "Proprietary drivers are NOT supported. To launch sway anyway, "
169 "launch with --unsupported-gpu and DO NOT report issues.");
170 exit(EXIT_FAILURE);
171 }
172
173 drmFreeVersion(version);
174}
175
176static void handle_renderer_lost(struct wl_listener *listener, void *data) {
177 struct sway_server *server = wl_container_of(listener, server, renderer_lost);
178
179 sway_log(SWAY_INFO, "Re-creating renderer after GPU reset");
180
181 struct wlr_renderer *renderer = wlr_renderer_autocreate(server->backend);
182 if (renderer == NULL) {
183 sway_log(SWAY_ERROR, "Unable to create renderer");
184 return;
185 }
186
187 struct wlr_allocator *allocator =
188 wlr_allocator_autocreate(server->backend, renderer);
189 if (allocator == NULL) {
190 sway_log(SWAY_ERROR, "Unable to create allocator");
191 wlr_renderer_destroy(renderer);
192 return;
193 }
194
195 struct wlr_renderer *old_renderer = server->renderer;
196 struct wlr_allocator *old_allocator = server->allocator;
197 server->renderer = renderer;
198 server->allocator = allocator;
199
200 wl_list_remove(&server->renderer_lost.link);
201 wl_signal_add(&server->renderer->events.lost, &server->renderer_lost);
202
203 wlr_compositor_set_renderer(server->compositor, renderer);
204
205 for (int i = 0; i < root->outputs->length; ++i) {
206 struct sway_output *output = root->outputs->items[i];
207 wlr_output_init_render(output->wlr_output,
208 server->allocator, server->renderer);
209 }
210
211 wlr_allocator_destroy(old_allocator);
212 wlr_renderer_destroy(old_renderer);
213}
214
215bool server_init(struct sway_server *server) {
216 sway_log(SWAY_DEBUG, "Initializing Wayland server");
44 server->wl_display = wl_display_create(); 217 server->wl_display = wl_display_create();
45 server->wl_event_loop = wl_display_get_event_loop(server->wl_display); 218 server->wl_event_loop = wl_display_get_event_loop(server->wl_display);
46 server->backend = wlr_backend_autocreate(server->wl_display);
47 219
220 wl_display_set_global_filter(server->wl_display, filter_global, NULL);
221
222 root = root_create(server->wl_display);
223
224 server->backend = wlr_backend_autocreate(server->wl_event_loop, &server->session);
48 if (!server->backend) { 225 if (!server->backend) {
49 sway_log(SWAY_ERROR, "Unable to create backend"); 226 sway_log(SWAY_ERROR, "Unable to create backend");
50 return false; 227 return false;
51 } 228 }
52 return true;
53}
54 229
55bool server_init(struct sway_server *server) { 230 wlr_multi_for_each_backend(server->backend, detect_proprietary, NULL);
56 sway_log(SWAY_DEBUG, "Initializing Wayland server");
57 231
58 struct wlr_renderer *renderer = wlr_backend_get_renderer(server->backend); 232 server->renderer = wlr_renderer_autocreate(server->backend);
59 assert(renderer); 233 if (!server->renderer) {
234 sway_log(SWAY_ERROR, "Failed to create renderer");
235 return false;
236 }
60 237
61 wlr_renderer_init_wl_display(renderer, server->wl_display); 238 server->renderer_lost.notify = handle_renderer_lost;
239 wl_signal_add(&server->renderer->events.lost, &server->renderer_lost);
62 240
63 server->compositor = wlr_compositor_create(server->wl_display, renderer); 241 wlr_renderer_init_wl_shm(server->renderer, server->wl_display);
64 server->compositor_new_surface.notify = handle_compositor_new_surface; 242
65 wl_signal_add(&server->compositor->events.new_surface, 243 if (wlr_renderer_get_texture_formats(server->renderer, WLR_BUFFER_CAP_DMABUF) != NULL) {
66 &server->compositor_new_surface); 244 server->linux_dmabuf_v1 = wlr_linux_dmabuf_v1_create_with_renderer(
245 server->wl_display, 4, server->renderer);
246 if (debug.legacy_wl_drm) {
247 wlr_drm_create(server->wl_display, server->renderer);
248 }
249 }
250
251 server->allocator = wlr_allocator_autocreate(server->backend,
252 server->renderer);
253 if (!server->allocator) {
254 sway_log(SWAY_ERROR, "Failed to create allocator");
255 return false;
256 }
257
258 server->compositor = wlr_compositor_create(server->wl_display, 6,
259 server->renderer);
260
261 wlr_subcompositor_create(server->wl_display);
67 262
68 server->data_device_manager = 263 server->data_device_manager =
69 wlr_data_device_manager_create(server->wl_display); 264 wlr_data_device_manager_create(server->wl_display);
70 265
71 wlr_gamma_control_manager_v1_create(server->wl_display); 266 server->gamma_control_manager_v1 =
267 wlr_gamma_control_manager_v1_create(server->wl_display);
268 server->gamma_control_set_gamma.notify = handle_gamma_control_set_gamma;
269 wl_signal_add(&server->gamma_control_manager_v1->events.set_gamma,
270 &server->gamma_control_set_gamma);
72 271
73 server->new_output.notify = handle_new_output; 272 server->new_output.notify = handle_new_output;
74 wl_signal_add(&server->backend->events.new_output, &server->new_output); 273 wl_signal_add(&server->backend->events.new_output, &server->new_output);
@@ -78,19 +277,20 @@ bool server_init(struct sway_server *server) {
78 277
79 wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout); 278 wlr_xdg_output_manager_v1_create(server->wl_display, root->output_layout);
80 279
81 server->idle = wlr_idle_create(server->wl_display); 280 server->idle_notifier_v1 = wlr_idle_notifier_v1_create(server->wl_display);
82 server->idle_inhibit_manager_v1 = 281 sway_idle_inhibit_manager_v1_init();
83 sway_idle_inhibit_manager_v1_create(server->wl_display, server->idle);
84 282
85 server->layer_shell = wlr_layer_shell_v1_create(server->wl_display); 283 server->layer_shell = wlr_layer_shell_v1_create(server->wl_display,
284 SWAY_LAYER_SHELL_VERSION);
86 wl_signal_add(&server->layer_shell->events.new_surface, 285 wl_signal_add(&server->layer_shell->events.new_surface,
87 &server->layer_shell_surface); 286 &server->layer_shell_surface);
88 server->layer_shell_surface.notify = handle_layer_shell_surface; 287 server->layer_shell_surface.notify = handle_layer_shell_surface;
89 288
90 server->xdg_shell = wlr_xdg_shell_create(server->wl_display); 289 server->xdg_shell = wlr_xdg_shell_create(server->wl_display,
91 wl_signal_add(&server->xdg_shell->events.new_surface, 290 SWAY_XDG_SHELL_VERSION);
92 &server->xdg_shell_surface); 291 wl_signal_add(&server->xdg_shell->events.new_toplevel,
93 server->xdg_shell_surface.notify = handle_xdg_shell_surface; 292 &server->xdg_shell_toplevel);
293 server->xdg_shell_toplevel.notify = handle_xdg_shell_toplevel;
94 294
95 server->tablet_v2 = wlr_tablet_v2_create(server->wl_display); 295 server->tablet_v2 = wlr_tablet_v2_create(server->wl_display);
96 296
@@ -121,8 +321,7 @@ bool server_init(struct sway_server *server) {
121 wl_signal_add(&server->pointer_constraints->events.new_constraint, 321 wl_signal_add(&server->pointer_constraints->events.new_constraint,
122 &server->pointer_constraint); 322 &server->pointer_constraint);
123 323
124 server->presentation = 324 wlr_presentation_create(server->wl_display, server->backend);
125 wlr_presentation_create(server->wl_display, server->backend);
126 325
127 server->output_manager_v1 = 326 server->output_manager_v1 =
128 wlr_output_manager_v1_create(server->wl_display); 327 wlr_output_manager_v1_create(server->wl_display);
@@ -141,19 +340,62 @@ bool server_init(struct sway_server *server) {
141 &server->output_power_manager_set_mode); 340 &server->output_power_manager_set_mode);
142 server->input_method = wlr_input_method_manager_v2_create(server->wl_display); 341 server->input_method = wlr_input_method_manager_v2_create(server->wl_display);
143 server->text_input = wlr_text_input_manager_v3_create(server->wl_display); 342 server->text_input = wlr_text_input_manager_v3_create(server->wl_display);
343 server->foreign_toplevel_list =
344 wlr_ext_foreign_toplevel_list_v1_create(server->wl_display, SWAY_FOREIGN_TOPLEVEL_LIST_VERSION);
144 server->foreign_toplevel_manager = 345 server->foreign_toplevel_manager =
145 wlr_foreign_toplevel_manager_v1_create(server->wl_display); 346 wlr_foreign_toplevel_manager_v1_create(server->wl_display);
146 347
147 wlr_export_dmabuf_manager_v1_create(server->wl_display); 348 sway_session_lock_init();
148 wlr_screencopy_manager_v1_create(server->wl_display); 349
149 wlr_data_control_manager_v1_create(server->wl_display); 350#if WLR_HAS_DRM_BACKEND
150 wlr_primary_selection_v1_device_manager_create(server->wl_display); 351 server->drm_lease_manager=
352 wlr_drm_lease_v1_manager_create(server->wl_display, server->backend);
353 if (server->drm_lease_manager) {
354 server->drm_lease_request.notify = handle_drm_lease_request;
355 wl_signal_add(&server->drm_lease_manager->events.request,
356 &server->drm_lease_request);
357 } else {
358 sway_log(SWAY_DEBUG, "Failed to create wlr_drm_lease_device_v1");
359 sway_log(SWAY_INFO, "VR will not be available");
360 }
361#endif
362
363 server->export_dmabuf_manager_v1 = wlr_export_dmabuf_manager_v1_create(server->wl_display);
364 server->screencopy_manager_v1 = wlr_screencopy_manager_v1_create(server->wl_display);
365 server->data_control_manager_v1 = wlr_data_control_manager_v1_create(server->wl_display);
366 server->security_context_manager_v1 = wlr_security_context_manager_v1_create(server->wl_display);
151 wlr_viewporter_create(server->wl_display); 367 wlr_viewporter_create(server->wl_display);
368 wlr_single_pixel_buffer_manager_v1_create(server->wl_display);
369 server->content_type_manager_v1 =
370 wlr_content_type_manager_v1_create(server->wl_display, 1);
371 wlr_fractional_scale_manager_v1_create(server->wl_display, 1);
372
373 struct wlr_xdg_foreign_registry *foreign_registry =
374 wlr_xdg_foreign_registry_create(server->wl_display);
375 wlr_xdg_foreign_v1_create(server->wl_display, foreign_registry);
376 wlr_xdg_foreign_v2_create(server->wl_display, foreign_registry);
377
378 server->xdg_activation_v1 = wlr_xdg_activation_v1_create(server->wl_display);
379 server->xdg_activation_v1_request_activate.notify =
380 xdg_activation_v1_handle_request_activate;
381 wl_signal_add(&server->xdg_activation_v1->events.request_activate,
382 &server->xdg_activation_v1_request_activate);
383 server->xdg_activation_v1_new_token.notify =
384 xdg_activation_v1_handle_new_token;
385 wl_signal_add(&server->xdg_activation_v1->events.new_token,
386 &server->xdg_activation_v1_new_token);
387
388 struct wlr_cursor_shape_manager_v1 *cursor_shape_manager =
389 wlr_cursor_shape_manager_v1_create(server->wl_display, 1);
390 server->request_set_cursor_shape.notify = handle_request_set_cursor_shape;
391 wl_signal_add(&cursor_shape_manager->events.request_set_shape, &server->request_set_cursor_shape);
392
393 wl_list_init(&server->pending_launcher_ctxs);
152 394
153 // Avoid using "wayland-0" as display socket 395 // Avoid using "wayland-0" as display socket
154 char name_candidate[16]; 396 char name_candidate[16];
155 for (int i = 1; i <= 32; ++i) { 397 for (unsigned int i = 1; i <= 32; ++i) {
156 sprintf(name_candidate, "wayland-%d", i); 398 snprintf(name_candidate, sizeof(name_candidate), "wayland-%u", i);
157 if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) { 399 if (wl_display_add_socket(server->wl_display, name_candidate) >= 0) {
158 server->socket = strdup(name_candidate); 400 server->socket = strdup(name_candidate);
159 break; 401 break;
@@ -166,27 +408,26 @@ bool server_init(struct sway_server *server) {
166 return false; 408 return false;
167 } 409 }
168 410
169 server->noop_backend = wlr_noop_backend_create(server->wl_display); 411 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) { 412 if (!server->headless_backend) {
177 sway_log(SWAY_INFO, "Failed to create secondary headless backend, " 413 sway_log(SWAY_ERROR, "Failed to create secondary headless backend");
178 "starting without it"); 414 wlr_backend_destroy(server->backend);
415 return false;
179 } else { 416 } else {
180 wlr_multi_backend_add(server->backend, server->headless_backend); 417 wlr_multi_backend_add(server->backend, server->headless_backend);
181 } 418 }
182 419
420 struct wlr_output *wlr_output =
421 wlr_headless_add_output(server->headless_backend, 800, 600);
422 wlr_output_set_name(wlr_output, "FALLBACK");
423 root->fallback_output = output_create(wlr_output);
424
183 // This may have been set already via -Dtxn-timeout 425 // This may have been set already via -Dtxn-timeout
184 if (!server->txn_timeout_ms) { 426 if (!server->txn_timeout_ms) {
185 server->txn_timeout_ms = 200; 427 server->txn_timeout_ms = 200;
186 } 428 }
187 429
188 server->dirty_nodes = create_list(); 430 server->dirty_nodes = create_list();
189 server->transactions = create_list();
190 431
191 server->input = input_manager_create(server); 432 server->input = input_manager_create(server);
192 input_manager_get_default_seat(); // create seat0 433 input_manager_get_default_seat(); // create seat0
@@ -200,9 +441,9 @@ void server_fini(struct sway_server *server) {
200 wlr_xwayland_destroy(server->xwayland.wlr_xwayland); 441 wlr_xwayland_destroy(server->xwayland.wlr_xwayland);
201#endif 442#endif
202 wl_display_destroy_clients(server->wl_display); 443 wl_display_destroy_clients(server->wl_display);
444 wlr_backend_destroy(server->backend);
203 wl_display_destroy(server->wl_display); 445 wl_display_destroy(server->wl_display);
204 list_free(server->dirty_nodes); 446 list_free(server->dirty_nodes);
205 list_free(server->transactions);
206} 447}
207 448
208bool server_start(struct sway_server *server) { 449bool server_start(struct sway_server *server) {
@@ -231,6 +472,10 @@ bool server_start(struct sway_server *server) {
231 } 472 }
232#endif 473#endif
233 474
475 if (config->primary_selection) {
476 wlr_primary_selection_v1_device_manager_create(server->wl_display);
477 }
478
234 sway_log(SWAY_INFO, "Starting backend on wayland display '%s'", 479 sway_log(SWAY_INFO, "Starting backend on wayland display '%s'",
235 server->socket); 480 server->socket);
236 if (!wlr_backend_start(server->backend)) { 481 if (!wlr_backend_start(server->backend)) {
@@ -238,6 +483,7 @@ bool server_start(struct sway_server *server) {
238 wlr_backend_destroy(server->backend); 483 wlr_backend_destroy(server->backend);
239 return false; 484 return false;
240 } 485 }
486
241 return true; 487 return true;
242} 488}
243 489
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..2f697248 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,8 +374,14 @@ 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
379|- floating
380: string
381: Floating state of container. Can be either "auto_off" or "user_on"
382|- scratchpad_state
383: string
384: Whether the window is in the scratchpad. Can be either "none" or "fresh"
375|- app_id 385|- app_id
376: string 386: string
377: (Only views) For an xdg-shell view, the name of the application, if set. 387: (Only views) For an xdg-shell view, the name of the application, if set.
@@ -1036,7 +1046,7 @@ An object with a single string property containing the contents of the config
1036*Example Reply:* 1046*Example Reply:*
1037``` 1047```
1038{ 1048{
1039 "config": "set $mod Mod4\nbindsym $mod+q exit\n" 1049 "config": "set $mod Mod4\\nbindsym $mod+q exit\\n"
1040} 1050}
1041``` 1051```
1042 1052
@@ -1131,6 +1141,9 @@ following properties:
1131|- xkb_active_layout_index 1141|- xkb_active_layout_index
1132: integer 1142: integer
1133: (Only keyboards) The index of the active keyboard layout in use 1143: (Only keyboards) The index of the active keyboard layout in use
1144|- scroll_factor
1145: floating
1146: (Only pointers) Multiplier applied on scroll event values.
1134|- libinput 1147|- libinput
1135: object 1148: object
1136: (Only libinput devices) An object describing the current device settings. 1149: (Only libinput devices) An object describing the current device settings.
@@ -1188,9 +1201,16 @@ following properties will be included for devices that support them:
1188: int 1201: int
1189: The scroll button to use when _scroll_method_ is _on_button_down_. This 1202: The scroll button to use when _scroll_method_ is _on_button_down_. This
1190 will be given as an input event code 1203 will be given as an input event code
1204|- scroll_button_lock
1205: string
1206: Whether scroll button lock is enabled. It can be _enabled_ or _disabled_
1191|- dwt 1207|- dwt
1192: string 1208: string
1193: Whether disable-while-typing is enabled. It can be _enabled_ or _disabled_ 1209: Whether disable-while-typing is enabled. It can be _enabled_ or _disabled_
1210|- dwtp
1211: string
1212: Whether disable-while-trackpointing is enabled. It can be _enabled_ or
1213 _disabled_
1194|- calibration_matrix 1214|- calibration_matrix
1195: array 1215: array
1196: An array of 6 floats representing the calibration matrix for absolute 1216: An array of 6 floats representing the calibration matrix for absolute
@@ -1230,7 +1250,8 @@ following properties will be included for devices that support them:
1230 "click_method": "button_areas", 1250 "click_method": "button_areas",
1231 "middle_emulation": "disabled", 1251 "middle_emulation": "disabled",
1232 "scroll_method": "edge", 1252 "scroll_method": "edge",
1233 "dwt": "enabled" 1253 "dwt": "enabled",
1254 "dwtp": "enabled"
1234 } 1255 }
1235 }, 1256 },
1236 { 1257 {
@@ -1357,7 +1378,8 @@ one seat. Each object has the following properties:
1357 "click_method": "button_areas", 1378 "click_method": "button_areas",
1358 "middle_emulation": "disabled", 1379 "middle_emulation": "disabled",
1359 "scroll_method": "edge", 1380 "scroll_method": "edge",
1360 "dwt": "enabled" 1381 "dwt": "enabled",
1382 "dwtp": "enabled"
1361 } 1383 }
1362 }, 1384 },
1363 { 1385 {
@@ -1433,6 +1455,9 @@ available:
1433: workspace 1455: workspace
1434:[ Sent whenever an event involving a workspace occurs such as initialization 1456:[ Sent whenever an event involving a workspace occurs such as initialization
1435 of a new workspace or a different workspace gains focus 1457 of a new workspace or a different workspace gains focus
1458|- 0x80000001
1459: output
1460: Sent when outputs are updated
1436|- 0x80000002 1461|- 0x80000002
1437: mode 1462: mode
1438: Sent whenever the binding mode changes 1463: Sent whenever the binding mode changes
@@ -1553,6 +1578,20 @@ The following change types are currently available:
1553} 1578}
1554``` 1579```
1555 1580
1581## 0x80000001. OUTPUT
1582
1583Sent whenever an output is added, removed, or its configuration is changed.
1584The event is a single object with the property _change_, which is a string
1585containing the reason for the change. Currently, the only value for _change_ is
1586_unspecified_.
1587
1588*Example Event:*
1589```
1590{
1591 "change": "unspecified"
1592}
1593```
1594
1556## 0x80000002. MODE 1595## 0x80000002. MODE
1557 1596
1558Sent whenever the binding mode changes. The event consists of a single object 1597Sent 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..7d088d5d 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
@@ -62,13 +72,11 @@ must be separated by one space. For example:
62 72
63*output* <name> scale <factor> 73*output* <name> scale <factor>
64 Scales the specified output by the specified scale _factor_. An integer is 74 Scales the specified output by the specified scale _factor_. An integer is
65 recommended, but fractional values are also supported. If a fractional 75 recommended, but fractional values are also supported. You may be better
66 value are specified, be warned that it is not possible to faithfully 76 served by setting an integer scale factor and adjusting the font size of
67 represent the contents of your windows - they will be rendered at the next 77 your applications to taste. HiDPI isn't supported with Xwayland clients
68 highest integer scale factor and downscaled. You may be better served by 78 (windows will blur). A fractional scale may be slightly adjusted to match
69 setting an integer scale factor and adjusting the font size of your 79 requirements of the protocol.
70 applications to taste. HiDPI isn't supported with Xwayland clients (windows
71 will blur).
72 80
73*output* <name> scale_filter linear|nearest|smart 81*output* <name> scale_filter linear|nearest|smart
74 Indicates how to scale application buffers that are rendered at a scale 82 Indicates how to scale application buffers that are rendered at a scale
@@ -109,12 +117,20 @@ must be separated by one space. For example:
109 Enables or disables the specified output (all outputs are enabled by 117 Enables or disables the specified output (all outputs are enabled by
110 default). 118 default).
111 119
120 As opposed to the _power_ command, the output will lose its current
121 workspace and windows.
122
112*output* <name> toggle 123*output* <name> toggle
113 Toggle the specified output. 124 Toggle the specified output.
114 125
115*output* <name> dpms on|off 126*output* <name> power on|off|toggle
116 Enables or disables the specified output via DPMS. To turn an output off 127 Turns on or off the specified output.
117 (ie. blank the screen but keep workspaces as-is), one can set DPMS to off. 128
129 As opposed to the _enable_ and _disable_ commands, the output keeps its
130 current workspaces and windows.
131
132*output* <name> dpms on|off|toggle
133 Deprecated. Alias for _power_.
118 134
119*output* <name> max_render_time off|<msec> 135*output* <name> max_render_time off|<msec>
120 Controls when sway composites the output, as a positive number of 136 Controls when sway composites the output, as a positive number of
@@ -142,11 +158,26 @@ must be separated by one space. For example:
142 Enables or disables adaptive synchronization (often referred to as Variable 158 Enables or disables adaptive synchronization (often referred to as Variable
143 Refresh Rate, or by the vendor-specific names FreeSync/G-Sync). 159 Refresh Rate, or by the vendor-specific names FreeSync/G-Sync).
144 160
145 Adaptive sync allows clients to submit frames a little to late without 161 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 162 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 163 adaptive sync can improve latency, but can cause flickering on some
148 hardware. 164 hardware.
149 165
166*output* <name> render_bit_depth 8|10
167 Controls the color channel bit depth at which frames are rendered; the
168 default is currently 8 bits per channel.
169
170 Setting higher values will not have an effect if hardware and software lack
171 support for such bit depths. Successfully increasing the render bit depth
172 will not necessarily increase the bit depth of the frames sent to a display.
173 An increased render bit depth may provide smoother rendering of gradients,
174 and screenshots which can more precisely store the colors of programs
175 which display high bit depth colors.
176
177 Warnings: this can break screenshot/screencast programs which have not been
178 updated to work with different bit depths. This command is experimental,
179 and may be removed or changed in the future.
180
150# SEE ALSO 181# SEE ALSO
151 182
152*sway*(5) *sway-input*(5) 183*sway*(5) *sway-input*(5)
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 02592b5f..9f823947 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
@@ -393,6 +400,12 @@ runtime.
393 only be available for that group. By default, if you overwrite a binding, 400 only be available for that group. By default, if you overwrite a binding,
394 swaynag will give you a warning. To silence this, use the _--no-warn_ flag. 401 swaynag will give you a warning. To silence this, use the _--no-warn_ flag.
395 402
403 For specifying modifier keys, you can use the XKB modifier names _Shift_,
404 _Lock_ (for Caps Lock), _Control_, _Mod1_ (for Alt), _Mod2_ (for Num Lock),
405 _Mod3_ (for XKB modifier Mod3), _Mod4_ (for the Logo key), and _Mod5_ (for
406 AltGr). In addition, you can use the aliases _Ctrl_ (for Control), _Alt_
407 (for Alt), and _Super_ (for the Logo key).
408
396 Unless the flag _--locked_ is set, the command will not be run when a 409 Unless the flag _--locked_ is set, the command will not be run when a
397 screen locking program is active. If there is a matching binding with 410 screen locking program is active. If there is a matching binding with
398 and without _--locked_, the one with will be preferred when locked and the 411 and without _--locked_, the one with will be preferred when locked and the
@@ -404,7 +417,7 @@ runtime.
404 a keyboard shortcuts inhibitor is active for the currently focused 417 a keyboard shortcuts inhibitor is active for the currently focused
405 window. Such inhibitors are usually requested by remote desktop and 418 window. Such inhibitors are usually requested by remote desktop and
406 virtualization software to enable the user to send keyboard shortcuts 419 virtualization software to enable the user to send keyboard shortcuts
407 to the remote or virtual session. The _--inhibited_ flag allows to 420 to the remote or virtual session. The _--inhibited_ flag allows one to
408 define bindings which will be exempt from pass-through to such 421 define bindings which will be exempt from pass-through to such
409 software. The same preference logic as for _--locked_ applies. 422 software. The same preference logic as for _--locked_ applies.
410 423
@@ -447,7 +460,8 @@ runtime.
447``` 460```
448 461
449 *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \ 462 *bindcode* [--whole-window] [--border] [--exclude-titlebar] [--release] \
450[--locked] [--input-device=<device>] [--no-warn] [Group<1-4>+]<code> <command> 463[--locked] [--input-device=<device>] [--no-warn] [--no-repeat] [--inhibited] \
464[Group<1-4>+]<code> <command>
451 is also available for binding with key/button codes instead of key/button names. 465 is also available for binding with key/button codes instead of key/button names.
452 466
453*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command> 467*bindswitch* [--locked] [--no-warn] [--reload] <switch>:<state> <command>
@@ -480,6 +494,62 @@ runtime.
480 bindswitch lid:toggle exec echo "Lid moved" 494 bindswitch lid:toggle exec echo "Lid moved"
481``` 495```
482 496
497*bindgesture* [--exact] [--input-device=<device>] [--no-warn] \
498<gesture>[:<fingers>][:directions] <command>
499 Binds _gesture_ to execute the sway command _command_ when detected.
500 Currently supports the _hold_, _pinch_ or _swipe_ gesture. Optionally
501 can be limited to bind to a certain number of _fingers_ or, for a
502 _pinch_ or _swipe_ gesture, to certain _directions_.
503
504[[ *type*
505:[ *fingers*
506:< *direction*
507| hold
508:- 1 - 5
509: none
510| swipe
511: 3 - 5
512: up, down, left, right
513| pinch
514: 2 - 5
515: all above + inward, outward, clockwise, counterclockwise
516
517 The _fingers_ can be limited to any sensible number or left empty to accept
518 any finger counts.
519 Valid directions are _up_, _down_, _left_ and _right_, as well as _inward_,
520 _outward_, _clockwise_, _counterclockwise_ for the _pinch_ gesture.
521 Multiple directions can be combined by a plus.
522
523 If a _input-device_ is given, the binding will only be executed for
524 that input device and will be executed instead of any binding that is
525 generic to all devices. By default, if you overwrite a binding,
526 swaynag will give you a warning. To silence this, use the _--no-warn_ flag.
527
528 The _--exact_ flag can be used to ensure a binding only matches when exactly
529 all specified directions are matched and nothing more. If there is matching
530 binding with _--exact_, it will be preferred.
531
532 The priority for matching bindings is as follows: input device, then
533 exact matches followed by matches with the highest number of matching
534 directions.
535
536 Gestures executed while the pointer is above a bar are not handled by sway.
537 See the respective documentation, e.g. *bindgesture* in *sway-bar*(5).
538
539 Example:
540```
541 # Allow switching between workspaces with left and right swipes
542 bindgesture swipe:right workspace prev
543 bindgesture swipe:left workspace next
544
545 # Allow container movements by pinching them
546 bindgesture pinch:inward+up move up
547 bindgesture pinch:inward+down move down
548 bindgesture pinch:inward+left move left
549 bindgesture pinch:inward+right move right
550
551```
552
483*client.background* <color> 553*client.background* <color>
484 This command is ignored and is only present for i3 compatibility. 554 This command is ignored and is only present for i3 compatibility.
485 555
@@ -497,6 +567,12 @@ runtime.
497 *client.focused_inactive* 567 *client.focused_inactive*
498 The most recently focused view within a container which is not focused. 568 The most recently focused view within a container which is not focused.
499 569
570 *client.focused_tab_title*
571 A view that has focused descendant container.
572 Tab or stack container title that is the parent of the focused container
573 but is not directly focused. Defaults to focused_inactive if not
574 specified and does not use the indicator and child_border colors.
575
500 *client.placeholder* 576 *client.placeholder*
501 Ignored (present for i3 compatibility). 577 Ignored (present for i3 compatibility).
502 578
@@ -552,6 +628,12 @@ The default colors are:
552: #ffffff 628: #ffffff
553: #484e50 629: #484e50
554: #5f676a 630: #5f676a
631| *focused_tab_title*
632: #333333
633: #5f676a
634: #ffffff
635: n/a
636: n/a
555| *unfocused* 637| *unfocused*
556: #333333 638: #333333
557: #222222 639: #222222
@@ -573,7 +655,8 @@ The default colors are:
573 655
574 656
575*default_border* normal|none|pixel [<n>] 657*default_border* normal|none|pixel [<n>]
576 Set default border style for new tiled windows. 658 Set default border style for new tiled windows. Config reload won't affect
659 existing windows, only newly created ones after the reload.
577 660
578*default_floating_border* normal|none|pixel [<n>] 661*default_floating_border* normal|none|pixel [<n>]
579 Set default border style for new floating windows. This only applies to 662 Set default border style for new floating windows. This only applies to
@@ -606,11 +689,11 @@ The default colors are:
606 after switching between workspaces. 689 after switching between workspaces.
607 690
608*focus_on_window_activation* smart|urgent|focus|none 691*focus_on_window_activation* smart|urgent|focus|none
609 This option determines what to do when an xwayland client requests 692 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 693 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. 694 _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 695 become focused only if it is already visible, otherwise the urgent state
613 visible, otherwise the urgent state will be set. Default is _urgent_. 696 will be set. Default is _urgent_.
614 697
615*focus_wrapping* yes|no|force|workspace 698*focus_wrapping* yes|no|force|workspace
616 This option determines what to do when attempting to focus over the edge 699 This option determines what to do when attempting to focus over the edge
@@ -630,14 +713,14 @@ The default colors are:
630 should be used instead. Regardless of whether pango markup is enabled, 713 should be used instead. Regardless of whether pango markup is enabled,
631 _font_ should be specified as a pango font description. For more 714 _font_ should be specified as a pango font description. For more
632 information on pango font descriptions, see 715 information on pango font descriptions, see
633 https://developer.gnome.org/pango/stable/pango-Fonts.html#pango-font-description-from-string 716 https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html#description
634 717
635*force_display_urgency_hint* <timeout> [ms] 718*force_display_urgency_hint* <timeout> [ms]
636 If an application on another workspace sets an urgency hint, switching to this 719 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 720 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 721 window decoration color would be immediately reset to *client.focused*. This
639 may make it unnecessarily hard to tell which window originally raised the 722 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. 723 event. This option allows one to set a _timeout_ in ms to delay the urgency hint reset.
641 724
642*titlebar_border_thickness* <thickness> 725*titlebar_border_thickness* <thickness>
643 Thickness of the titlebar border in pixels 726 Thickness of the titlebar border in pixels
@@ -690,9 +773,10 @@ The default colors are:
690 borders will only be enabled if the workspace has more than one visible 773 borders will only be enabled if the workspace has more than one visible
691 child and gaps equal to zero. 774 child and gaps equal to zero.
692 775
693*smart_gaps* on|off 776*smart_gaps* on|off|toggle|inverse_outer
694 If smart_gaps are _on_ gaps will only be enabled if a workspace has more 777 If smart_gaps are _on_ gaps will only be enabled if a workspace has more
695 than one child. 778 than one child. If smart_gaps are _inverse_outer_ outer gaps will only
779 be enabled if a workspace has exactly one child.
696 780
697*mark* --add|--replace [--toggle] <identifier> 781*mark* --add|--replace [--toggle] <identifier>
698 Marks are arbitrary labels that can be used to identify certain windows and 782 Marks are arbitrary labels that can be used to identify certain windows and
@@ -731,6 +815,10 @@ The default colors are:
731 dialog will not be rendered. If _leave_fullscreen_, the view will exit 815 dialog will not be rendered. If _leave_fullscreen_, the view will exit
732 fullscreen mode and the dialog will be rendered. 816 fullscreen mode and the dialog will be rendered.
733 817
818*primary_selection* enabled|disabled
819 Enable or disable the primary selection clipboard. May only be configured
820 at launch. Default is _enabled_.
821
734*set* $<name> <value> 822*set* $<name> <value>
735 Sets variable $_name_ to _value_. You can use the new variable in the 823 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 824 arguments of future commands. When the variable is used, it can be escaped
@@ -771,6 +859,11 @@ The default colors are:
771*unbindswitch* <switch>:<state> 859*unbindswitch* <switch>:<state>
772 Removes a binding for when <switch> changes to <state>. 860 Removes a binding for when <switch> changes to <state>.
773 861
862*unbindgesture* [--exact] [--input-device=<device>] \
863<gesture>[:<fingers>][:directions]
864 Removes a binding for the specified _gesture_, _fingers_
865 and _directions_ combination.
866
774*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \ 867*unbindsym* [--whole-window] [--border] [--exclude-titlebar] [--release] [--locked] \
775[--to-code] [--input-device=<device>] <key combo> 868[--to-code] [--input-device=<device>] <key combo>
776 Removes the binding for _key combo_ that was previously bound with the 869 Removes the binding for _key combo_ that was previously bound with the
@@ -876,6 +969,9 @@ properties in practice for your applications.
876 969
877The following attributes may be matched with: 970The following attributes may be matched with:
878 971
972*all*
973 Matches all windows.
974
879*app_id* 975*app_id*
880 Compare value against the app id. Can be a regular expression. If value is 976 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 977 \_\_focused\_\_, then the app id must be the same as that of the currently
@@ -884,7 +980,8 @@ The following attributes may be matched with:
884*class* 980*class*
885 Compare value against the window class. Can be a regular expression. If 981 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 982 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. 983 the currently focused window. _class_ are specific to X11 applications and
984 require XWayland.
888 985
889*con_id* 986*con_id*
890 Compare against the internal container ID, which you can find via IPC. If 987 Compare against the internal container ID, which you can find via IPC. If
@@ -898,12 +995,14 @@ The following attributes may be matched with:
898 Matches floating windows. 995 Matches floating windows.
899 996
900*id* 997*id*
901 Compare value against the X11 window ID. Must be numeric. 998 Compare value against the X11 window ID. Must be numeric. id is specific to
999 X11 applications and requires XWayland.
902 1000
903*instance* 1001*instance*
904 Compare value against the window instance. Can be a regular expression. If 1002 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 1003 value is \_\_focused\_\_, then the window instance must be the same as that
906 of the currently focused window. 1004 of the currently focused window. instance is specific to X11 applications and
1005 requires XWayland.
907 1006
908*pid* 1007*pid*
909 Compare value against the window's process ID. Must be numeric. 1008 Compare value against the window's process ID. Must be numeric.
@@ -928,12 +1027,14 @@ The following attributes may be matched with:
928*window_role* 1027*window_role*
929 Compare against the window role (WM_WINDOW_ROLE). Can be a regular 1028 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 1029 expression. If value is \_\_focused\_\_, then the window role must be the
931 same as that of the currently focused window. 1030 same as that of the currently focused window. window_role is specific to X11
1031 applications and requires XWayland.
932 1032
933*window_type* 1033*window_type*
934 Compare against the window type (\_NET_WM_WINDOW_TYPE). Possible values 1034 Compare against the window type (\_NET_WM_WINDOW_TYPE). Possible values
935 are normal, dialog, utility, toolbar, splash, menu, dropdown_menu, 1035 are normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
936 popup_menu, tooltip and notification. 1036 popup_menu, tooltip and notification. window_type is specific to X11
1037 applications and requires XWayland.
937 1038
938*workspace* 1039*workspace*
939 Compare against the workspace name for this view. Can be a regular 1040 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..5eba53ba
--- /dev/null
+++ b/sway/sway_text_node.c
@@ -0,0 +1,302 @@
1#include <drm_fourcc.h>
2#include <stdio.h>
3#include <stdlib.h>
4#include <string.h>
5#include <wlr/types/wlr_buffer.h>
6#include <wlr/interfaces/wlr_buffer.h>
7#include "cairo_util.h"
8#include "log.h"
9#include "pango.h"
10#include "sway/config.h"
11#include "sway/sway_text_node.h"
12
13struct cairo_buffer {
14 struct wlr_buffer base;
15 cairo_surface_t *surface;
16 cairo_t *cairo;
17};
18
19static void cairo_buffer_handle_destroy(struct wlr_buffer *wlr_buffer) {
20 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
21
22 cairo_surface_destroy(buffer->surface);
23 cairo_destroy(buffer->cairo);
24 free(buffer);
25}
26
27static bool cairo_buffer_handle_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
28 uint32_t flags, void **data, uint32_t *format, size_t *stride) {
29 struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
30 *data = cairo_image_surface_get_data(buffer->surface);
31 *stride = cairo_image_surface_get_stride(buffer->surface);
32 *format = DRM_FORMAT_ARGB8888;
33 return true;
34}
35
36static void cairo_buffer_handle_end_data_ptr_access(struct wlr_buffer *wlr_buffer) {
37 // This space is intentionally left blank
38}
39
40static const struct wlr_buffer_impl cairo_buffer_impl = {
41 .destroy = cairo_buffer_handle_destroy,
42 .begin_data_ptr_access = cairo_buffer_handle_begin_data_ptr_access,
43 .end_data_ptr_access = cairo_buffer_handle_end_data_ptr_access,
44};
45
46struct text_buffer {
47 struct wlr_scene_buffer *buffer_node;
48 char *text;
49 struct sway_text_node props;
50
51 bool visible;
52 float scale;
53 enum wl_output_subpixel subpixel;
54
55 struct wl_listener outputs_update;
56 struct wl_listener destroy;
57};
58
59static int get_text_width(struct sway_text_node *props) {
60 int width = props->width;
61 if (props->max_width) {
62 width = MIN(width, props->max_width);
63 }
64 return MAX(width, 0);
65}
66
67static void update_source_box(struct text_buffer *buffer) {
68 struct sway_text_node *props = &buffer->props;
69 struct wlr_fbox source_box = {
70 .x = 0,
71 .y = 0,
72 .width = ceil(get_text_width(props) * buffer->scale),
73 .height = ceil(props->height * buffer->scale),
74 };
75
76 wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box);
77}
78
79static void render_backing_buffer(struct text_buffer *buffer) {
80 if (!buffer->visible) {
81 return;
82 }
83
84 float scale = buffer->scale;
85 int width = ceil(buffer->props.width * scale);
86 int height = ceil(buffer->props.height * scale);
87 float *color = (float *)&buffer->props.color;
88 float *background = (float *)&buffer->props.background;
89 PangoContext *pango = NULL;
90
91 cairo_font_options_t *fo = cairo_font_options_create();
92 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
93 enum wl_output_subpixel subpixel = buffer->subpixel;
94 if (subpixel == WL_OUTPUT_SUBPIXEL_NONE || subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
95 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
96 } else {
97 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
98 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel));
99 }
100
101 cairo_surface_t *surface = cairo_image_surface_create(
102 CAIRO_FORMAT_ARGB32, width, height);
103 cairo_status_t status = cairo_surface_status(surface);
104 if (status != CAIRO_STATUS_SUCCESS) {
105 sway_log(SWAY_ERROR, "cairo_image_surface_create failed: %s",
106 cairo_status_to_string(status));
107 goto err;
108 }
109
110 struct cairo_buffer *cairo_buffer = calloc(1, sizeof(*cairo_buffer));
111 if (!cairo_buffer) {
112 sway_log(SWAY_ERROR, "cairo_buffer allocation failed");
113 goto err;
114 }
115
116 cairo_t *cairo = cairo_create(surface);
117 if (!cairo) {
118 sway_log(SWAY_ERROR, "cairo_create failed");
119 free(cairo_buffer);
120 goto err;
121 }
122
123 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
124 cairo_set_font_options(cairo, fo);
125 pango = pango_cairo_create_context(cairo);
126
127 cairo_set_source_rgba(cairo, background[0], background[1], background[2], background[3]);
128 cairo_rectangle(cairo, 0, 0, width, height);
129 cairo_fill(cairo);
130
131 cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]);
132 cairo_move_to(cairo, 0, (config->font_baseline - buffer->props.baseline) * scale);
133
134 render_text(cairo, config->font_description, scale, buffer->props.pango_markup,
135 "%s", buffer->text);
136
137 cairo_surface_flush(surface);
138
139 wlr_buffer_init(&cairo_buffer->base, &cairo_buffer_impl, width, height);
140 cairo_buffer->surface = surface;
141 cairo_buffer->cairo = cairo;
142
143 wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base);
144 wlr_buffer_drop(&cairo_buffer->base);
145 update_source_box(buffer);
146
147 pixman_region32_t opaque;
148 pixman_region32_init(&opaque);
149 if (background[3] == 1) {
150 pixman_region32_union_rect(&opaque, &opaque, 0, 0,
151 buffer->props.width, buffer->props.height);
152 }
153 wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque);
154 pixman_region32_fini(&opaque);
155
156err:
157 if (pango) g_object_unref(pango);
158 cairo_font_options_destroy(fo);
159}
160
161static void handle_outputs_update(struct wl_listener *listener, void *data) {
162 struct text_buffer *buffer = wl_container_of(listener, buffer, outputs_update);
163 struct wlr_scene_outputs_update_event *event = data;
164
165 float scale = 0;
166 enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN;
167
168 for (size_t i = 0; i < event->size; i++) {
169 struct wlr_scene_output *output = event->active[i];
170 if (subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) {
171 subpixel = output->output->subpixel;
172 } else if (subpixel != output->output->subpixel) {
173 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
174 }
175
176 if (scale != 0 && scale != output->output->scale) {
177 // drop down to gray scale if we encounter outputs with different
178 // scales or else we will have chromatic aberations
179 subpixel = WL_OUTPUT_SUBPIXEL_NONE;
180 }
181
182 if (scale < output->output->scale) {
183 scale = output->output->scale;
184 }
185 }
186
187 buffer->visible = event->size > 0;
188
189 if (scale != buffer->scale || subpixel != buffer->subpixel) {
190 buffer->scale = scale;
191 buffer->subpixel = subpixel;
192 render_backing_buffer(buffer);
193 }
194}
195
196static void handle_destroy(struct wl_listener *listener, void *data) {
197 struct text_buffer *buffer = wl_container_of(listener, buffer, destroy);
198
199 wl_list_remove(&buffer->outputs_update.link);
200 wl_list_remove(&buffer->destroy.link);
201
202 free(buffer->text);
203 free(buffer);
204}
205
206static void text_calc_size(struct text_buffer *buffer) {
207 struct sway_text_node *props = &buffer->props;
208
209 cairo_t *c = cairo_create(NULL);
210 if (!c) {
211 sway_log(SWAY_ERROR, "cairo_t allocation failed");
212 return;
213 }
214
215 cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
216 get_text_size(c, config->font_description, &props->width, NULL,
217 &props->baseline, 1, props->pango_markup, "%s", buffer->text);
218 cairo_destroy(c);
219
220 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
221 get_text_width(props), props->height);
222}
223
224struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent,
225 char *text, float color[4], bool pango_markup) {
226 struct text_buffer *buffer = calloc(1, sizeof(*buffer));
227 if (!buffer) {
228 return NULL;
229 }
230
231 struct wlr_scene_buffer *node = wlr_scene_buffer_create(parent, NULL);
232 if (!node) {
233 free(buffer);
234 return NULL;
235 }
236
237 buffer->buffer_node = node;
238 buffer->props.node = &node->node;
239 buffer->text = strdup(text);
240 if (!buffer->text) {
241 free(buffer);
242 wlr_scene_node_destroy(&node->node);
243 return NULL;
244 }
245
246 buffer->props.height = config->font_height;
247 buffer->props.pango_markup = pango_markup;
248 memcpy(&buffer->props.color, color, sizeof(*color) * 4);
249
250 buffer->destroy.notify = handle_destroy;
251 wl_signal_add(&node->node.events.destroy, &buffer->destroy);
252 buffer->outputs_update.notify = handle_outputs_update;
253 wl_signal_add(&node->events.outputs_update, &buffer->outputs_update);
254
255 text_calc_size(buffer);
256
257 return &buffer->props;
258}
259
260void sway_text_node_set_color(struct sway_text_node *node, float color[4]) {
261 if (memcmp(&node->color, color, sizeof(*color) * 4) == 0) {
262 return;
263 }
264
265 memcpy(&node->color, color, sizeof(*color) * 4);
266 struct text_buffer *buffer = wl_container_of(node, buffer, props);
267
268 render_backing_buffer(buffer);
269}
270
271void sway_text_node_set_text(struct sway_text_node *node, char *text) {
272 struct text_buffer *buffer = wl_container_of(node, buffer, props);
273 if (strcmp(buffer->text, text) == 0) {
274 return;
275 }
276
277 char *new_text = strdup(text);
278 if (!new_text) {
279 return;
280 }
281
282 free(buffer->text);
283 buffer->text = new_text;
284
285 text_calc_size(buffer);
286 render_backing_buffer(buffer);
287}
288
289void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) {
290 struct text_buffer *buffer = wl_container_of(node, buffer, props);
291 buffer->props.max_width = max_width;
292 wlr_scene_buffer_set_dest_size(buffer->buffer_node,
293 get_text_width(&buffer->props), buffer->props.height);
294 update_source_box(buffer);
295 render_backing_buffer(buffer);
296}
297
298void sway_text_node_set_background(struct sway_text_node *node, float background[4]) {
299 struct text_buffer *buffer = wl_container_of(node, buffer, props);
300 memcpy(&node->background, background, sizeof(*background) * 4);
301 render_backing_buffer(buffer);
302}
diff --git a/sway/swaynag.c b/sway/swaynag.c
index db5a919a..bc5e23ea 100644
--- a/sway/swaynag.c
+++ b/sway/swaynag.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <signal.h> 1#include <signal.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -64,6 +63,8 @@ bool swaynag_spawn(const char *swaynag_command,
64 sway_log(SWAY_ERROR, "Failed to create fork for swaynag"); 63 sway_log(SWAY_ERROR, "Failed to create fork for swaynag");
65 goto failed; 64 goto failed;
66 } else if (pid == 0) { 65 } else if (pid == 0) {
66 restore_nofile_limit();
67
67 pid = fork(); 68 pid = fork();
68 if (pid < 0) { 69 if (pid < 0) {
69 sway_log_errno(SWAY_ERROR, "fork failed"); 70 sway_log_errno(SWAY_ERROR, "fork failed");
@@ -87,8 +88,8 @@ bool swaynag_spawn(const char *swaynag_command,
87 size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2; 88 size_t length = strlen(swaynag_command) + strlen(swaynag->args) + 2;
88 char *cmd = malloc(length); 89 char *cmd = malloc(length);
89 snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args); 90 snprintf(cmd, length, "%s %s", swaynag_command, swaynag->args);
90 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); 91 execlp("sh", "sh", "-c", cmd, NULL);
91 sway_log_errno(SWAY_ERROR, "execl failed"); 92 sway_log_errno(SWAY_ERROR, "execlp failed");
92 _exit(EXIT_FAILURE); 93 _exit(EXIT_FAILURE);
93 } 94 }
94 _exit(EXIT_SUCCESS); 95 _exit(EXIT_SUCCESS);
@@ -143,22 +144,16 @@ void swaynag_log(const char *swaynag_command, struct swaynag_instance *swaynag,
143 144
144 va_list args; 145 va_list args;
145 va_start(args, fmt); 146 va_start(args, fmt);
146 size_t length = vsnprintf(NULL, 0, fmt, args) + 1; 147 char *str = vformat_str(fmt, args);
147 va_end(args); 148 va_end(args);
148 149 if (!str) {
149 char *temp = malloc(length + 1);
150 if (!temp) {
151 sway_log(SWAY_ERROR, "Failed to allocate buffer for swaynag log entry."); 150 sway_log(SWAY_ERROR, "Failed to allocate buffer for swaynag log entry.");
152 return; 151 return;
153 } 152 }
154 153
155 va_start(args, fmt); 154 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 155
161 free(temp); 156 free(str);
162} 157}
163 158
164void swaynag_show(struct swaynag_instance *swaynag) { 159void swaynag_show(struct swaynag_instance *swaynag) {
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index bac9f2fa..d4003fe6 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -55,7 +54,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
55 // Calculate gap size 54 // Calculate gap size
56 double inner_gap = 0; 55 double inner_gap = 0;
57 struct sway_container *child = children->items[0]; 56 struct sway_container *child = children->items[0];
58 struct sway_workspace *ws = child->workspace; 57 struct sway_workspace *ws = child->pending.workspace;
59 if (ws) { 58 if (ws) {
60 inner_gap = ws->gaps_inner; 59 inner_gap = ws->gaps_inner;
61 } 60 }
@@ -66,7 +65,7 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
66 if (layout == L_TABBED || layout == L_STACKED) { 65 if (layout == L_TABBED || layout == L_STACKED) {
67 inner_gap = 0; 66 inner_gap = 0;
68 } 67 }
69 temp = temp->parent; 68 temp = temp->pending.parent;
70 } 69 }
71 double total_gap = fmin(inner_gap * (children->length - 1), 70 double total_gap = fmin(inner_gap * (children->length - 1),
72 fmax(0, parent->width - MIN_SANE_W * children->length)); 71 fmax(0, parent->width - MIN_SANE_W * children->length));
@@ -79,15 +78,15 @@ static void apply_horiz_layout(list_t *children, struct wlr_box *parent) {
79 for (int i = 0; i < children->length; ++i) { 78 for (int i = 0; i < children->length; ++i) {
80 struct sway_container *child = children->items[i]; 79 struct sway_container *child = children->items[i];
81 child->child_total_width = child_total_width; 80 child->child_total_width = child_total_width;
82 child->x = child_x; 81 child->pending.x = child_x;
83 child->y = parent->y; 82 child->pending.y = parent->y;
84 child->width = round(child->width_fraction * child_total_width); 83 child->pending.width = round(child->width_fraction * child_total_width);
85 child->height = parent->height; 84 child->pending.height = parent->height;
86 child_x += child->width + inner_gap; 85 child_x += child->pending.width + inner_gap;
87 86
88 // Make last child use remaining width of parent 87 // Make last child use remaining width of parent
89 if (i == children->length - 1) { 88 if (i == children->length - 1) {
90 child->width = parent->x + parent->width - child->x; 89 child->pending.width = parent->x + parent->width - child->pending.x;
91 } 90 }
92 } 91 }
93} 92}
@@ -134,7 +133,7 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
134 // Calculate gap size 133 // Calculate gap size
135 double inner_gap = 0; 134 double inner_gap = 0;
136 struct sway_container *child = children->items[0]; 135 struct sway_container *child = children->items[0];
137 struct sway_workspace *ws = child->workspace; 136 struct sway_workspace *ws = child->pending.workspace;
138 if (ws) { 137 if (ws) {
139 inner_gap = ws->gaps_inner; 138 inner_gap = ws->gaps_inner;
140 } 139 }
@@ -145,7 +144,7 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
145 if (layout == L_TABBED || layout == L_STACKED) { 144 if (layout == L_TABBED || layout == L_STACKED) {
146 inner_gap = 0; 145 inner_gap = 0;
147 } 146 }
148 temp = temp->parent; 147 temp = temp->pending.parent;
149 } 148 }
150 double total_gap = fmin(inner_gap * (children->length - 1), 149 double total_gap = fmin(inner_gap * (children->length - 1),
151 fmax(0, parent->height - MIN_SANE_H * children->length)); 150 fmax(0, parent->height - MIN_SANE_H * children->length));
@@ -158,15 +157,15 @@ static void apply_vert_layout(list_t *children, struct wlr_box *parent) {
158 for (int i = 0; i < children->length; ++i) { 157 for (int i = 0; i < children->length; ++i) {
159 struct sway_container *child = children->items[i]; 158 struct sway_container *child = children->items[i];
160 child->child_total_height = child_total_height; 159 child->child_total_height = child_total_height;
161 child->x = parent->x; 160 child->pending.x = parent->x;
162 child->y = child_y; 161 child->pending.y = child_y;
163 child->width = parent->width; 162 child->pending.width = parent->width;
164 child->height = round(child->height_fraction * child_total_height); 163 child->pending.height = round(child->height_fraction * child_total_height);
165 child_y += child->height + inner_gap; 164 child_y += child->pending.height + inner_gap;
166 165
167 // Make last child use remaining height of parent 166 // Make last child use remaining height of parent
168 if (i == children->length - 1) { 167 if (i == children->length - 1) {
169 child->height = parent->y + parent->height - child->y; 168 child->pending.height = parent->y + parent->height - child->pending.y;
170 } 169 }
171 } 170 }
172} 171}
@@ -178,10 +177,10 @@ static void apply_tabbed_layout(list_t *children, struct wlr_box *parent) {
178 for (int i = 0; i < children->length; ++i) { 177 for (int i = 0; i < children->length; ++i) {
179 struct sway_container *child = children->items[i]; 178 struct sway_container *child = children->items[i];
180 int parent_offset = child->view ? 0 : container_titlebar_height(); 179 int parent_offset = child->view ? 0 : container_titlebar_height();
181 child->x = parent->x; 180 child->pending.x = parent->x;
182 child->y = parent->y + parent_offset; 181 child->pending.y = parent->y + parent_offset;
183 child->width = parent->width; 182 child->pending.width = parent->width;
184 child->height = parent->height - parent_offset; 183 child->pending.height = parent->height - parent_offset;
185 } 184 }
186} 185}
187 186
@@ -193,10 +192,10 @@ static void apply_stacked_layout(list_t *children, struct wlr_box *parent) {
193 struct sway_container *child = children->items[i]; 192 struct sway_container *child = children->items[i];
194 int parent_offset = child->view ? 0 : 193 int parent_offset = child->view ? 0 :
195 container_titlebar_height() * children->length; 194 container_titlebar_height() * children->length;
196 child->x = parent->x; 195 child->pending.x = parent->x;
197 child->y = parent->y + parent_offset; 196 child->pending.y = parent->y + parent_offset;
198 child->width = parent->width; 197 child->pending.width = parent->width;
199 child->height = parent->height - parent_offset; 198 child->pending.height = parent->height - parent_offset;
200 } 199 }
201} 200}
202 201
@@ -246,7 +245,7 @@ void arrange_container(struct sway_container *container) {
246 } 245 }
247 struct wlr_box box; 246 struct wlr_box box;
248 container_get_box(container, &box); 247 container_get_box(container, &box);
249 arrange_children(container->children, container->layout, &box); 248 arrange_children(container->pending.children, container->pending.layout, &box);
250 node_set_dirty(&container->node); 249 node_set_dirty(&container->node);
251} 250}
252 251
@@ -264,6 +263,9 @@ void arrange_workspace(struct sway_workspace *workspace) {
264 area->width, area->height, area->x, area->y); 263 area->width, area->height, area->x, area->y);
265 264
266 bool first_arrange = workspace->width == 0 && workspace->height == 0; 265 bool first_arrange = workspace->width == 0 && workspace->height == 0;
266 struct wlr_box prev_box;
267 workspace_get_box(workspace, &prev_box);
268
267 double prev_x = workspace->x - workspace->current_gaps.left; 269 double prev_x = workspace->x - workspace->current_gaps.left;
268 double prev_y = workspace->y - workspace->current_gaps.top; 270 double prev_y = workspace->y - workspace->current_gaps.top;
269 workspace->width = area->width; 271 workspace->width = area->width;
@@ -277,13 +279,14 @@ void arrange_workspace(struct sway_workspace *workspace) {
277 if (!first_arrange && (diff_x != 0 || diff_y != 0)) { 279 if (!first_arrange && (diff_x != 0 || diff_y != 0)) {
278 for (int i = 0; i < workspace->floating->length; ++i) { 280 for (int i = 0; i < workspace->floating->length; ++i) {
279 struct sway_container *floater = workspace->floating->items[i]; 281 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; 282 struct wlr_box workspace_box;
284 workspace_get_box(workspace, &workspace_box); 283 workspace_get_box(workspace, &workspace_box);
285 if (!wlr_box_contains_point(&workspace_box, center_x, center_y)) { 284 floating_fix_coordinates(floater, &prev_box, &workspace_box);
286 container_floating_move_to_center(floater); 285 // Set transformation for scratchpad windows.
286 if (floater->scratchpad) {
287 struct wlr_box output_box;
288 output_get_box(output, &output_box);
289 floater->transform = output_box;
287 } 290 }
288 } 291 }
289 } 292 }
@@ -294,10 +297,10 @@ void arrange_workspace(struct sway_workspace *workspace) {
294 workspace->x, workspace->y); 297 workspace->x, workspace->y);
295 if (workspace->fullscreen) { 298 if (workspace->fullscreen) {
296 struct sway_container *fs = workspace->fullscreen; 299 struct sway_container *fs = workspace->fullscreen;
297 fs->x = output->lx; 300 fs->pending.x = output->lx;
298 fs->y = output->ly; 301 fs->pending.y = output->ly;
299 fs->width = output->width; 302 fs->pending.width = output->width;
300 fs->height = output->height; 303 fs->pending.height = output->height;
301 arrange_container(fs); 304 arrange_container(fs);
302 } else { 305 } else {
303 struct wlr_box box; 306 struct wlr_box box;
@@ -311,12 +314,13 @@ void arrange_output(struct sway_output *output) {
311 if (config->reloading) { 314 if (config->reloading) {
312 return; 315 return;
313 } 316 }
314 const struct wlr_box *output_box = wlr_output_layout_get_box( 317 struct wlr_box output_box;
315 root->output_layout, output->wlr_output); 318 wlr_output_layout_get_box(root->output_layout,
316 output->lx = output_box->x; 319 output->wlr_output, &output_box);
317 output->ly = output_box->y; 320 output->lx = output_box.x;
318 output->width = output_box->width; 321 output->ly = output_box.y;
319 output->height = output_box->height; 322 output->width = output_box.width;
323 output->height = output_box.height;
320 324
321 for (int i = 0; i < output->workspaces->length; ++i) { 325 for (int i = 0; i < output->workspaces->length; ++i) {
322 struct sway_workspace *workspace = output->workspaces->items[i]; 326 struct sway_workspace *workspace = output->workspaces->items[i];
@@ -328,19 +332,19 @@ void arrange_root(void) {
328 if (config->reloading) { 332 if (config->reloading) {
329 return; 333 return;
330 } 334 }
331 const struct wlr_box *layout_box = 335 struct wlr_box layout_box;
332 wlr_output_layout_get_box(root->output_layout, NULL); 336 wlr_output_layout_get_box(root->output_layout, NULL, &layout_box);
333 root->x = layout_box->x; 337 root->x = layout_box.x;
334 root->y = layout_box->y; 338 root->y = layout_box.y;
335 root->width = layout_box->width; 339 root->width = layout_box.width;
336 root->height = layout_box->height; 340 root->height = layout_box.height;
337 341
338 if (root->fullscreen_global) { 342 if (root->fullscreen_global) {
339 struct sway_container *fs = root->fullscreen_global; 343 struct sway_container *fs = root->fullscreen_global;
340 fs->x = root->x; 344 fs->pending.x = root->x;
341 fs->y = root->y; 345 fs->pending.y = root->y;
342 fs->width = root->width; 346 fs->pending.width = root->width;
343 fs->height = root->height; 347 fs->pending.height = root->height;
344 arrange_container(fs); 348 arrange_container(fs);
345 } else { 349 } else {
346 for (int i = 0; i < root->outputs->length; ++i) { 350 for (int i = 0; i < root->outputs->length; ++i) {
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 6a9ce1c4..9224b4fb 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -1,28 +1,77 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
2#include <drm_fourcc.h>
3#include <stdint.h> 3#include <stdint.h>
4#include <stdlib.h> 4#include <stdlib.h>
5#include <string.h>
6#include <strings.h>
7#include <wayland-server-core.h> 5#include <wayland-server-core.h>
6#include <wlr/types/wlr_foreign_toplevel_management_v1.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..7aaf9762 100644
--- a/sway/tree/node.c
+++ b/sway/tree/node.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include "sway/output.h" 1#include "sway/output.h"
3#include "sway/server.h" 2#include "sway/server.h"
4#include "sway/tree/container.h" 3#include "sway/tree/container.h"
@@ -18,13 +17,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) { 17const char *node_type_to_str(enum sway_node_type type) {
19 switch (type) { 18 switch (type) {
20 case N_ROOT: 19 case N_ROOT:
21 return "N_ROOT"; 20 return "root";
22 case N_OUTPUT: 21 case N_OUTPUT:
23 return "N_OUTPUT"; 22 return "output";
24 case N_WORKSPACE: 23 case N_WORKSPACE:
25 return "N_WORKSPACE"; 24 return "workspace";
26 case N_CONTAINER: 25 case N_CONTAINER:
27 return "N_CONTAINER"; 26 return "container";
28 } 27 }
29 return ""; 28 return "";
30} 29}
@@ -75,7 +74,7 @@ void node_get_box(struct sway_node *node, struct wlr_box *box) {
75struct sway_output *node_get_output(struct sway_node *node) { 74struct sway_output *node_get_output(struct sway_node *node) {
76 switch (node->type) { 75 switch (node->type) {
77 case N_CONTAINER: { 76 case N_CONTAINER: {
78 struct sway_workspace *ws = node->sway_container->workspace; 77 struct sway_workspace *ws = node->sway_container->pending.workspace;
79 return ws ? ws->output : NULL; 78 return ws ? ws->output : NULL;
80 } 79 }
81 case N_WORKSPACE: 80 case N_WORKSPACE:
@@ -91,7 +90,7 @@ struct sway_output *node_get_output(struct sway_node *node) {
91enum sway_container_layout node_get_layout(struct sway_node *node) { 90enum sway_container_layout node_get_layout(struct sway_node *node) {
92 switch (node->type) { 91 switch (node->type) {
93 case N_CONTAINER: 92 case N_CONTAINER:
94 return node->sway_container->layout; 93 return node->sway_container->pending.layout;
95 case N_WORKSPACE: 94 case N_WORKSPACE:
96 return node->sway_workspace->layout; 95 return node->sway_workspace->layout;
97 case N_OUTPUT: 96 case N_OUTPUT:
@@ -105,11 +104,11 @@ struct sway_node *node_get_parent(struct sway_node *node) {
105 switch (node->type) { 104 switch (node->type) {
106 case N_CONTAINER: { 105 case N_CONTAINER: {
107 struct sway_container *con = node->sway_container; 106 struct sway_container *con = node->sway_container;
108 if (con->parent) { 107 if (con->pending.parent) {
109 return &con->parent->node; 108 return &con->pending.parent->node;
110 } 109 }
111 if (con->workspace) { 110 if (con->pending.workspace) {
112 return &con->workspace->node; 111 return &con->pending.workspace->node;
113 } 112 }
114 } 113 }
115 return NULL; 114 return NULL;
@@ -131,7 +130,7 @@ struct sway_node *node_get_parent(struct sway_node *node) {
131list_t *node_get_children(struct sway_node *node) { 130list_t *node_get_children(struct sway_node *node) {
132 switch (node->type) { 131 switch (node->type) {
133 case N_CONTAINER: 132 case N_CONTAINER:
134 return node->sway_container->children; 133 return node->sway_container->pending.children;
135 case N_WORKSPACE: 134 case N_WORKSPACE:
136 return node->sway_workspace->tiling; 135 return node->sway_workspace->tiling;
137 case N_OUTPUT: 136 case N_OUTPUT:
@@ -143,7 +142,7 @@ list_t *node_get_children(struct sway_node *node) {
143 142
144bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) { 143bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) {
145 if (ancestor->type == N_ROOT && node->type == N_CONTAINER && 144 if (ancestor->type == N_ROOT && node->type == N_CONTAINER &&
146 node->sway_container->fullscreen_mode == FULLSCREEN_GLOBAL) { 145 node->sway_container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
147 return true; 146 return true;
148 } 147 }
149 struct sway_node *parent = node_get_parent(node); 148 struct sway_node *parent = node_get_parent(node);
@@ -152,10 +151,39 @@ bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) {
152 return true; 151 return true;
153 } 152 }
154 if (ancestor->type == N_ROOT && parent->type == N_CONTAINER && 153 if (ancestor->type == N_ROOT && parent->type == N_CONTAINER &&
155 parent->sway_container->fullscreen_mode == FULLSCREEN_GLOBAL) { 154 parent->sway_container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
156 return true; 155 return true;
157 } 156 }
158 parent = node_get_parent(parent); 157 parent = node_get_parent(parent);
159 } 158 }
160 return false; 159 return false;
161} 160}
161
162void scene_node_disown_children(struct wlr_scene_tree *tree) {
163 // this function can be called as part of destruction code that will be invoked
164 // upon an allocation failure. Let's not crash on NULL due to an allocation error.
165 if (!tree) {
166 return;
167 }
168
169 struct wlr_scene_node *child, *tmp_child;
170 wl_list_for_each_safe(child, tmp_child, &tree->children, link) {
171 wlr_scene_node_reparent(child, root->staging);
172 }
173}
174
175struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent,
176 bool *failed) {
177 // fallthrough
178 if (*failed) {
179 return NULL;
180 }
181
182 struct wlr_scene_tree *tree = wlr_scene_tree_create(parent);
183 if (!tree) {
184 sway_log(SWAY_ERROR, "Failed to allocate a scene node");
185 *failed = true;
186 }
187
188 return tree;
189}
diff --git a/sway/tree/output.c b/sway/tree/output.c
index a8ae30f7..2d11195e 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -1,14 +1,13 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <ctype.h> 2#include <ctype.h>
4#include <string.h> 3#include <string.h>
5#include <strings.h> 4#include <strings.h>
6#include <wlr/types/wlr_output_damage.h>
7#include "sway/ipc-server.h" 5#include "sway/ipc-server.h"
8#include "sway/layers.h" 6#include "sway/layers.h"
9#include "sway/output.h" 7#include "sway/output.h"
10#include "sway/tree/arrange.h" 8#include "sway/tree/arrange.h"
11#include "sway/tree/workspace.h" 9#include "sway/tree/workspace.h"
10#include "sway/server.h"
12#include "log.h" 11#include "log.h"
13#include "util.h" 12#include "util.h"
14 13
@@ -56,8 +55,8 @@ static void restore_workspaces(struct sway_output *output) {
56 } 55 }
57 56
58 // Saved workspaces 57 // Saved workspaces
59 while (root->noop_output->workspaces->length) { 58 while (root->fallback_output->workspaces->length) {
60 struct sway_workspace *ws = root->noop_output->workspaces->items[0]; 59 struct sway_workspace *ws = root->fallback_output->workspaces->items[0];
61 workspace_detach(ws); 60 workspace_detach(ws);
62 output_add_workspace(output, ws); 61 output_add_workspace(output, ws);
63 62
@@ -70,13 +69,13 @@ static void restore_workspaces(struct sway_output *output) {
70 // floater re-centered 69 // floater re-centered
71 for (int i = 0; i < ws->floating->length; i++) { 70 for (int i = 0; i < ws->floating->length; i++) {
72 struct sway_container *floater = ws->floating->items[i]; 71 struct sway_container *floater = ws->floating->items[i];
73 if (floater->width == 0 || floater->height == 0 || 72 if (floater->pending.width == 0 || floater->pending.height == 0 ||
74 floater->width > output->width || 73 floater->pending.width > output->width ||
75 floater->height > output->height || 74 floater->pending.height > output->height ||
76 floater->x > output->lx + output->width || 75 floater->pending.x > output->lx + output->width ||
77 floater->y > output->ly + output->height || 76 floater->pending.y > output->ly + output->height ||
78 floater->x + floater->width < output->lx || 77 floater->pending.x + floater->pending.width < output->lx ||
79 floater->y + floater->height < output->ly) { 78 floater->pending.y + floater->pending.height < output->ly) {
80 container_floating_resize_and_center(floater); 79 container_floating_resize_and_center(floater);
81 } 80 }
82 } 81 }
@@ -87,26 +86,63 @@ static void restore_workspaces(struct sway_output *output) {
87 output_sort_workspaces(output); 86 output_sort_workspaces(output);
88} 87}
89 88
89static void destroy_scene_layers(struct sway_output *output) {
90 wlr_scene_node_destroy(&output->fullscreen_background->node);
91
92 scene_node_disown_children(output->layers.tiling);
93 scene_node_disown_children(output->layers.fullscreen);
94
95 wlr_scene_node_destroy(&output->layers.shell_background->node);
96 wlr_scene_node_destroy(&output->layers.shell_bottom->node);
97 wlr_scene_node_destroy(&output->layers.tiling->node);
98 wlr_scene_node_destroy(&output->layers.fullscreen->node);
99 wlr_scene_node_destroy(&output->layers.shell_top->node);
100 wlr_scene_node_destroy(&output->layers.shell_overlay->node);
101 wlr_scene_node_destroy(&output->layers.session_lock->node);
102}
103
90struct sway_output *output_create(struct wlr_output *wlr_output) { 104struct sway_output *output_create(struct wlr_output *wlr_output) {
91 struct sway_output *output = calloc(1, sizeof(struct sway_output)); 105 struct sway_output *output = calloc(1, sizeof(struct sway_output));
92 node_init(&output->node, N_OUTPUT, output); 106 node_init(&output->node, N_OUTPUT, output);
107
108 bool failed = false;
109 output->layers.shell_background = alloc_scene_tree(root->staging, &failed);
110 output->layers.shell_bottom = alloc_scene_tree(root->staging, &failed);
111 output->layers.tiling = alloc_scene_tree(root->staging, &failed);
112 output->layers.fullscreen = alloc_scene_tree(root->staging, &failed);
113 output->layers.shell_top = alloc_scene_tree(root->staging, &failed);
114 output->layers.shell_overlay = alloc_scene_tree(root->staging, &failed);
115 output->layers.session_lock = alloc_scene_tree(root->staging, &failed);
116
117 if (!failed) {
118 output->fullscreen_background = wlr_scene_rect_create(
119 output->layers.fullscreen, 0, 0, (float[4]){0.f, 0.f, 0.f, 1.f});
120
121 if (!output->fullscreen_background) {
122 sway_log(SWAY_ERROR, "Unable to allocate a background rect");
123 failed = true;
124 }
125 }
126
127 if (failed) {
128 destroy_scene_layers(output);
129 wlr_scene_output_destroy(output->scene_output);
130 free(output);
131 return NULL;
132 }
133
93 output->wlr_output = wlr_output; 134 output->wlr_output = wlr_output;
94 wlr_output->data = output; 135 wlr_output->data = output;
95 output->detected_subpixel = wlr_output->subpixel; 136 output->detected_subpixel = wlr_output->subpixel;
96 output->scale_filter = SCALE_FILTER_NEAREST; 137 output->scale_filter = SCALE_FILTER_NEAREST;
97 138
98 wl_signal_init(&output->events.destroy); 139 wl_signal_init(&output->events.disable);
99 140
100 wl_list_insert(&root->all_outputs, &output->link); 141 wl_list_insert(&root->all_outputs, &output->link);
101 142
102 output->workspaces = create_list(); 143 output->workspaces = create_list();
103 output->current.workspaces = create_list(); 144 output->current.workspaces = create_list();
104 145
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; 146 return output;
111} 147}
112 148
@@ -146,7 +182,7 @@ void output_enable(struct sway_output *output) {
146 182
147 input_manager_configure_xcursor(); 183 input_manager_configure_xcursor();
148 184
149 wl_signal_emit(&root->events.new_node, &output->node); 185 wl_signal_emit_mutable(&root->events.new_node, &output->node);
150 186
151 arrange_layers(output); 187 arrange_layers(output);
152 arrange_root(); 188 arrange_root();
@@ -192,7 +228,7 @@ static void output_evacuate(struct sway_output *output) {
192 new_output = fallback_output; 228 new_output = fallback_output;
193 } 229 }
194 if (!new_output) { 230 if (!new_output) {
195 new_output = root->noop_output; 231 new_output = root->fallback_output;
196 } 232 }
197 233
198 struct sway_workspace *new_output_ws = 234 struct sway_workspace *new_output_ws =
@@ -238,20 +274,14 @@ void output_destroy(struct sway_output *output) {
238 "which is still referenced by transactions")) { 274 "which is still referenced by transactions")) {
239 return; 275 return;
240 } 276 }
277
278 destroy_scene_layers(output);
241 list_free(output->workspaces); 279 list_free(output->workspaces);
242 list_free(output->current.workspaces); 280 list_free(output->current.workspaces);
243 wl_event_source_remove(output->repaint_timer); 281 wl_event_source_remove(output->repaint_timer);
244 free(output); 282 free(output);
245} 283}
246 284
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) { 285void output_disable(struct sway_output *output) {
256 if (!sway_assert(output->enabled, "Expected an enabled output")) { 286 if (!sway_assert(output->enabled, "Expected an enabled output")) {
257 return; 287 return;
@@ -262,23 +292,20 @@ void output_disable(struct sway_output *output) {
262 } 292 }
263 293
264 sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name); 294 sway_log(SWAY_DEBUG, "Disabling output '%s'", output->wlr_output->name);
265 wl_signal_emit(&output->events.destroy, output); 295 wl_signal_emit_mutable(&output->events.disable, output);
266 296
267 output_evacuate(output); 297 output_evacuate(output);
268 298
269 root_for_each_container(untrack_output, output);
270
271 list_del(root->outputs, index); 299 list_del(root->outputs, index);
272 300
273 output->enabled = false; 301 output->enabled = false;
274 output->current_mode = NULL;
275 302
276 arrange_root(); 303 arrange_root();
277 304
278 // Reconfigure all devices, since devices with map_to_output directives for 305 // 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 306 // an output that goes offline should stop sending events as long as the
280 // output remains offline. 307 // output remains offline.
281 input_manager_configure_all_inputs(); 308 input_manager_configure_all_input_mappings();
282} 309}
283 310
284void output_begin_destroy(struct sway_output *output) { 311void output_begin_destroy(struct sway_output *output) {
@@ -286,13 +313,10 @@ void output_begin_destroy(struct sway_output *output) {
286 return; 313 return;
287 } 314 }
288 sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name); 315 sway_log(SWAY_DEBUG, "Destroying output '%s'", output->wlr_output->name);
316 wl_signal_emit_mutable(&output->node.events.destroy, &output->node);
289 317
290 output->node.destroying = true; 318 output->node.destroying = true;
291 node_set_dirty(&output->node); 319 node_set_dirty(&output->node);
292
293 wl_list_remove(&output->link);
294 output->wlr_output->data = NULL;
295 output->wlr_output = NULL;
296} 320}
297 321
298struct sway_output *output_from_wlr_output(struct wlr_output *output) { 322struct sway_output *output_from_wlr_output(struct wlr_output *output) {
@@ -304,10 +328,10 @@ struct sway_output *output_get_in_direction(struct sway_output *reference,
304 if (!sway_assert(direction, "got invalid direction: %d", direction)) { 328 if (!sway_assert(direction, "got invalid direction: %d", direction)) {
305 return NULL; 329 return NULL;
306 } 330 }
307 struct wlr_box *output_box = 331 struct wlr_box output_box;
308 wlr_output_layout_get_box(root->output_layout, reference->wlr_output); 332 wlr_output_layout_get_box(root->output_layout, reference->wlr_output, &output_box);
309 int lx = output_box->x + output_box->width / 2; 333 int lx = output_box.x + output_box.width / 2;
310 int ly = output_box->y + output_box->height / 2; 334 int ly = output_box.y + output_box.height / 2;
311 struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output( 335 struct wlr_output *wlr_adjacent = wlr_output_layout_adjacent_output(
312 root->output_layout, direction, reference->wlr_output, lx, ly); 336 root->output_layout, direction, reference->wlr_output, lx, ly);
313 if (!wlr_adjacent) { 337 if (!wlr_adjacent) {
@@ -393,6 +417,33 @@ void output_get_box(struct sway_output *output, struct wlr_box *box) {
393 box->height = output->height; 417 box->height = output->height;
394} 418}
395 419
420static void handle_destroy_non_desktop(struct wl_listener *listener, void *data) {
421 struct sway_output_non_desktop *output =
422 wl_container_of(listener, output, destroy);
423
424 sway_log(SWAY_DEBUG, "Destroying non-desktop output '%s'", output->wlr_output->name);
425
426 int index = list_find(root->non_desktop_outputs, output);
427 list_del(root->non_desktop_outputs, index);
428
429 wl_list_remove(&output->destroy.link);
430
431 free(output);
432}
433
434struct sway_output_non_desktop *output_non_desktop_create(
435 struct wlr_output *wlr_output) {
436 struct sway_output_non_desktop *output =
437 calloc(1, sizeof(struct sway_output_non_desktop));
438
439 output->wlr_output = wlr_output;
440
441 wl_signal_add(&wlr_output->events.destroy, &output->destroy);
442 output->destroy.notify = handle_destroy_non_desktop;
443
444 return output;
445}
446
396enum sway_container_layout output_get_default_layout( 447enum sway_container_layout output_get_default_layout(
397 struct sway_output *output) { 448 struct sway_output *output) {
398 if (config->default_orientation != L_NONE) { 449 if (config->default_orientation != L_NONE) {
diff --git a/sway/tree/root.c b/sway/tree/root.c
index ebd185ec..ae3c3cb2 100644
--- a/sway/tree/root.c
+++ b/sway/tree/root.c
@@ -1,12 +1,14 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <string.h> 3#include <string.h>
5#include <wlr/types/wlr_output_layout.h> 4#include <wlr/types/wlr_output_layout.h>
5#include <wlr/types/wlr_scene.h>
6#include <wlr/util/transform.h>
6#include "sway/desktop/transaction.h" 7#include "sway/desktop/transaction.h"
7#include "sway/input/seat.h" 8#include "sway/input/seat.h"
8#include "sway/ipc-server.h" 9#include "sway/ipc-server.h"
9#include "sway/output.h" 10#include "sway/output.h"
11#include "sway/scene_descriptor.h"
10#include "sway/tree/arrange.h" 12#include "sway/tree/arrange.h"
11#include "sway/tree/container.h" 13#include "sway/tree/container.h"
12#include "sway/tree/root.h" 14#include "sway/tree/root.h"
@@ -23,21 +25,60 @@ static void output_layout_handle_change(struct wl_listener *listener,
23 transaction_commit_dirty(); 25 transaction_commit_dirty();
24} 26}
25 27
26struct sway_root *root_create(void) { 28struct sway_root *root_create(struct wl_display *wl_display) {
27 struct sway_root *root = calloc(1, sizeof(struct sway_root)); 29 struct sway_root *root = calloc(1, sizeof(struct sway_root));
28 if (!root) { 30 if (!root) {
29 sway_log(SWAY_ERROR, "Unable to allocate sway_root"); 31 sway_log(SWAY_ERROR, "Unable to allocate sway_root");
30 return NULL; 32 return NULL;
31 } 33 }
34
35 struct wlr_scene *root_scene = wlr_scene_create();
36 if (!root_scene) {
37 sway_log(SWAY_ERROR, "Unable to allocate root scene node");
38 free(root);
39 return NULL;
40 }
41
32 node_init(&root->node, N_ROOT, root); 42 node_init(&root->node, N_ROOT, root);
33 root->output_layout = wlr_output_layout_create(); 43 root->root_scene = root_scene;
34 wl_list_init(&root->all_outputs); 44
45 bool failed = false;
46 root->staging = alloc_scene_tree(&root_scene->tree, &failed);
47 root->layer_tree = alloc_scene_tree(&root_scene->tree, &failed);
48
49 root->layers.shell_background = alloc_scene_tree(root->layer_tree, &failed);
50 root->layers.shell_bottom = alloc_scene_tree(root->layer_tree, &failed);
51 root->layers.tiling = alloc_scene_tree(root->layer_tree, &failed);
52 root->layers.floating = alloc_scene_tree(root->layer_tree, &failed);
53 root->layers.shell_top = alloc_scene_tree(root->layer_tree, &failed);
54 root->layers.fullscreen = alloc_scene_tree(root->layer_tree, &failed);
55 root->layers.fullscreen_global = alloc_scene_tree(root->layer_tree, &failed);
35#if HAVE_XWAYLAND 56#if HAVE_XWAYLAND
36 wl_list_init(&root->xwayland_unmanaged); 57 root->layers.unmanaged = alloc_scene_tree(root->layer_tree, &failed);
37#endif 58#endif
38 wl_list_init(&root->drag_icons); 59 root->layers.shell_overlay = alloc_scene_tree(root->layer_tree, &failed);
60 root->layers.popup = alloc_scene_tree(root->layer_tree, &failed);
61 root->layers.seat = alloc_scene_tree(root->layer_tree, &failed);
62 root->layers.session_lock = alloc_scene_tree(root->layer_tree, &failed);
63
64 if (!failed && !scene_descriptor_assign(&root->layers.seat->node,
65 SWAY_SCENE_DESC_NON_INTERACTIVE, (void *)1)) {
66 failed = true;
67 }
68
69 if (failed) {
70 wlr_scene_node_destroy(&root_scene->tree.node);
71 free(root);
72 return NULL;
73 }
74
75 wlr_scene_node_set_enabled(&root->staging->node, false);
76
77 root->output_layout = wlr_output_layout_create(wl_display);
78 wl_list_init(&root->all_outputs);
39 wl_signal_init(&root->events.new_node); 79 wl_signal_init(&root->events.new_node);
40 root->outputs = create_list(); 80 root->outputs = create_list();
81 root->non_desktop_outputs = create_list();
41 root->scratchpad = create_list(); 82 root->scratchpad = create_list();
42 83
43 root->output_layout_change.notify = output_layout_handle_change; 84 root->output_layout_change.notify = output_layout_handle_change;
@@ -49,21 +90,34 @@ struct sway_root *root_create(void) {
49void root_destroy(struct sway_root *root) { 90void root_destroy(struct sway_root *root) {
50 wl_list_remove(&root->output_layout_change.link); 91 wl_list_remove(&root->output_layout_change.link);
51 list_free(root->scratchpad); 92 list_free(root->scratchpad);
93 list_free(root->non_desktop_outputs);
52 list_free(root->outputs); 94 list_free(root->outputs);
53 wlr_output_layout_destroy(root->output_layout); 95 wlr_scene_node_destroy(&root->root_scene->tree.node);
54 free(root); 96 free(root);
55} 97}
56 98
99static void set_container_transform(struct sway_workspace *ws,
100 struct sway_container *con) {
101 struct sway_output *output = ws->output;
102 struct wlr_box box = {0};
103 if (output) {
104 output_get_box(output, &box);
105 }
106 con->transform = box;
107}
108
57void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws) { 109void root_scratchpad_add_container(struct sway_container *con, struct sway_workspace *ws) {
58 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) { 110 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
59 return; 111 return;
60 } 112 }
61 113
62 struct sway_container *parent = con->parent; 114 struct sway_container *parent = con->pending.parent;
63 struct sway_workspace *workspace = con->workspace; 115 struct sway_workspace *workspace = con->pending.workspace;
116
117 set_container_transform(workspace, con);
64 118
65 // Clear the fullscreen mode when sending to the scratchpad 119 // Clear the fullscreen mode when sending to the scratchpad
66 if (con->fullscreen_mode != FULLSCREEN_NONE) { 120 if (con->pending.fullscreen_mode != FULLSCREEN_NONE) {
67 container_fullscreen_disable(con); 121 container_fullscreen_disable(con);
68 } 122 }
69 123
@@ -117,7 +171,7 @@ void root_scratchpad_show(struct sway_container *con) {
117 sway_log(SWAY_DEBUG, "No focused workspace to show scratchpad on"); 171 sway_log(SWAY_DEBUG, "No focused workspace to show scratchpad on");
118 return; 172 return;
119 } 173 }
120 struct sway_workspace *old_ws = con->workspace; 174 struct sway_workspace *old_ws = con->pending.workspace;
121 175
122 // If the current con or any of its parents are in fullscreen mode, we 176 // 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. 177 // first need to disable it before showing the scratchpad con.
@@ -131,31 +185,34 @@ void root_scratchpad_show(struct sway_container *con) {
131 // Show the container 185 // Show the container
132 if (old_ws) { 186 if (old_ws) {
133 container_detach(con); 187 container_detach(con);
134 workspace_consider_destroy(old_ws); 188 // Make sure the last inactive container on the old workspace is above
189 // the workspace itself in the focus stack.
190 struct sway_node *node = seat_get_focus_inactive(seat, &old_ws->node);
191 seat_set_raw_focus(seat, node);
135 } else { 192 } else {
136 // Act on the ancestor of scratchpad hidden split containers 193 // Act on the ancestor of scratchpad hidden split containers
137 while (con->parent) { 194 while (con->pending.parent) {
138 con = con->parent; 195 con = con->pending.parent;
139 } 196 }
140 } 197 }
141 workspace_add_floating(new_ws, con); 198 workspace_add_floating(new_ws, con);
142 199
143 // Make sure the container's center point overlaps this workspace 200 if (new_ws->output) {
144 double center_lx = con->x + con->width / 2; 201 struct wlr_box output_box;
145 double center_ly = con->y + con->height / 2; 202 output_get_box(new_ws->output, &output_box);
146 203 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 } 204 }
205 set_container_transform(new_ws, con);
152 206
153 arrange_workspace(new_ws); 207 arrange_workspace(new_ws);
154 seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node)); 208 seat_set_focus(seat, seat_get_focus_inactive(seat, &con->node));
209 if (old_ws) {
210 workspace_consider_destroy(old_ws);
211 }
155} 212}
156 213
157static void disable_fullscreen(struct sway_container *con, void *data) { 214static void disable_fullscreen(struct sway_container *con, void *data) {
158 if (con->fullscreen_mode != FULLSCREEN_NONE) { 215 if (con->pending.fullscreen_mode != FULLSCREEN_NONE) {
159 container_fullscreen_disable(con); 216 container_fullscreen_disable(con);
160 } 217 }
161} 218}
@@ -163,14 +220,16 @@ static void disable_fullscreen(struct sway_container *con, void *data) {
163void root_scratchpad_hide(struct sway_container *con) { 220void root_scratchpad_hide(struct sway_container *con) {
164 struct sway_seat *seat = input_manager_current_seat(); 221 struct sway_seat *seat = input_manager_current_seat();
165 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); 222 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node);
166 struct sway_workspace *ws = con->workspace; 223 struct sway_workspace *ws = con->pending.workspace;
167 224
168 if (con->fullscreen_mode == FULLSCREEN_GLOBAL && !con->workspace) { 225 if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL && !con->pending.workspace) {
169 // If the container was made fullscreen global while in the scratchpad, 226 // If the container was made fullscreen global while in the scratchpad,
170 // it should be shown until fullscreen has been disabled 227 // it should be shown until fullscreen has been disabled
171 return; 228 return;
172 } 229 }
173 230
231 set_container_transform(con->pending.workspace, con);
232
174 disable_fullscreen(con, NULL); 233 disable_fullscreen(con, NULL);
175 container_for_each_child(con, disable_fullscreen, NULL); 234 container_for_each_child(con, disable_fullscreen, NULL);
176 container_detach(con); 235 container_detach(con);
@@ -183,163 +242,6 @@ void root_scratchpad_hide(struct sway_container *con) {
183 ipc_event_window(con, "move"); 242 ipc_event_window(con, "move");
184} 243}
185 244
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), 245void root_for_each_workspace(void (*f)(struct sway_workspace *ws, void *data),
344 void *data) { 246 void *data) {
345 for (int i = 0; i < root->outputs->length; ++i) { 247 for (int i = 0; i < root->outputs->length; ++i) {
@@ -365,8 +267,8 @@ void root_for_each_container(void (*f)(struct sway_container *con, void *data),
365 } 267 }
366 268
367 // Saved workspaces 269 // Saved workspaces
368 for (int i = 0; i < root->noop_output->workspaces->length; ++i) { 270 for (int i = 0; i < root->fallback_output->workspaces->length; ++i) {
369 struct sway_workspace *ws = root->noop_output->workspaces->items[i]; 271 struct sway_workspace *ws = root->fallback_output->workspaces->items[i];
370 workspace_for_each_container(ws, f, data); 272 workspace_for_each_container(ws, f, data);
371 } 273 }
372} 274}
@@ -418,8 +320,8 @@ struct sway_container *root_find_container(
418 } 320 }
419 321
420 // Saved workspaces 322 // Saved workspaces
421 for (int i = 0; i < root->noop_output->workspaces->length; ++i) { 323 for (int i = 0; i < root->fallback_output->workspaces->length; ++i) {
422 struct sway_workspace *ws = root->noop_output->workspaces->items[i]; 324 struct sway_workspace *ws = root->fallback_output->workspaces->items[i];
423 if ((result = workspace_find_container(ws, test, data))) { 325 if ((result = workspace_find_container(ws, test, data))) {
424 return result; 326 return result;
425 } 327 }
@@ -434,17 +336,3 @@ void root_get_box(struct sway_root *root, struct wlr_box *box) {
434 box->width = root->width; 336 box->width = root->width;
435 box->height = root->height; 337 box->height = root->height;
436} 338}
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..35b4b73f 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -1,11 +1,13 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <strings.h> 2#include <strings.h>
4#include <wayland-server-core.h> 3#include <wayland-server-core.h>
5#include <wlr/render/wlr_renderer.h> 4#include <wlr/render/wlr_renderer.h>
6#include <wlr/types/wlr_buffer.h> 5#include <wlr/types/wlr_buffer.h>
6#include <wlr/types/wlr_ext_foreign_toplevel_list_v1.h>
7#include <wlr/types/wlr_foreign_toplevel_management_v1.h>
7#include <wlr/types/wlr_output_layout.h> 8#include <wlr/types/wlr_output_layout.h>
8#include <wlr/types/wlr_server_decoration.h> 9#include <wlr/types/wlr_server_decoration.h>
10#include <wlr/types/wlr_subcompositor.h>
9#include <wlr/types/wlr_xdg_decoration_v1.h> 11#include <wlr/types/wlr_xdg_decoration_v1.h>
10#include "config.h" 12#include "config.h"
11#if HAVE_XWAYLAND 13#if HAVE_XWAYLAND
@@ -15,14 +17,16 @@
15#include "log.h" 17#include "log.h"
16#include "sway/criteria.h" 18#include "sway/criteria.h"
17#include "sway/commands.h" 19#include "sway/commands.h"
18#include "sway/desktop.h"
19#include "sway/desktop/transaction.h" 20#include "sway/desktop/transaction.h"
20#include "sway/desktop/idle_inhibit_v1.h" 21#include "sway/desktop/idle_inhibit_v1.h"
22#include "sway/desktop/launcher.h"
21#include "sway/input/cursor.h" 23#include "sway/input/cursor.h"
22#include "sway/ipc-server.h" 24#include "sway/ipc-server.h"
23#include "sway/output.h" 25#include "sway/output.h"
24#include "sway/input/seat.h" 26#include "sway/input/seat.h"
27#include "sway/scene_descriptor.h"
25#include "sway/server.h" 28#include "sway/server.h"
29#include "sway/sway_text_node.h"
26#include "sway/tree/arrange.h" 30#include "sway/tree/arrange.h"
27#include "sway/tree/container.h" 31#include "sway/tree/container.h"
28#include "sway/tree/view.h" 32#include "sway/tree/view.h"
@@ -32,15 +36,29 @@
32#include "pango.h" 36#include "pango.h"
33#include "stringop.h" 37#include "stringop.h"
34 38
35void view_init(struct sway_view *view, enum sway_view_type type, 39bool view_init(struct sway_view *view, enum sway_view_type type,
36 const struct sway_view_impl *impl) { 40 const struct sway_view_impl *impl) {
41 bool failed = false;
42 view->scene_tree = alloc_scene_tree(root->staging, &failed);
43 view->content_tree = alloc_scene_tree(view->scene_tree, &failed);
44
45 if (!failed && !scene_descriptor_assign(&view->scene_tree->node,
46 SWAY_SCENE_DESC_VIEW, view)) {
47 failed = true;
48 }
49
50 if (failed) {
51 wlr_scene_node_destroy(&view->scene_tree->node);
52 return false;
53 }
54
37 view->type = type; 55 view->type = type;
38 view->impl = impl; 56 view->impl = impl;
39 view->executed_criteria = create_list(); 57 view->executed_criteria = create_list();
40 wl_list_init(&view->saved_buffers);
41 view->allow_request_urgent = true; 58 view->allow_request_urgent = true;
42 view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT; 59 view->shortcuts_inhibit = SHORTCUTS_INHIBIT_DEFAULT;
43 wl_signal_init(&view->events.unmap); 60 wl_signal_init(&view->events.unmap);
61 return true;
44} 62}
45 63
46void view_destroy(struct sway_view *view) { 64void view_destroy(struct sway_view *view) {
@@ -56,11 +74,11 @@ void view_destroy(struct sway_view *view) {
56 "(might have a pending transaction?)")) { 74 "(might have a pending transaction?)")) {
57 return; 75 return;
58 } 76 }
59 if (!wl_list_empty(&view->saved_buffers)) { 77 wl_list_remove(&view->events.unmap.listener_list);
60 view_remove_saved_buffer(view);
61 }
62 list_free(view->executed_criteria); 78 list_free(view->executed_criteria);
63 79
80 view_assign_ctx(view, NULL);
81 wlr_scene_node_destroy(&view->scene_tree->node);
64 free(view->title_format); 82 free(view->title_format);
65 83
66 if (view->impl->destroy) { 84 if (view->impl->destroy) {
@@ -206,7 +224,7 @@ bool view_ancestor_is_only_visible(struct sway_view *view) {
206 } else { 224 } else {
207 only_visible = true; 225 only_visible = true;
208 } 226 }
209 con = con->parent; 227 con = con->pending.parent;
210 } 228 }
211 return only_visible; 229 return only_visible;
212} 230}
@@ -222,72 +240,73 @@ static bool view_is_only_visible(struct sway_view *view) {
222 } 240 }
223 } 241 }
224 242
225 con = con->parent; 243 con = con->pending.parent;
226 } 244 }
227 245
228 return true; 246 return true;
229} 247}
230 248
231static bool gaps_to_edge(struct sway_view *view) { 249static bool gaps_to_edge(struct sway_view *view) {
232 struct side_gaps gaps = view->container->workspace->current_gaps; 250 struct side_gaps gaps = view->container->pending.workspace->current_gaps;
233 return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0; 251 return gaps.top > 0 || gaps.right > 0 || gaps.bottom > 0 || gaps.left > 0;
234} 252}
235 253
236void view_autoconfigure(struct sway_view *view) { 254void view_autoconfigure(struct sway_view *view) {
237 struct sway_container *con = view->container; 255 struct sway_container *con = view->container;
238 struct sway_workspace *ws = con->workspace; 256 struct sway_workspace *ws = con->pending.workspace;
239 257
240 if (container_is_scratchpad_hidden(con) && 258 if (container_is_scratchpad_hidden(con) &&
241 con->fullscreen_mode != FULLSCREEN_GLOBAL) { 259 con->pending.fullscreen_mode != FULLSCREEN_GLOBAL) {
242 return; 260 return;
243 } 261 }
244 struct sway_output *output = ws ? ws->output : NULL; 262 struct sway_output *output = ws ? ws->output : NULL;
245 263
246 if (con->fullscreen_mode == FULLSCREEN_WORKSPACE) { 264 if (con->pending.fullscreen_mode == FULLSCREEN_WORKSPACE) {
247 con->content_x = output->lx; 265 con->pending.content_x = output->lx;
248 con->content_y = output->ly; 266 con->pending.content_y = output->ly;
249 con->content_width = output->width; 267 con->pending.content_width = output->width;
250 con->content_height = output->height; 268 con->pending.content_height = output->height;
251 return; 269 return;
252 } else if (con->fullscreen_mode == FULLSCREEN_GLOBAL) { 270 } else if (con->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
253 con->content_x = root->x; 271 con->pending.content_x = root->x;
254 con->content_y = root->y; 272 con->pending.content_y = root->y;
255 con->content_width = root->width; 273 con->pending.content_width = root->width;
256 con->content_height = root->height; 274 con->pending.content_height = root->height;
257 return; 275 return;
258 } 276 }
259 277
260 con->border_top = con->border_bottom = true; 278 con->pending.border_top = con->pending.border_bottom = true;
261 con->border_left = con->border_right = true; 279 con->pending.border_left = con->pending.border_right = true;
262 double y_offset = 0; 280 double y_offset = 0;
263 281
264 if (!container_is_floating(con) && ws) { 282 if (!container_is_floating_or_child(con) && ws) {
265 if (config->hide_edge_borders == E_BOTH 283 if (config->hide_edge_borders == E_BOTH
266 || config->hide_edge_borders == E_VERTICAL) { 284 || config->hide_edge_borders == E_VERTICAL) {
267 con->border_left = con->x != ws->x; 285 con->pending.border_left = con->pending.x != ws->x;
268 int right_x = con->x + con->width; 286 int right_x = con->pending.x + con->pending.width;
269 con->border_right = right_x != ws->x + ws->width; 287 con->pending.border_right = right_x != ws->x + ws->width;
270 } 288 }
271 289
272 if (config->hide_edge_borders == E_BOTH 290 if (config->hide_edge_borders == E_BOTH
273 || config->hide_edge_borders == E_HORIZONTAL) { 291 || config->hide_edge_borders == E_HORIZONTAL) {
274 con->border_top = con->y != ws->y; 292 con->pending.border_top = con->pending.y != ws->y;
275 int bottom_y = con->y + con->height; 293 int bottom_y = con->pending.y + con->pending.height;
276 con->border_bottom = bottom_y != ws->y + ws->height; 294 con->pending.border_bottom = bottom_y != ws->y + ws->height;
277 } 295 }
278 296
279 bool smart = config->hide_edge_borders_smart == ESMART_ON || 297 bool smart = config->hide_edge_borders_smart == ESMART_ON ||
280 (config->hide_edge_borders_smart == ESMART_NO_GAPS && 298 (config->hide_edge_borders_smart == ESMART_NO_GAPS &&
281 !gaps_to_edge(view)); 299 !gaps_to_edge(view));
282 if (smart) { 300 if (smart) {
283 bool show_border = container_is_floating_or_child(con) || 301 bool show_border = !view_is_only_visible(view);
284 !view_is_only_visible(view); 302 con->pending.border_left &= show_border;
285 con->border_left &= show_border; 303 con->pending.border_right &= show_border;
286 con->border_right &= show_border; 304 con->pending.border_top &= show_border;
287 con->border_top &= show_border; 305 con->pending.border_bottom &= show_border;
288 con->border_bottom &= show_border;
289 } 306 }
307 }
290 308
309 if (!container_is_floating(con)) {
291 // In a tabbed or stacked container, the container's y is the top of the 310 // 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, 311 // 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. 312 // bar, and disable any top border because we'll always have the title bar.
@@ -298,56 +317,56 @@ void view_autoconfigure(struct sway_view *view) {
298 enum sway_container_layout layout = container_parent_layout(con); 317 enum sway_container_layout layout = container_parent_layout(con);
299 if (layout == L_TABBED) { 318 if (layout == L_TABBED) {
300 y_offset = container_titlebar_height(); 319 y_offset = container_titlebar_height();
301 con->border_top = false; 320 con->pending.border_top = false;
302 } else if (layout == L_STACKED) { 321 } else if (layout == L_STACKED) {
303 y_offset = container_titlebar_height() * siblings->length; 322 y_offset = container_titlebar_height() * siblings->length;
304 con->border_top = false; 323 con->pending.border_top = false;
305 } 324 }
306 } 325 }
307 } 326 }
308 327
309 double x, y, width, height; 328 double x, y, width, height;
310 switch (con->border) { 329 switch (con->pending.border) {
311 default: 330 default:
312 case B_CSD: 331 case B_CSD:
313 case B_NONE: 332 case B_NONE:
314 x = con->x; 333 x = con->pending.x;
315 y = con->y + y_offset; 334 y = con->pending.y + y_offset;
316 width = con->width; 335 width = con->pending.width;
317 height = con->height - y_offset; 336 height = con->pending.height - y_offset;
318 break; 337 break;
319 case B_PIXEL: 338 case B_PIXEL:
320 x = con->x + con->border_thickness * con->border_left; 339 x = con->pending.x + con->pending.border_thickness * con->pending.border_left;
321 y = con->y + con->border_thickness * con->border_top + y_offset; 340 y = con->pending.y + con->pending.border_thickness * con->pending.border_top + y_offset;
322 width = con->width 341 width = con->pending.width
323 - con->border_thickness * con->border_left 342 - con->pending.border_thickness * con->pending.border_left
324 - con->border_thickness * con->border_right; 343 - con->pending.border_thickness * con->pending.border_right;
325 height = con->height - y_offset 344 height = con->pending.height - y_offset
326 - con->border_thickness * con->border_top 345 - con->pending.border_thickness * con->pending.border_top
327 - con->border_thickness * con->border_bottom; 346 - con->pending.border_thickness * con->pending.border_bottom;
328 break; 347 break;
329 case B_NORMAL: 348 case B_NORMAL:
330 // Height is: 1px border + 3px pad + title height + 3px pad + 1px border 349 // Height is: 1px border + 3px pad + title height + 3px pad + 1px border
331 x = con->x + con->border_thickness * con->border_left; 350 x = con->pending.x + con->pending.border_thickness * con->pending.border_left;
332 width = con->width 351 width = con->pending.width
333 - con->border_thickness * con->border_left 352 - con->pending.border_thickness * con->pending.border_left
334 - con->border_thickness * con->border_right; 353 - con->pending.border_thickness * con->pending.border_right;
335 if (y_offset) { 354 if (y_offset) {
336 y = con->y + y_offset; 355 y = con->pending.y + y_offset;
337 height = con->height - y_offset 356 height = con->pending.height - y_offset
338 - con->border_thickness * con->border_bottom; 357 - con->pending.border_thickness * con->pending.border_bottom;
339 } else { 358 } else {
340 y = con->y + container_titlebar_height(); 359 y = con->pending.y + container_titlebar_height();
341 height = con->height - container_titlebar_height() 360 height = con->pending.height - container_titlebar_height()
342 - con->border_thickness * con->border_bottom; 361 - con->pending.border_thickness * con->pending.border_bottom;
343 } 362 }
344 break; 363 break;
345 } 364 }
346 365
347 con->content_x = x; 366 con->pending.content_x = x;
348 con->content_y = y; 367 con->pending.content_y = y;
349 con->content_width = width; 368 con->pending.content_width = width;
350 con->content_height = height; 369 con->pending.content_height = height;
351} 370}
352 371
353void view_set_activated(struct sway_view *view, bool activated) { 372void view_set_activated(struct sway_view *view, bool activated) {
@@ -360,17 +379,17 @@ void view_set_activated(struct sway_view *view, bool activated) {
360 } 379 }
361} 380}
362 381
363void view_request_activate(struct sway_view *view) { 382void view_request_activate(struct sway_view *view, struct sway_seat *seat) {
364 struct sway_workspace *ws = view->container->workspace; 383 struct sway_workspace *ws = view->container->pending.workspace;
365 if (!ws) { // hidden scratchpad container 384 if (!seat) {
366 return; 385 seat = input_manager_current_seat();
367 } 386 }
368 struct sway_seat *seat = input_manager_current_seat();
369 387
370 switch (config->focus_on_window_activation) { 388 switch (config->focus_on_window_activation) {
371 case FOWA_SMART: 389 case FOWA_SMART:
372 if (workspace_is_visible(ws)) { 390 if (ws && workspace_is_visible(ws)) {
373 seat_set_focus_container(seat, view->container); 391 seat_set_focus_container(seat, view->container);
392 container_raise_floating(view->container);
374 } else { 393 } else {
375 view_set_urgent(view, true); 394 view_set_urgent(view, true);
376 } 395 }
@@ -379,11 +398,23 @@ void view_request_activate(struct sway_view *view) {
379 view_set_urgent(view, true); 398 view_set_urgent(view, true);
380 break; 399 break;
381 case FOWA_FOCUS: 400 case FOWA_FOCUS:
382 seat_set_focus_container(seat, view->container); 401 if (container_is_scratchpad_hidden_or_child(view->container)) {
402 root_scratchpad_show(view->container);
403 } else {
404 seat_set_focus_container(seat, view->container);
405 container_raise_floating(view->container);
406 }
383 break; 407 break;
384 case FOWA_NONE: 408 case FOWA_NONE:
385 break; 409 break;
386 } 410 }
411 transaction_commit_dirty();
412}
413
414void view_request_urgent(struct sway_view *view) {
415 if (config->focus_on_window_activation != FOWA_NONE) {
416 view_set_urgent(view, true);
417 }
387} 418}
388 419
389void view_set_csd_from_server(struct sway_view *view, bool enabled) { 420void view_set_csd_from_server(struct sway_view *view, bool enabled) {
@@ -401,13 +432,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) { 432void view_update_csd_from_client(struct sway_view *view, bool enabled) {
402 sway_log(SWAY_DEBUG, "View %p updated CSD to %i", view, enabled); 433 sway_log(SWAY_DEBUG, "View %p updated CSD to %i", view, enabled);
403 struct sway_container *con = view->container; 434 struct sway_container *con = view->container;
404 if (enabled && con && con->border != B_CSD) { 435 if (enabled && con && con->pending.border != B_CSD) {
405 con->saved_border = con->border; 436 con->saved_border = con->pending.border;
406 if (container_is_floating(con)) { 437 if (container_is_floating(con)) {
407 con->border = B_CSD; 438 con->pending.border = B_CSD;
408 } 439 }
409 } else if (!enabled && con && con->border == B_CSD) { 440 } else if (!enabled && con && con->pending.border == B_CSD) {
410 con->border = con->saved_border; 441 con->pending.border = con->saved_border;
411 } 442 }
412 view->using_csd = enabled; 443 view->using_csd = enabled;
413} 444}
@@ -430,49 +461,6 @@ void view_close_popups(struct sway_view *view) {
430 } 461 }
431} 462}
432 463
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, 464static bool view_has_executed_criteria(struct sway_view *view,
477 struct criteria *criteria) { 465 struct criteria *criteria) {
478 for (int i = 0; i < view->executed_criteria->length; ++i) { 466 for (int i = 0; i < view->executed_criteria->length; ++i) {
@@ -514,7 +502,7 @@ static void view_populate_pid(struct sway_view *view) {
514#if HAVE_XWAYLAND 502#if HAVE_XWAYLAND
515 case SWAY_VIEW_XWAYLAND:; 503 case SWAY_VIEW_XWAYLAND:;
516 struct wlr_xwayland_surface *surf = 504 struct wlr_xwayland_surface *surf =
517 wlr_xwayland_surface_from_wlr_surface(view->surface); 505 wlr_xwayland_surface_try_from_wlr_surface(view->surface);
518 pid = surf->pid; 506 pid = surf->pid;
519 break; 507 break;
520#endif 508#endif
@@ -527,6 +515,20 @@ static void view_populate_pid(struct sway_view *view) {
527 view->pid = pid; 515 view->pid = pid;
528} 516}
529 517
518void view_assign_ctx(struct sway_view *view, struct launcher_ctx *ctx) {
519 if (view->ctx) {
520 // This ctx has been replaced
521 launcher_ctx_destroy(view->ctx);
522 view->ctx = NULL;
523 }
524 if (ctx == NULL) {
525 return;
526 }
527 launcher_ctx_consume(ctx);
528
529 view->ctx = ctx;
530}
531
530static struct sway_workspace *select_workspace(struct sway_view *view) { 532static struct sway_workspace *select_workspace(struct sway_view *view) {
531 struct sway_seat *seat = input_manager_current_seat(); 533 struct sway_seat *seat = input_manager_current_seat();
532 534
@@ -562,13 +564,14 @@ static struct sway_workspace *select_workspace(struct sway_view *view) {
562 } 564 }
563 list_free(criterias); 565 list_free(criterias);
564 if (ws) { 566 if (ws) {
565 root_remove_workspace_pid(view->pid); 567 view_assign_ctx(view, NULL);
566 return ws; 568 return ws;
567 } 569 }
568 570
569 // Check if there's a PID mapping 571 // Check if there's a PID mapping
570 ws = root_workspace_for_pid(view->pid); 572 ws = view->ctx ? launcher_ctx_get_workspace(view->ctx) : NULL;
571 if (ws) { 573 if (ws) {
574 view_assign_ctx(view, NULL);
572 return ws; 575 return ws;
573 } 576 }
574 577
@@ -577,7 +580,7 @@ static struct sway_workspace *select_workspace(struct sway_view *view) {
577 if (node && node->type == N_WORKSPACE) { 580 if (node && node->type == N_WORKSPACE) {
578 return node->sway_workspace; 581 return node->sway_workspace;
579 } else if (node && node->type == N_CONTAINER) { 582 } else if (node && node->type == N_CONTAINER) {
580 return node->sway_container->workspace; 583 return node->sway_container->pending.workspace;
581 } 584 }
582 585
583 // When there's no outputs connected, the above should match a workspace on 586 // When there's no outputs connected, the above should match a workspace on
@@ -586,16 +589,29 @@ static struct sway_workspace *select_workspace(struct sway_view *view) {
586 return NULL; 589 return NULL;
587} 590}
588 591
592static void update_ext_foreign_toplevel(struct sway_view *view) {
593 struct wlr_ext_foreign_toplevel_handle_v1_state toplevel_state = {
594 .app_id = view_get_app_id(view),
595 .title = view_get_title(view),
596 };
597 wlr_ext_foreign_toplevel_handle_v1_update_state(view->ext_foreign_toplevel, &toplevel_state);
598}
599
589static bool should_focus(struct sway_view *view) { 600static bool should_focus(struct sway_view *view) {
590 struct sway_seat *seat = input_manager_current_seat(); 601 struct sway_seat *seat = input_manager_current_seat();
591 struct sway_container *prev_con = seat_get_focused_container(seat); 602 struct sway_container *prev_con = seat_get_focused_container(seat);
592 struct sway_workspace *prev_ws = seat_get_focused_workspace(seat); 603 struct sway_workspace *prev_ws = seat_get_focused_workspace(seat);
593 struct sway_workspace *map_ws = view->container->workspace; 604 struct sway_workspace *map_ws = view->container->pending.workspace;
594 605
595 if (view->container->fullscreen_mode == FULLSCREEN_GLOBAL) { 606 if (view->container->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
596 return true; 607 return true;
597 } 608 }
598 609
610 // View opened "under" fullscreen view should not be given focus.
611 if (root->fullscreen_global || !map_ws || map_ws->fullscreen) {
612 return false;
613 }
614
599 // Views can only take focus if they are mapped into the active workspace 615 // Views can only take focus if they are mapped into the active workspace
600 if (prev_ws != map_ws) { 616 if (prev_ws != map_ws) {
601 return false; 617 return false;
@@ -603,9 +619,9 @@ static bool should_focus(struct sway_view *view) {
603 619
604 // If the view is the only one in the focused workspace, it'll get focus 620 // If the view is the only one in the focused workspace, it'll get focus
605 // regardless of any no_focus criteria. 621 // regardless of any no_focus criteria.
606 if (!view->container->parent && !prev_con) { 622 if (!view->container->pending.parent && !prev_con) {
607 size_t num_children = view->container->workspace->tiling->length + 623 size_t num_children = view->container->pending.workspace->tiling->length +
608 view->container->workspace->floating->length; 624 view->container->pending.workspace->floating->length;
609 if (num_children == 1) { 625 if (num_children == 1) {
610 return true; 626 return true;
611 } 627 }
@@ -635,6 +651,7 @@ static void handle_foreign_activate_request(
635 break; 651 break;
636 } 652 }
637 } 653 }
654 transaction_commit_dirty();
638} 655}
639 656
640static void handle_foreign_fullscreen_request( 657static void handle_foreign_fullscreen_request(
@@ -645,9 +662,21 @@ static void handle_foreign_fullscreen_request(
645 662
646 // Match fullscreen command behavior for scratchpad hidden views 663 // Match fullscreen command behavior for scratchpad hidden views
647 struct sway_container *container = view->container; 664 struct sway_container *container = view->container;
648 if (!container->workspace) { 665 if (!container->pending.workspace) {
649 while (container->parent) { 666 while (container->pending.parent) {
650 container = container->parent; 667 container = container->pending.parent;
668 }
669 }
670
671 if (event->fullscreen && event->output && event->output->data) {
672 struct sway_output *output = event->output->data;
673 struct sway_workspace *ws = output_get_active_workspace(output);
674 if (ws && !container_is_scratchpad_hidden(view->container)) {
675 if (container_is_floating(view->container)) {
676 workspace_add_floating(ws, view->container);
677 } else {
678 workspace_add_tiling(ws, view->container);
679 }
651 } 680 }
652 } 681 }
653 682
@@ -656,12 +685,13 @@ static void handle_foreign_fullscreen_request(
656 if (event->fullscreen) { 685 if (event->fullscreen) {
657 arrange_root(); 686 arrange_root();
658 } else { 687 } else {
659 if (container->parent) { 688 if (container->pending.parent) {
660 arrange_container(container->parent); 689 arrange_container(container->pending.parent);
661 } else if (container->workspace) { 690 } else if (container->pending.workspace) {
662 arrange_workspace(container->workspace); 691 arrange_workspace(container->pending.workspace);
663 } 692 }
664 } 693 }
694 transaction_commit_dirty();
665} 695}
666 696
667static void handle_foreign_close_request( 697static void handle_foreign_close_request(
@@ -692,6 +722,13 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
692 view_populate_pid(view); 722 view_populate_pid(view);
693 view->container = container_create(view); 723 view->container = container_create(view);
694 724
725 if (view->ctx == NULL) {
726 struct launcher_ctx *ctx = launcher_ctx_find_pid(view->pid);
727 if (ctx != NULL) {
728 view_assign_ctx(view, ctx);
729 }
730 }
731
695 // If there is a request to be opened fullscreen on a specific output, try 732 // 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, 733 // to honor that request. Otherwise, fallback to assigns, pid mappings,
697 // focused workspace, etc 734 // focused workspace, etc
@@ -705,10 +742,36 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
705 } 742 }
706 743
707 struct sway_seat *seat = input_manager_current_seat(); 744 struct sway_seat *seat = input_manager_current_seat();
708 struct sway_node *node = ws ? seat_get_focus_inactive(seat, &ws->node) 745 struct sway_node *node =
709 : seat_get_focus_inactive(seat, &root->node); 746 seat_get_focus_inactive(seat, ws ? &ws->node : &root->node);
710 struct sway_container *target_sibling = node->type == N_CONTAINER ? 747 struct sway_container *target_sibling = NULL;
711 node->sway_container : NULL; 748 if (node && node->type == N_CONTAINER) {
749 if (container_is_floating(node->sway_container)) {
750 // If we're about to launch the view into the floating container, then
751 // launch it as a tiled view instead.
752 if (ws) {
753 target_sibling = seat_get_focus_inactive_tiling(seat, ws);
754 if (target_sibling) {
755 struct sway_container *con =
756 seat_get_focus_inactive_view(seat, &target_sibling->node);
757 if (con) {
758 target_sibling = con;
759 }
760 }
761 } else {
762 ws = seat_get_last_known_workspace(seat);
763 }
764 } else {
765 target_sibling = node->sway_container;
766 }
767 }
768
769 struct wlr_ext_foreign_toplevel_handle_v1_state foreign_toplevel_state = {
770 .app_id = view_get_app_id(view),
771 .title = view_get_title(view),
772 };
773 view->ext_foreign_toplevel =
774 wlr_ext_foreign_toplevel_handle_v1_create(server.foreign_toplevel_list, &foreign_toplevel_state);
712 775
713 view->foreign_toplevel = 776 view->foreign_toplevel =
714 wlr_foreign_toplevel_handle_v1_create(server.foreign_toplevel_manager); 777 wlr_foreign_toplevel_handle_v1_create(server.foreign_toplevel_manager);
@@ -725,13 +788,6 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
725 wl_signal_add(&view->foreign_toplevel->events.destroy, 788 wl_signal_add(&view->foreign_toplevel->events.destroy,
726 &view->foreign_destroy); 789 &view->foreign_destroy);
727 790
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; 791 struct sway_container *container = view->container;
736 if (target_sibling) { 792 if (target_sibling) {
737 container_add_sibling(target_sibling, container, 1); 793 container_add_sibling(target_sibling, container, 1);
@@ -740,30 +796,25 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
740 } 796 }
741 ipc_event_window(view->container, "new"); 797 ipc_event_window(view->container, "new");
742 798
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) { 799 if (decoration) {
749 view_update_csd_from_client(view, decoration); 800 view_update_csd_from_client(view, decoration);
750 } 801 }
751 802
752 if (view->impl->wants_floating && view->impl->wants_floating(view)) { 803 if (view->impl->wants_floating && view->impl->wants_floating(view)) {
753 view->container->border = config->floating_border; 804 view->container->pending.border = config->floating_border;
754 view->container->border_thickness = config->floating_border_thickness; 805 view->container->pending.border_thickness = config->floating_border_thickness;
755 container_set_floating(view->container, true); 806 container_set_floating(view->container, true);
756 } else { 807 } else {
757 view->container->border = config->border; 808 view->container->pending.border = config->border;
758 view->container->border_thickness = config->border_thickness; 809 view->container->pending.border_thickness = config->border_thickness;
759 view_set_tiled(view, true); 810 view_set_tiled(view, true);
760 } 811 }
761 812
762 if (config->popup_during_fullscreen == POPUP_LEAVE && 813 if (config->popup_during_fullscreen == POPUP_LEAVE &&
763 container->workspace && 814 container->pending.workspace &&
764 container->workspace->fullscreen && 815 container->pending.workspace->fullscreen &&
765 container->workspace->fullscreen->view) { 816 container->pending.workspace->fullscreen->view) {
766 struct sway_container *fs = container->workspace->fullscreen; 817 struct sway_container *fs = container->pending.workspace->fullscreen;
767 if (view_is_transient_for(view, fs->view)) { 818 if (view_is_transient_for(view, fs->view)) {
768 container_set_fullscreen(fs, false); 819 container_set_fullscreen(fs, false);
769 } 820 }
@@ -774,12 +825,12 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
774 825
775 if (fullscreen) { 826 if (fullscreen) {
776 container_set_fullscreen(view->container, true); 827 container_set_fullscreen(view->container, true);
777 arrange_workspace(view->container->workspace); 828 arrange_workspace(view->container->pending.workspace);
778 } else { 829 } else {
779 if (container->parent) { 830 if (container->pending.parent) {
780 arrange_container(container->parent); 831 arrange_container(container->pending.parent);
781 } else if (container->workspace) { 832 } else if (container->pending.workspace) {
782 arrange_workspace(container->workspace); 833 arrange_workspace(container->pending.workspace);
783 } 834 }
784 } 835 }
785 836
@@ -788,11 +839,10 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
788 bool set_focus = should_focus(view); 839 bool set_focus = should_focus(view);
789 840
790#if HAVE_XWAYLAND 841#if HAVE_XWAYLAND
791 if (wlr_surface_is_xwayland_surface(wlr_surface)) { 842 struct wlr_xwayland_surface *xsurface;
792 struct wlr_xwayland_surface *xsurface = 843 if ((xsurface = wlr_xwayland_surface_try_from_wlr_surface(wlr_surface))) {
793 wlr_xwayland_surface_from_wlr_surface(wlr_surface); 844 set_focus &= wlr_xwayland_icccm_input_model(xsurface) !=
794 set_focus = (wlr_xwayland_icccm_input_model(xsurface) != 845 WLR_ICCCM_INPUT_MODEL_NONE;
795 WLR_ICCCM_INPUT_MODEL_NONE) && set_focus;
796 } 846 }
797#endif 847#endif
798 848
@@ -800,34 +850,41 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface,
800 input_manager_set_focus(&view->container->node); 850 input_manager_set_focus(&view->container->node);
801 } 851 }
802 852
853 if (view->ext_foreign_toplevel) {
854 update_ext_foreign_toplevel(view);
855 }
856
803 const char *app_id; 857 const char *app_id;
804 const char *class; 858 const char *class;
805 if ((app_id = view_get_app_id(view)) != NULL) { 859 if ((app_id = view_get_app_id(view)) != NULL) {
806 wlr_foreign_toplevel_handle_v1_set_app_id( 860 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) { 861 } else if ((class = view_get_class(view)) != NULL) {
809 wlr_foreign_toplevel_handle_v1_set_app_id( 862 wlr_foreign_toplevel_handle_v1_set_app_id(view->foreign_toplevel, class);
810 view->foreign_toplevel, class);
811 } 863 }
812} 864}
813 865
814void view_unmap(struct sway_view *view) { 866void view_unmap(struct sway_view *view) {
815 wl_signal_emit(&view->events.unmap, view); 867 wl_signal_emit_mutable(&view->events.unmap, view);
816 868
817 wl_list_remove(&view->surface_new_subsurface.link); 869 view->executed_criteria->length = 0;
818 870
819 if (view->urgent_timer) { 871 if (view->urgent_timer) {
820 wl_event_source_remove(view->urgent_timer); 872 wl_event_source_remove(view->urgent_timer);
821 view->urgent_timer = NULL; 873 view->urgent_timer = NULL;
822 } 874 }
823 875
876 if (view->ext_foreign_toplevel) {
877 wlr_ext_foreign_toplevel_handle_v1_destroy(view->ext_foreign_toplevel);
878 view->ext_foreign_toplevel = NULL;
879 }
880
824 if (view->foreign_toplevel) { 881 if (view->foreign_toplevel) {
825 wlr_foreign_toplevel_handle_v1_destroy(view->foreign_toplevel); 882 wlr_foreign_toplevel_handle_v1_destroy(view->foreign_toplevel);
826 view->foreign_toplevel = NULL; 883 view->foreign_toplevel = NULL;
827 } 884 }
828 885
829 struct sway_container *parent = view->container->parent; 886 struct sway_container *parent = view->container->pending.parent;
830 struct sway_workspace *ws = view->container->workspace; 887 struct sway_workspace *ws = view->container->pending.workspace;
831 container_begin_destroy(view->container); 888 container_begin_destroy(view->container);
832 if (parent) { 889 if (parent) {
833 container_reap_empty(parent); 890 container_reap_empty(parent);
@@ -860,262 +917,54 @@ void view_unmap(struct sway_view *view) {
860 view->surface = NULL; 917 view->surface = NULL;
861} 918}
862 919
863void view_update_size(struct sway_view *view, int width, int height) { 920void view_update_size(struct sway_view *view) {
864 struct sway_container *con = view->container; 921 struct sway_container *con = view->container;
865 922 con->pending.content_width = view->geometry.width;
866 if (container_is_floating(con)) { 923 con->pending.content_height = view->geometry.height;
867 con->content_width = width; 924 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} 925}
877 926
878static const struct sway_view_child_impl subsurface_impl; 927void view_center_and_clip_surface(struct sway_view *view) {
928 struct sway_container *con = view->container;
879 929
880static void subsurface_get_root_coords(struct sway_view_child *child, 930 if (container_is_floating(con)) {
881 int *root_sx, int *root_sy) { 931 // We always center the current coordinates rather than the next, as the
882 struct wlr_surface *surface = child->surface; 932 // geometry immediately affects the currently active rendering.
883 *root_sx = -child->view->geometry.x; 933 int x = (int) fmax(0, (con->current.content_width - view->geometry.width) / 2);
884 *root_sy = -child->view->geometry.y; 934 int y = (int) fmax(0, (con->current.content_height - view->geometry.height) / 2);
885 935
886 if (child->parent && child->parent->impl && 936 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 { 937 } else {
893 while (surface && wlr_surface_is_subsurface(surface)) { 938 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 } 939 }
904}
905 940
906static void subsurface_destroy(struct sway_view_child *child) { 941 // only make sure to clip the content if there is content to clip
907 if (!sway_assert(child->impl == &subsurface_impl, 942 if (!wl_list_empty(&con->view->content_tree->children)) {
908 "Expected a subsurface")) { 943 wlr_scene_subsurface_tree_set_clip(&con->view->content_tree->node, &(struct wlr_box){
909 return; 944 .x = con->view->geometry.x,
910 } 945 .y = con->view->geometry.y,
911 struct sway_subsurface *subsurface = (struct sway_subsurface *)child; 946 .width = con->current.content_width,
912 wl_list_remove(&subsurface->destroy.link); 947 .height = con->current.content_height,
913 free(subsurface); 948 });
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 }
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
989static void view_child_handle_surface_new_subsurface(
990 struct wl_listener *listener, void *data) {
991 struct sway_view_child *child =
992 wl_container_of(listener, child, surface_new_subsurface);
993 struct wlr_subsurface *subsurface = data;
994 view_child_subsurface_create(child, subsurface);
995}
996
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 } 949 }
1098} 950}
1099 951
1100struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) { 952struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) {
1101 if (wlr_surface_is_xdg_surface(wlr_surface)) { 953 struct wlr_xdg_surface *xdg_surface;
1102 struct wlr_xdg_surface *xdg_surface = 954 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); 955 return view_from_wlr_xdg_surface(xdg_surface);
1105 } 956 }
1106#if HAVE_XWAYLAND 957#if HAVE_XWAYLAND
1107 if (wlr_surface_is_xwayland_surface(wlr_surface)) { 958 struct wlr_xwayland_surface *xsurface;
1108 struct wlr_xwayland_surface *xsurface = 959 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); 960 return view_from_wlr_xwayland_surface(xsurface);
1111 } 961 }
1112#endif 962#endif
1113 if (wlr_surface_is_subsurface(wlr_surface)) { 963 struct wlr_subsurface *subsurface;
1114 struct wlr_subsurface *subsurface = 964 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); 965 return view_from_wlr_surface(subsurface->parent);
1117 } 966 }
1118 if (wlr_surface_is_layer_surface(wlr_surface)) { 967 if (wlr_layer_surface_v1_try_from_wlr_surface(wlr_surface) != NULL) {
1119 return NULL; 968 return NULL;
1120 } 969 }
1121 970
@@ -1196,6 +1045,18 @@ static size_t parse_title_format(struct sway_view *view, char *buffer) {
1196 return len; 1045 return len;
1197} 1046}
1198 1047
1048void view_update_app_id(struct sway_view *view) {
1049 const char *app_id = view_get_app_id(view);
1050
1051 if (view->foreign_toplevel && app_id) {
1052 wlr_foreign_toplevel_handle_v1_set_app_id(view->foreign_toplevel, app_id);
1053 }
1054
1055 if (view->ext_foreign_toplevel) {
1056 update_ext_foreign_toplevel(view);
1057 }
1058}
1059
1199void view_update_title(struct sway_view *view, bool force) { 1060void view_update_title(struct sway_view *view, bool force) {
1200 const char *title = view_get_title(view); 1061 const char *title = view_get_title(view);
1201 1062
@@ -1211,46 +1072,56 @@ void view_update_title(struct sway_view *view, bool force) {
1211 1072
1212 free(view->container->title); 1073 free(view->container->title);
1213 free(view->container->formatted_title); 1074 free(view->container->formatted_title);
1214 if (title) { 1075
1215 size_t len = parse_title_format(view, NULL); 1076 size_t len = parse_title_format(view, NULL);
1077
1078 if (len) {
1216 char *buffer = calloc(len + 1, sizeof(char)); 1079 char *buffer = calloc(len + 1, sizeof(char));
1217 if (!sway_assert(buffer, "Unable to allocate title string")) { 1080 if (!sway_assert(buffer, "Unable to allocate title string")) {
1218 return; 1081 return;
1219 } 1082 }
1220 parse_title_format(view, buffer);
1221 1083
1222 view->container->title = strdup(title); 1084 parse_title_format(view, buffer);
1223 view->container->formatted_title = buffer; 1085 view->container->formatted_title = buffer;
1224 } else { 1086 } else {
1225 view->container->title = NULL;
1226 view->container->formatted_title = NULL; 1087 view->container->formatted_title = NULL;
1227 } 1088 }
1228 container_calculate_title_height(view->container); 1089
1229 config_update_font_height(false); 1090 view->container->title = title ? strdup(title) : NULL;
1230 1091
1231 // Update title after the global font height is updated 1092 // Update title after the global font height is updated
1232 container_update_title_textures(view->container); 1093 if (view->container->title_bar.title_text && len) {
1094 sway_text_node_set_text(view->container->title_bar.title_text,
1095 view->container->formatted_title);
1096 container_arrange_title_bar(view->container);
1097 } else {
1098 container_update_title_bar(view->container);
1099 }
1233 1100
1234 ipc_event_window(view->container, "title"); 1101 ipc_event_window(view->container, "title");
1235 1102
1236 if (view->foreign_toplevel && title) { 1103 if (view->foreign_toplevel && title) {
1237 wlr_foreign_toplevel_handle_v1_set_title(view->foreign_toplevel, title); 1104 wlr_foreign_toplevel_handle_v1_set_title(view->foreign_toplevel, title);
1238 } 1105 }
1106
1107 if (view->ext_foreign_toplevel) {
1108 update_ext_foreign_toplevel(view);
1109 }
1239} 1110}
1240 1111
1241bool view_is_visible(struct sway_view *view) { 1112bool view_is_visible(struct sway_view *view) {
1242 if (view->container->node.destroying) { 1113 if (view->container->node.destroying) {
1243 return false; 1114 return false;
1244 } 1115 }
1245 struct sway_workspace *workspace = view->container->workspace; 1116 struct sway_workspace *workspace = view->container->pending.workspace;
1246 if (!workspace && view->container->fullscreen_mode != FULLSCREEN_GLOBAL) { 1117 if (!workspace && view->container->pending.fullscreen_mode != FULLSCREEN_GLOBAL) {
1247 bool fs_global_descendant = false; 1118 bool fs_global_descendant = false;
1248 struct sway_container *parent = view->container->parent; 1119 struct sway_container *parent = view->container->pending.parent;
1249 while (parent) { 1120 while (parent) {
1250 if (parent->fullscreen_mode == FULLSCREEN_GLOBAL) { 1121 if (parent->pending.fullscreen_mode == FULLSCREEN_GLOBAL) {
1251 fs_global_descendant = true; 1122 fs_global_descendant = true;
1252 } 1123 }
1253 parent = parent->parent; 1124 parent = parent->pending.parent;
1254 } 1125 }
1255 if (!fs_global_descendant) { 1126 if (!fs_global_descendant) {
1256 return false; 1127 return false;
@@ -1268,13 +1139,13 @@ bool view_is_visible(struct sway_view *view) {
1268 enum sway_container_layout layout = container_parent_layout(con); 1139 enum sway_container_layout layout = container_parent_layout(con);
1269 if ((layout == L_TABBED || layout == L_STACKED) 1140 if ((layout == L_TABBED || layout == L_STACKED)
1270 && !container_is_floating(con)) { 1141 && !container_is_floating(con)) {
1271 struct sway_node *parent = con->parent ? 1142 struct sway_node *parent = con->pending.parent ?
1272 &con->parent->node : &con->workspace->node; 1143 &con->pending.parent->node : &con->pending.workspace->node;
1273 if (seat_get_active_tiling_child(seat, parent) != &con->node) { 1144 if (seat_get_active_tiling_child(seat, parent) != &con->node) {
1274 return false; 1145 return false;
1275 } 1146 }
1276 } 1147 }
1277 con = con->parent; 1148 con = con->pending.parent;
1278 } 1149 }
1279 // Check view isn't hidden by another fullscreen view 1150 // Check view isn't hidden by another fullscreen view
1280 struct sway_container *fs = root->fullscreen_global ? 1151 struct sway_container *fs = root->fullscreen_global ?
@@ -1296,6 +1167,7 @@ void view_set_urgent(struct sway_view *view, bool enable) {
1296 return; 1167 return;
1297 } 1168 }
1298 clock_gettime(CLOCK_MONOTONIC, &view->urgent); 1169 clock_gettime(CLOCK_MONOTONIC, &view->urgent);
1170 container_update_itself_and_parents(view->container);
1299 } else { 1171 } else {
1300 view->urgent = (struct timespec){ 0 }; 1172 view->urgent = (struct timespec){ 0 };
1301 if (view->urgent_timer) { 1173 if (view->urgent_timer) {
@@ -1303,12 +1175,11 @@ void view_set_urgent(struct sway_view *view, bool enable) {
1303 view->urgent_timer = NULL; 1175 view->urgent_timer = NULL;
1304 } 1176 }
1305 } 1177 }
1306 container_damage_whole(view->container);
1307 1178
1308 ipc_event_window(view->container, "urgent"); 1179 ipc_event_window(view->container, "urgent");
1309 1180
1310 if (!container_is_scratchpad_hidden(view->container)) { 1181 if (!container_is_scratchpad_hidden(view->container)) {
1311 workspace_detect_urgent(view->container->workspace); 1182 workspace_detect_urgent(view->container->pending.workspace);
1312 } 1183 }
1313} 1184}
1314 1185
@@ -1317,40 +1188,54 @@ bool view_is_urgent(struct sway_view *view) {
1317} 1188}
1318 1189
1319void view_remove_saved_buffer(struct sway_view *view) { 1190void view_remove_saved_buffer(struct sway_view *view) {
1320 if (!sway_assert(!wl_list_empty(&view->saved_buffers), "Expected a saved buffer")) { 1191 if (!sway_assert(view->saved_surface_tree, "Expected a saved buffer")) {
1321 return; 1192 return;
1322 } 1193 }
1323 struct sway_saved_buffer *saved_buf, *tmp; 1194
1324 wl_list_for_each_safe(saved_buf, tmp, &view->saved_buffers, link) { 1195 wlr_scene_node_destroy(&view->saved_surface_tree->node);
1325 wlr_buffer_unlock(&saved_buf->buffer->base); 1196 view->saved_surface_tree = NULL;
1326 wl_list_remove(&saved_buf->link); 1197 wlr_scene_node_set_enabled(&view->content_tree->node, true);
1327 free(saved_buf);
1328 }
1329} 1198}
1330 1199
1331static void view_save_buffer_iterator(struct wlr_surface *surface, 1200static void view_save_buffer_iterator(struct wlr_scene_buffer *buffer,
1332 int sx, int sy, void *data) { 1201 int sx, int sy, void *data) {
1333 struct sway_view *view = data; 1202 struct wlr_scene_tree *tree = data;
1334 1203
1335 if (surface && wlr_surface_has_buffer(surface)) { 1204 struct wlr_scene_buffer *sbuf = wlr_scene_buffer_create(tree, NULL);
1336 wlr_buffer_lock(&surface->buffer->base); 1205 if (!sbuf) {
1337 struct sway_saved_buffer *saved_buffer = calloc(1, sizeof(struct sway_saved_buffer)); 1206 sway_log(SWAY_ERROR, "Could not allocate a scene buffer when saving a surface");
1338 saved_buffer->buffer = surface->buffer; 1207 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 } 1208 }
1209
1210 wlr_scene_buffer_set_dest_size(sbuf,
1211 buffer->dst_width, buffer->dst_height);
1212 wlr_scene_buffer_set_opaque_region(sbuf, &buffer->opaque_region);
1213 wlr_scene_buffer_set_source_box(sbuf, &buffer->src_box);
1214 wlr_scene_node_set_position(&sbuf->node, sx, sy);
1215 wlr_scene_buffer_set_transform(sbuf, buffer->transform);
1216 wlr_scene_buffer_set_buffer(sbuf, buffer->buffer);
1347} 1217}
1348 1218
1349void view_save_buffer(struct sway_view *view) { 1219void view_save_buffer(struct sway_view *view) {
1350 if (!sway_assert(wl_list_empty(&view->saved_buffers), "Didn't expect saved buffer")) { 1220 if (!sway_assert(!view->saved_surface_tree, "Didn't expect saved buffer")) {
1351 view_remove_saved_buffer(view); 1221 view_remove_saved_buffer(view);
1352 } 1222 }
1353 view_for_each_surface(view, view_save_buffer_iterator, view); 1223
1224 view->saved_surface_tree = wlr_scene_tree_create(view->scene_tree);
1225 if (!view->saved_surface_tree) {
1226 sway_log(SWAY_ERROR, "Could not allocate a scene tree node when saving a surface");
1227 return;
1228 }
1229
1230 // Enable and disable the saved surface tree like so to atomitaclly update
1231 // the tree. This will prevent over damaging or other weirdness.
1232 wlr_scene_node_set_enabled(&view->saved_surface_tree->node, false);
1233
1234 wlr_scene_node_for_each_buffer(&view->content_tree->node,
1235 view_save_buffer_iterator, view->saved_surface_tree);
1236
1237 wlr_scene_node_set_enabled(&view->content_tree->node, false);
1238 wlr_scene_node_set_enabled(&view->saved_surface_tree->node, true);
1354} 1239}
1355 1240
1356bool view_is_transient_for(struct sway_view *child, 1241bool view_is_transient_for(struct sway_view *child,
@@ -1358,3 +1243,19 @@ bool view_is_transient_for(struct sway_view *child,
1358 return child->impl->is_transient_for && 1243 return child->impl->is_transient_for &&
1359 child->impl->is_transient_for(child, ancestor); 1244 child->impl->is_transient_for(child, ancestor);
1360} 1245}
1246
1247static void send_frame_done_iterator(struct wlr_scene_buffer *scene_buffer,
1248 int x, int y, void *data) {
1249 struct timespec *when = data;
1250 wl_signal_emit_mutable(&scene_buffer->events.frame_done, when);
1251}
1252
1253void view_send_frame_done(struct sway_view *view) {
1254 struct timespec when;
1255 clock_gettime(CLOCK_MONOTONIC, &when);
1256
1257 struct wlr_scene_node *node;
1258 wl_list_for_each(node, &view->content_tree->children, link) {
1259 wlr_scene_node_for_each_buffer(node, send_frame_done_iterator, &when);
1260 }
1261}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 921b7d19..a68dc927 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809
2#include <ctype.h> 1#include <ctype.h>
3#include <limits.h> 2#include <limits.h>
4#include <stdbool.h> 3#include <stdbool.h>
@@ -48,14 +47,16 @@ struct sway_output *workspace_get_initial_output(const char *name) {
48 if (focus && focus->type == N_WORKSPACE) { 47 if (focus && focus->type == N_WORKSPACE) {
49 return focus->sway_workspace->output; 48 return focus->sway_workspace->output;
50 } else if (focus && focus->type == N_CONTAINER) { 49 } else if (focus && focus->type == N_CONTAINER) {
51 return focus->sway_container->workspace->output; 50 return focus->sway_container->pending.workspace->output;
52 } 51 }
53 // Fallback to the first output or noop output for headless 52 // Fallback to the first output or the headless output
54 return root->outputs->length ? root->outputs->items[0] : root->noop_output; 53 return root->outputs->length ? root->outputs->items[0] : root->fallback_output;
55} 54}
56 55
57struct sway_workspace *workspace_create(struct sway_output *output, 56struct sway_workspace *workspace_create(struct sway_output *output,
58 const char *name) { 57 const char *name) {
58 sway_assert(name, "NULL name given to workspace_create");
59
59 if (output == NULL) { 60 if (output == NULL) {
60 output = workspace_get_initial_output(name); 61 output = workspace_get_initial_output(name);
61 } 62 }
@@ -69,7 +70,19 @@ struct sway_workspace *workspace_create(struct sway_output *output,
69 return NULL; 70 return NULL;
70 } 71 }
71 node_init(&ws->node, N_WORKSPACE, ws); 72 node_init(&ws->node, N_WORKSPACE, ws);
72 ws->name = name ? strdup(name) : NULL; 73
74 bool failed = false;
75 ws->layers.tiling = alloc_scene_tree(root->staging, &failed);
76 ws->layers.fullscreen = alloc_scene_tree(root->staging, &failed);
77
78 if (failed) {
79 wlr_scene_node_destroy(&ws->layers.tiling->node);
80 wlr_scene_node_destroy(&ws->layers.fullscreen->node);
81 free(ws);
82 return NULL;
83 }
84
85 ws->name = strdup(name);
73 ws->prev_split_layout = L_NONE; 86 ws->prev_split_layout = L_NONE;
74 ws->layout = output_get_default_layout(output); 87 ws->layout = output_get_default_layout(output);
75 ws->floating = create_list(); 88 ws->floating = create_list();
@@ -114,7 +127,7 @@ struct sway_workspace *workspace_create(struct sway_output *output,
114 output_sort_workspaces(output); 127 output_sort_workspaces(output);
115 128
116 ipc_event_workspace(NULL, ws, "init"); 129 ipc_event_workspace(NULL, ws, "init");
117 wl_signal_emit(&root->events.new_node, &ws->node); 130 wl_signal_emit_mutable(&root->events.new_node, &ws->node);
118 131
119 return ws; 132 return ws;
120} 133}
@@ -129,6 +142,11 @@ void workspace_destroy(struct sway_workspace *workspace) {
129 return; 142 return;
130 } 143 }
131 144
145 scene_node_disown_children(workspace->layers.tiling);
146 scene_node_disown_children(workspace->layers.fullscreen);
147 wlr_scene_node_destroy(&workspace->layers.tiling->node);
148 wlr_scene_node_destroy(&workspace->layers.fullscreen->node);
149
132 free(workspace->name); 150 free(workspace->name);
133 free(workspace->representation); 151 free(workspace->representation);
134 list_free_items_and_destroy(workspace->output_priority); 152 list_free_items_and_destroy(workspace->output_priority);
@@ -142,7 +160,7 @@ void workspace_destroy(struct sway_workspace *workspace) {
142void workspace_begin_destroy(struct sway_workspace *workspace) { 160void workspace_begin_destroy(struct sway_workspace *workspace) {
143 sway_log(SWAY_DEBUG, "Destroying workspace '%s'", workspace->name); 161 sway_log(SWAY_DEBUG, "Destroying workspace '%s'", workspace->name);
144 ipc_event_workspace(NULL, workspace, "empty"); // intentional 162 ipc_event_workspace(NULL, workspace, "empty"); // intentional
145 wl_signal_emit(&workspace->node.events.destroy, &workspace->node); 163 wl_signal_emit_mutable(&workspace->node.events.destroy, &workspace->node);
146 164
147 if (workspace->output) { 165 if (workspace->output) {
148 workspace_detach(workspace); 166 workspace_detach(workspace);
@@ -174,22 +192,16 @@ void workspace_consider_destroy(struct sway_workspace *ws) {
174static bool workspace_valid_on_output(const char *output_name, 192static bool workspace_valid_on_output(const char *output_name,
175 const char *ws_name) { 193 const char *ws_name) {
176 struct workspace_config *wsc = workspace_find_config(ws_name); 194 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); 195 struct sway_output *output = output_by_name_or_id(output_name);
179 if (!output) { 196 if (!output) {
180 return false; 197 return false;
181 } 198 }
182 output_name = output->wlr_output->name;
183 output_get_identifier(identifier, sizeof(identifier), output);
184
185 if (!wsc) { 199 if (!wsc) {
186 return true; 200 return true;
187 } 201 }
188 202
189 for (int i = 0; i < wsc->outputs->length; i++) { 203 for (int i = 0; i < wsc->outputs->length; i++) {
190 if (strcmp(wsc->outputs->items[i], "*") == 0 || 204 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; 205 return true;
194 } 206 }
195 } 207 }
@@ -222,10 +234,8 @@ static void workspace_name_from_binding(const struct sway_binding * binding,
222 // not a command about workspaces 234 // not a command about workspaces
223 if (strcmp(_target, "next") == 0 || 235 if (strcmp(_target, "next") == 0 ||
224 strcmp(_target, "prev") == 0 || 236 strcmp(_target, "prev") == 0 ||
225 strncmp(_target, "next_on_output", 237 strcmp(_target, "next_on_output") == 0 ||
226 strlen("next_on_output")) == 0 || 238 strcmp(_target, "prev_on_output") == 0 ||
227 strncmp(_target, "prev_on_output",
228 strlen("next_on_output")) == 0 ||
229 strcmp(_target, "number") == 0 || 239 strcmp(_target, "number") == 0 ||
230 strcmp(_target, "back_and_forth") == 0 || 240 strcmp(_target, "back_and_forth") == 0 ||
231 strcmp(_target, "current") == 0) { 241 strcmp(_target, "current") == 0) {
@@ -286,13 +296,10 @@ char *workspace_next_name(const char *output_name) {
286 // assignments primarily, falling back to bindings and numbers. 296 // assignments primarily, falling back to bindings and numbers.
287 struct sway_mode *mode = config->current_mode; 297 struct sway_mode *mode = config->current_mode;
288 298
289 char identifier[128];
290 struct sway_output *output = output_by_name_or_id(output_name); 299 struct sway_output *output = output_by_name_or_id(output_name);
291 if (!output) { 300 if (!output) {
292 return NULL; 301 return NULL;
293 } 302 }
294 output_name = output->wlr_output->name;
295 output_get_identifier(identifier, sizeof(identifier), output);
296 303
297 int order = INT_MAX; 304 int order = INT_MAX;
298 char *target = NULL; 305 char *target = NULL;
@@ -312,9 +319,7 @@ char *workspace_next_name(const char *output_name) {
312 } 319 }
313 bool found = false; 320 bool found = false;
314 for (int j = 0; j < wsc->outputs->length; ++j) { 321 for (int j = 0; j < wsc->outputs->length; ++j) {
315 if (strcmp(wsc->outputs->items[j], "*") == 0 || 322 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; 323 found = true;
319 free(target); 324 free(target);
320 target = strdup(wsc->workspace); 325 target = strdup(wsc->workspace);
@@ -363,11 +368,11 @@ struct sway_workspace *workspace_by_name(const char *name) {
363 if (current && strcmp(name, "prev") == 0) { 368 if (current && strcmp(name, "prev") == 0) {
364 return workspace_prev(current); 369 return workspace_prev(current);
365 } else if (current && strcmp(name, "prev_on_output") == 0) { 370 } else if (current && strcmp(name, "prev_on_output") == 0) {
366 return workspace_output_prev(current, false); 371 return workspace_output_prev(current);
367 } else if (current && strcmp(name, "next") == 0) { 372 } else if (current && strcmp(name, "next") == 0) {
368 return workspace_next(current); 373 return workspace_next(current);
369 } else if (current && strcmp(name, "next_on_output") == 0) { 374 } else if (current && strcmp(name, "next_on_output") == 0) {
370 return workspace_output_next(current, false); 375 return workspace_output_next(current);
371 } else if (strcmp(name, "current") == 0) { 376 } else if (strcmp(name, "current") == 0) {
372 return current; 377 return current;
373 } else if (strcasecmp(name, "back_and_forth") == 0) { 378 } else if (strcasecmp(name, "back_and_forth") == 0) {
@@ -530,7 +535,7 @@ struct sway_workspace *workspace_next(struct sway_workspace *workspace) {
530 * otherwise the next one is returned. 535 * otherwise the next one is returned.
531 */ 536 */
532static struct sway_workspace *workspace_output_prev_next_impl( 537static struct sway_workspace *workspace_output_prev_next_impl(
533 struct sway_output *output, int dir, bool create) { 538 struct sway_output *output, int dir) {
534 struct sway_seat *seat = input_manager_current_seat(); 539 struct sway_seat *seat = input_manager_current_seat();
535 struct sway_workspace *workspace = seat_get_focused_workspace(seat); 540 struct sway_workspace *workspace = seat_get_focused_workspace(seat);
536 if (!workspace) { 541 if (!workspace) {
@@ -540,46 +545,43 @@ static struct sway_workspace *workspace_output_prev_next_impl(
540 } 545 }
541 546
542 int index = list_find(output->workspaces, workspace); 547 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); 548 size_t new_index = wrap(index + dir, output->workspaces->length);
551 return output->workspaces->items[new_index]; 549 return output->workspaces->items[new_index];
552} 550}
553 551
554struct sway_workspace *workspace_output_next( 552
555 struct sway_workspace *current, bool create) { 553struct sway_workspace *workspace_output_next(struct sway_workspace *current) {
556 return workspace_output_prev_next_impl(current->output, 1, create); 554 return workspace_output_prev_next_impl(current->output, 1);
557} 555}
558 556
559struct sway_workspace *workspace_output_prev( 557struct sway_workspace *workspace_output_prev(struct sway_workspace *current) {
560 struct sway_workspace *current, bool create) { 558 return workspace_output_prev_next_impl(current->output, -1);
561 return workspace_output_prev_next_impl(current->output, -1, create);
562} 559}
563 560
564bool workspace_switch(struct sway_workspace *workspace, 561struct sway_workspace *workspace_auto_back_and_forth(
565 bool no_auto_back_and_forth) { 562 struct sway_workspace *workspace) {
566 struct sway_seat *seat = input_manager_current_seat(); 563 struct sway_seat *seat = input_manager_current_seat();
567 struct sway_workspace *active_ws = NULL; 564 struct sway_workspace *active_ws = NULL;
568 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node); 565 struct sway_node *focus = seat_get_focus_inactive(seat, &root->node);
569 if (focus && focus->type == N_WORKSPACE) { 566 if (focus && focus->type == N_WORKSPACE) {
570 active_ws = focus->sway_workspace; 567 active_ws = focus->sway_workspace;
571 } else if (focus && focus->type == N_CONTAINER) { 568 } else if (focus && focus->type == N_CONTAINER) {
572 active_ws = focus->sway_container->workspace; 569 active_ws = focus->sway_container->pending.workspace;
573 } 570 }
574 571
575 if (!no_auto_back_and_forth && config->auto_back_and_forth && active_ws 572 if (config->auto_back_and_forth && active_ws && active_ws == workspace &&
576 && active_ws == workspace && seat->prev_workspace_name) { 573 seat->prev_workspace_name) {
577 struct sway_workspace *new_ws = 574 struct sway_workspace *new_ws =
578 workspace_by_name(seat->prev_workspace_name); 575 workspace_by_name(seat->prev_workspace_name);
579 workspace = new_ws ? 576 workspace = new_ws ?
580 new_ws : 577 new_ws :
581 workspace_create(NULL, seat->prev_workspace_name); 578 workspace_create(NULL, seat->prev_workspace_name);
582 } 579 }
580 return workspace;
581}
582
583bool workspace_switch(struct sway_workspace *workspace) {
584 struct sway_seat *seat = input_manager_current_seat();
583 585
584 sway_log(SWAY_DEBUG, "Switching to workspace %p:%s", 586 sway_log(SWAY_DEBUG, "Switching to workspace %p:%s",
585 workspace, workspace->name); 587 workspace, workspace->name);
@@ -657,15 +659,9 @@ void workspace_output_add_priority(struct sway_workspace *workspace,
657 659
658struct sway_output *workspace_output_get_highest_available( 660struct sway_output *workspace_output_get_highest_available(
659 struct sway_workspace *ws, struct sway_output *exclude) { 661 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++) { 662 for (int i = 0; i < ws->output_priority->length; i++) {
666 char *name = ws->output_priority->items[i]; 663 const char *name = ws->output_priority->items[i];
667 if (exclude && (strcmp(name, exclude->wlr_output->name) == 0 664 if (exclude && output_match_name_or_id(exclude, name)) {
668 || strcmp(name, exclude_id) == 0)) {
669 continue; 665 continue;
670 } 666 }
671 667
@@ -689,7 +685,6 @@ void workspace_detect_urgent(struct sway_workspace *workspace) {
689 if (workspace->urgent != new_urgent) { 685 if (workspace->urgent != new_urgent) {
690 workspace->urgent = new_urgent; 686 workspace->urgent = new_urgent;
691 ipc_event_workspace(NULL, workspace, "urgent"); 687 ipc_event_workspace(NULL, workspace, "urgent");
692 output_damage_whole(workspace->output);
693 } 688 }
694} 689}
695 690
@@ -736,13 +731,13 @@ struct sway_container *workspace_find_container(struct sway_workspace *ws,
736} 731}
737 732
738static void set_workspace(struct sway_container *container, void *data) { 733static void set_workspace(struct sway_container *container, void *data) {
739 container->workspace = container->parent->workspace; 734 container->pending.workspace = container->pending.parent->pending.workspace;
740} 735}
741 736
742static void workspace_attach_tiling(struct sway_workspace *ws, 737static void workspace_attach_tiling(struct sway_workspace *ws,
743 struct sway_container *con) { 738 struct sway_container *con) {
744 list_add(ws->tiling, con); 739 list_add(ws->tiling, con);
745 con->workspace = ws; 740 con->pending.workspace = ws;
746 container_for_each_child(con, set_workspace, NULL); 741 container_for_each_child(con, set_workspace, NULL);
747 container_handle_fullscreen_reparent(con); 742 container_handle_fullscreen_reparent(con);
748 workspace_update_representation(ws); 743 workspace_update_representation(ws);
@@ -753,7 +748,7 @@ static void workspace_attach_tiling(struct sway_workspace *ws,
753struct sway_container *workspace_wrap_children(struct sway_workspace *ws) { 748struct sway_container *workspace_wrap_children(struct sway_workspace *ws) {
754 struct sway_container *fs = ws->fullscreen; 749 struct sway_container *fs = ws->fullscreen;
755 struct sway_container *middle = container_create(NULL); 750 struct sway_container *middle = container_create(NULL);
756 middle->layout = ws->layout; 751 middle->pending.layout = ws->layout;
757 while (ws->tiling->length) { 752 while (ws->tiling->length) {
758 struct sway_container *child = ws->tiling->items[0]; 753 struct sway_container *child = ws->tiling->items[0];
759 container_detach(child); 754 container_detach(child);
@@ -771,9 +766,9 @@ void workspace_unwrap_children(struct sway_workspace *ws,
771 return; 766 return;
772 } 767 }
773 768
774 ws->layout = wrap->layout; 769 ws->layout = wrap->pending.layout;
775 while (wrap->children->length) { 770 while (wrap->pending.children->length) {
776 struct sway_container *child = wrap->children->items[0]; 771 struct sway_container *child = wrap->pending.children->items[0];
777 container_detach(child); 772 container_detach(child);
778 workspace_add_tiling(ws, child); 773 workspace_add_tiling(ws, child);
779 } 774 }
@@ -793,14 +788,18 @@ void workspace_detach(struct sway_workspace *workspace) {
793 788
794struct sway_container *workspace_add_tiling(struct sway_workspace *workspace, 789struct sway_container *workspace_add_tiling(struct sway_workspace *workspace,
795 struct sway_container *con) { 790 struct sway_container *con) {
796 if (con->workspace) { 791 if (con->pending.workspace) {
792 struct sway_container *old_parent = con->pending.parent;
797 container_detach(con); 793 container_detach(con);
794 if (old_parent) {
795 container_reap_empty(old_parent);
796 }
798 } 797 }
799 if (config->default_layout != L_NONE) { 798 if (config->default_layout != L_NONE) {
800 con = container_split(con, config->default_layout); 799 con = container_split(con, config->default_layout);
801 } 800 }
802 list_add(workspace->tiling, con); 801 list_add(workspace->tiling, con);
803 con->workspace = workspace; 802 con->pending.workspace = workspace;
804 container_for_each_child(con, set_workspace, NULL); 803 container_for_each_child(con, set_workspace, NULL);
805 container_handle_fullscreen_reparent(con); 804 container_handle_fullscreen_reparent(con);
806 workspace_update_representation(workspace); 805 workspace_update_representation(workspace);
@@ -811,11 +810,11 @@ struct sway_container *workspace_add_tiling(struct sway_workspace *workspace,
811 810
812void workspace_add_floating(struct sway_workspace *workspace, 811void workspace_add_floating(struct sway_workspace *workspace,
813 struct sway_container *con) { 812 struct sway_container *con) {
814 if (con->workspace) { 813 if (con->pending.workspace) {
815 container_detach(con); 814 container_detach(con);
816 } 815 }
817 list_add(workspace->floating, con); 816 list_add(workspace->floating, con);
818 con->workspace = workspace; 817 con->pending.workspace = workspace;
819 container_for_each_child(con, set_workspace, NULL); 818 container_for_each_child(con, set_workspace, NULL);
820 container_handle_fullscreen_reparent(con); 819 container_handle_fullscreen_reparent(con);
821 node_set_dirty(&workspace->node); 820 node_set_dirty(&workspace->node);
@@ -825,7 +824,7 @@ void workspace_add_floating(struct sway_workspace *workspace,
825void workspace_insert_tiling_direct(struct sway_workspace *workspace, 824void workspace_insert_tiling_direct(struct sway_workspace *workspace,
826 struct sway_container *con, int index) { 825 struct sway_container *con, int index) {
827 list_insert(workspace->tiling, index, con); 826 list_insert(workspace->tiling, index, con);
828 con->workspace = workspace; 827 con->pending.workspace = workspace;
829 container_for_each_child(con, set_workspace, NULL); 828 container_for_each_child(con, set_workspace, NULL);
830 container_handle_fullscreen_reparent(con); 829 container_handle_fullscreen_reparent(con);
831 workspace_update_representation(workspace); 830 workspace_update_representation(workspace);
@@ -835,7 +834,7 @@ void workspace_insert_tiling_direct(struct sway_workspace *workspace,
835 834
836struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace, 835struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace,
837 struct sway_container *con, int index) { 836 struct sway_container *con, int index) {
838 if (con->workspace) { 837 if (con->pending.workspace) {
839 container_detach(con); 838 container_detach(con);
840 } 839 }
841 if (config->default_layout != L_NONE) { 840 if (config->default_layout != L_NONE) {
@@ -845,24 +844,36 @@ struct sway_container *workspace_insert_tiling(struct sway_workspace *workspace,
845 return con; 844 return con;
846} 845}
847 846
847bool workspace_has_single_visible_container(struct sway_workspace *ws) {
848 struct sway_seat *seat = input_manager_get_default_seat();
849 struct sway_container *focus =
850 seat_get_focus_inactive_tiling(seat, ws);
851 if (focus && !focus->view) {
852 focus = seat_get_focus_inactive_view(seat, &focus->node);
853 }
854 return (focus && focus->view && view_ancestor_is_only_visible(focus->view));
855}
856
848void workspace_add_gaps(struct sway_workspace *ws) { 857void workspace_add_gaps(struct sway_workspace *ws) {
849 if (config->smart_gaps) { 858 if (config->smart_gaps == SMART_GAPS_ON
850 struct sway_seat *seat = input_manager_get_default_seat(); 859 && workspace_has_single_visible_container(ws)) {
851 struct sway_container *focus = 860 ws->current_gaps.top = 0;
852 seat_get_focus_inactive_tiling(seat, ws); 861 ws->current_gaps.right = 0;
853 if (focus && !focus->view) { 862 ws->current_gaps.bottom = 0;
854 focus = seat_get_focus_inactive_view(seat, &focus->node); 863 ws->current_gaps.left = 0;
855 } 864 return;
856 if (focus && focus->view && view_ancestor_is_only_visible(focus->view)) { 865 }
857 ws->current_gaps.top = 0; 866
858 ws->current_gaps.right = 0; 867 if (config->smart_gaps == SMART_GAPS_INVERSE_OUTER
859 ws->current_gaps.bottom = 0; 868 && !workspace_has_single_visible_container(ws)) {
860 ws->current_gaps.left = 0; 869 ws->current_gaps.top = 0;
861 return; 870 ws->current_gaps.right = 0;
862 } 871 ws->current_gaps.bottom = 0;
872 ws->current_gaps.left = 0;
873 } else {
874 ws->current_gaps = ws->gaps_outer;
863 } 875 }
864 876
865 ws->current_gaps = ws->gaps_outer;
866 // Add inner gaps and make sure we don't turn out negative 877 // 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); 878 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); 879 ws->current_gaps.right = fmax(0, ws->current_gaps.right + ws->gaps_inner);
@@ -905,7 +916,7 @@ struct sway_container *workspace_split(struct sway_workspace *workspace,
905 enum sway_container_layout old_layout = workspace->layout; 916 enum sway_container_layout old_layout = workspace->layout;
906 struct sway_container *middle = workspace_wrap_children(workspace); 917 struct sway_container *middle = workspace_wrap_children(workspace);
907 workspace->layout = layout; 918 workspace->layout = layout;
908 middle->layout = old_layout; 919 middle->pending.layout = old_layout;
909 920
910 struct sway_seat *seat; 921 struct sway_seat *seat;
911 wl_list_for_each(seat, &server.input->seats, link) { 922 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..b7c80dd4
--- /dev/null
+++ b/sway/xdg_activation_v1.c
@@ -0,0 +1,65 @@
1#include <wlr/types/wlr_xdg_activation_v1.h>
2#include <wlr/types/wlr_xdg_shell.h>
3#include "sway/desktop/launcher.h"
4#include "sway/tree/view.h"
5#include "sway/tree/workspace.h"
6
7void xdg_activation_v1_handle_request_activate(struct wl_listener *listener,
8 void *data) {
9 const struct wlr_xdg_activation_v1_request_activate_event *event = data;
10
11 struct wlr_xdg_surface *xdg_surface =
12 wlr_xdg_surface_try_from_wlr_surface(event->surface);
13 if (xdg_surface == NULL) {
14 return;
15 }
16 struct sway_view *view = xdg_surface->data;
17 if (view == NULL) {
18 return;
19 }
20
21 struct launcher_ctx *ctx = event->token->data;
22 if (ctx == NULL) {
23 return;
24 }
25
26 if (!xdg_surface->surface->mapped) {
27 // This is a startup notification. If we are tracking it, the data
28 // field is a launcher_ctx.
29 if (ctx->activated) {
30 // This ctx has already been activated and cannot be used again
31 // for a startup notification. It will be destroyed
32 return;
33 } else {
34 ctx->activated = true;
35 view_assign_ctx(view, ctx);
36 }
37 return;
38 }
39
40 // This is an activation request. If this context is internal we have ctx->seat.
41 struct sway_seat *seat = ctx->seat;
42 if (!seat) {
43 // Otherwise, use the seat indicated by the launcher client in set_serial
44 seat = ctx->token->seat ? ctx->token->seat->data : NULL;
45 }
46
47 if (seat && ctx->had_focused_surface) {
48 view_request_activate(view, seat);
49 } else {
50 // The token is valid, but cannot be used to activate a window
51 view_request_urgent(view);
52 }
53}
54
55void xdg_activation_v1_handle_new_token(struct wl_listener *listener, void *data) {
56 struct wlr_xdg_activation_token_v1 *token = data;
57 struct sway_seat *seat = token->seat ? token->seat->data :
58 input_manager_current_seat();
59
60 struct sway_workspace *ws = seat_get_focused_workspace(seat);
61 if (ws) {
62 launcher_ctx_create(token, &ws->node);
63 return;
64 }
65}
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..5b1213a8 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <errno.h> 2#include <errno.h>
4#include <fcntl.h> 3#include <fcntl.h>
@@ -51,10 +50,6 @@ static void swaybar_output_free(struct swaybar_output *output) {
51 if (output->surface != NULL) { 50 if (output->surface != NULL) {
52 wl_surface_destroy(output->surface); 51 wl_surface_destroy(output->surface);
53 } 52 }
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); 53 wl_output_destroy(output->output);
59 destroy_buffer(&output->buffers[0]); 54 destroy_buffer(&output->buffers[0]);
60 destroy_buffer(&output->buffers[1]); 55 destroy_buffer(&output->buffers[1]);
@@ -90,7 +85,7 @@ static void layer_surface_closed(void *_output,
90 swaybar_output_free(output); 85 swaybar_output_free(output);
91} 86}
92 87
93struct zwlr_layer_surface_v1_listener layer_surface_listener = { 88static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
94 .configure = layer_surface_configure, 89 .configure = layer_surface_configure,
95 .closed = layer_surface_closed, 90 .closed = layer_surface_closed,
96}; 91};
@@ -114,10 +109,9 @@ static void add_layer_surface(struct swaybar_output *output) {
114 109
115 if (overlay) { 110 if (overlay) {
116 // Empty input region 111 // Empty input region
117 output->input_region = wl_compositor_create_region(bar->compositor); 112 struct wl_region *region = wl_compositor_create_region(bar->compositor);
118 assert(output->input_region); 113 wl_surface_set_input_region(output->surface, region);
119 114 wl_region_destroy(region);
120 wl_surface_set_input_region(output->surface, output->input_region);
121 } 115 }
122 116
123 zwlr_layer_surface_v1_set_anchor(output->layer_surface, config->position); 117 zwlr_layer_surface_v1_set_anchor(output->layer_surface, config->position);
@@ -172,7 +166,7 @@ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer) {
172 if (bar->status) { 166 if (bar->status) {
173 sway_log(SWAY_DEBUG, "Sending %s signal to status command", 167 sway_log(SWAY_DEBUG, "Sending %s signal to status command",
174 visible ? "cont" : "stop"); 168 visible ? "cont" : "stop");
175 kill(bar->status->pid, visible ? 169 kill(-bar->status->pid, visible ?
176 bar->status->cont_signal : bar->status->stop_signal); 170 bar->status->cont_signal : bar->status->stop_signal);
177 } 171 }
178 } 172 }
@@ -230,7 +224,7 @@ static void output_scale(void *data, struct wl_output *wl_output,
230 } 224 }
231} 225}
232 226
233struct wl_output_listener output_listener = { 227static const struct wl_output_listener output_listener = {
234 .geometry = output_geometry, 228 .geometry = output_geometry,
235 .mode = output_mode, 229 .mode = output_mode,
236 .done = output_done, 230 .done = output_done,
@@ -307,7 +301,7 @@ static void xdg_output_handle_description(void *data,
307 } 301 }
308} 302}
309 303
310struct zxdg_output_v1_listener xdg_output_listener = { 304static const struct zxdg_output_v1_listener xdg_output_listener = {
311 .logical_position = xdg_output_handle_logical_position, 305 .logical_position = xdg_output_handle_logical_position,
312 .logical_size = xdg_output_handle_logical_size, 306 .logical_size = xdg_output_handle_logical_size,
313 .done = xdg_output_handle_done, 307 .done = xdg_output_handle_done,
@@ -367,6 +361,9 @@ static void handle_global(void *data, struct wl_registry *registry,
367 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { 361 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
368 bar->xdg_output_manager = wl_registry_bind(registry, name, 362 bar->xdg_output_manager = wl_registry_bind(registry, name,
369 &zxdg_output_manager_v1_interface, 2); 363 &zxdg_output_manager_v1_interface, 2);
364 } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
365 bar->cursor_shape_manager = wl_registry_bind(registry, name,
366 &wp_cursor_shape_manager_v1_interface, 1);
370 } 367 }
371} 368}
372 369
@@ -430,15 +427,17 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
430 // Second roundtrip for xdg-output 427 // Second roundtrip for xdg-output
431 wl_display_roundtrip(bar->display); 428 wl_display_roundtrip(bar->display);
432 429
433 struct swaybar_seat *seat; 430 if (!bar->cursor_shape_manager) {
434 wl_list_for_each(seat, &bar->seats, link) { 431 struct swaybar_seat *seat;
435 struct swaybar_pointer *pointer = &seat->pointer; 432 wl_list_for_each(seat, &bar->seats, link) {
436 if (!pointer) { 433 struct swaybar_pointer *pointer = &seat->pointer;
437 continue; 434 if (!pointer) {
435 continue;
436 }
437 pointer->cursor_surface =
438 wl_compositor_create_surface(bar->compositor);
439 assert(pointer->cursor_surface);
438 } 440 }
439 pointer->cursor_surface =
440 wl_compositor_create_surface(bar->compositor);
441 assert(pointer->cursor_surface);
442 } 441 }
443 442
444 if (bar->config->status_command) { 443 if (bar->config->status_command) {
@@ -461,13 +460,28 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
461 460
462static void display_in(int fd, short mask, void *data) { 461static void display_in(int fd, short mask, void *data) {
463 struct swaybar *bar = data; 462 struct swaybar *bar = data;
463 if (mask & (POLLHUP | POLLERR)) {
464 if (mask & POLLERR) {
465 sway_log(SWAY_ERROR, "Wayland display poll error");
466 }
467 bar->running = false;
468 return;
469 }
464 if (wl_display_dispatch(bar->display) == -1) { 470 if (wl_display_dispatch(bar->display) == -1) {
471 sway_log(SWAY_ERROR, "wl_display_dispatch failed");
465 bar->running = false; 472 bar->running = false;
466 } 473 }
467} 474}
468 475
469static void ipc_in(int fd, short mask, void *data) { 476static void ipc_in(int fd, short mask, void *data) {
470 struct swaybar *bar = data; 477 struct swaybar *bar = data;
478 if (mask & (POLLHUP | POLLERR)) {
479 if (mask & POLLERR) {
480 sway_log(SWAY_ERROR, "IPC poll error");
481 }
482 bar->running = false;
483 return;
484 }
471 if (handle_ipc_readable(bar)) { 485 if (handle_ipc_readable(bar)) {
472 set_bar_dirty(bar); 486 set_bar_dirty(bar);
473 } 487 }
diff --git a/swaybar/config.c b/swaybar/config.c
index abedaec0..55bfcb72 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <string.h> 2#include <string.h>
4#include "swaybar/config.h" 3#include "swaybar/config.h"
@@ -26,7 +25,7 @@ struct swaybar_config *init_config(void) {
26 config->status_command = NULL; 25 config->status_command = NULL;
27 config->pango_markup = false; 26 config->pango_markup = false;
28 config->position = parse_position("bottom"); 27 config->position = parse_position("bottom");
29 config->font = strdup("monospace 10"); 28 config->font_description = pango_font_description_from_string("monospace 10");
30 config->mode = strdup("dock"); 29 config->mode = strdup("dock");
31 config->hidden_state = strdup("hide"); 30 config->hidden_state = strdup("hide");
32 config->sep_symbol = NULL; 31 config->sep_symbol = NULL;
@@ -105,7 +104,7 @@ void free_tray_binding(struct tray_binding *binding) {
105 104
106void free_config(struct swaybar_config *config) { 105void free_config(struct swaybar_config *config) {
107 free(config->status_command); 106 free(config->status_command);
108 free(config->font); 107 pango_font_description_free(config->font_description);
109 free(config->mode); 108 free(config->mode);
110 free(config->hidden_state); 109 free(config->hidden_state);
111 free(config->sep_symbol); 110 free(config->sep_symbol);
diff --git a/swaybar/i3bar.c b/swaybar/i3bar.c
index 4bcd5843..62c22d43 100644
--- a/swaybar/i3bar.c
+++ b/swaybar/i3bar.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <json.h> 1#include <json.h>
3#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
4#include <ctype.h> 3#include <ctype.h>
@@ -28,6 +27,19 @@ void i3bar_block_unref(struct i3bar_block *block) {
28 } 27 }
29} 28}
30 29
30static bool i3bar_parse_json_color(json_object *json, uint32_t *color) {
31 if (!json) {
32 return false;
33 }
34
35 const char *hexstring = json_object_get_string(json);
36 bool color_set = parse_color(hexstring, color);
37 if (!color_set) {
38 sway_log(SWAY_ERROR, "Ignoring invalid block hexadecimal color string: %s", hexstring);
39 }
40 return color_set;
41}
42
31static void i3bar_parse_json(struct status_line *status, 43static void i3bar_parse_json(struct status_line *status,
32 struct json_object *json_array) { 44 struct json_object *json_array) {
33 struct i3bar_block *block, *tmp; 45 struct i3bar_block *block, *tmp;
@@ -68,13 +80,7 @@ static void i3bar_parse_json(struct status_line *status,
68 strdup(json_object_get_string(full_text)) : NULL; 80 strdup(json_object_get_string(full_text)) : NULL;
69 block->short_text = short_text ? 81 block->short_text = short_text ?
70 strdup(json_object_get_string(short_text)) : NULL; 82 strdup(json_object_get_string(short_text)) : NULL;
71 if (color) { 83 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) { 84 if (min_width) {
79 json_type type = json_object_get_type(min_width); 85 json_type type = json_object_get_type(min_width);
80 if (type == json_type_int) { 86 if (type == json_type_int) {
@@ -100,14 +106,8 @@ static void i3bar_parse_json(struct status_line *status,
100 block->separator_block_width = separator_block_width ? 106 block->separator_block_width = separator_block_width ?
101 json_object_get_int(separator_block_width) : 9; 107 json_object_get_int(separator_block_width) : 9;
102 // Airblader features 108 // Airblader features
103 const char *hex = background ? json_object_get_string(background) : NULL; 109 i3bar_parse_json_color(background, &block->background);
104 if (hex && !parse_color(hex, &block->background)) { 110 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; 111 block->border_top = border_top ? json_object_get_int(border_top) : 1;
112 block->border_bottom = border_bottom ? 112 block->border_bottom = border_bottom ?
113 json_object_get_int(border_bottom) : 1; 113 json_object_get_int(border_bottom) : 1;
@@ -268,11 +268,16 @@ bool i3bar_handle_readable(struct status_line *status) {
268 268
269enum hotspot_event_handling i3bar_block_send_click(struct status_line *status, 269enum hotspot_event_handling i3bar_block_send_click(struct status_line *status,
270 struct i3bar_block *block, double x, double y, double rx, double ry, 270 struct i3bar_block *block, double x, double y, double rx, double ry,
271 double w, double h, int scale, uint32_t button) { 271 double w, double h, int scale, uint32_t button, bool released) {
272 sway_log(SWAY_DEBUG, "block %s clicked", block->name); 272 sway_log(SWAY_DEBUG, "block %s clicked", block->name);
273 if (!block->name || !status->click_events) { 273 if (!block->name || !status->click_events) {
274 return HOTSPOT_PROCESS; 274 return HOTSPOT_PROCESS;
275 } 275 }
276 if (released) {
277 // Since we handle the pressed event, also handle the released event
278 // to block it from falling through to a binding in the bar
279 return HOTSPOT_IGNORE;
280 }
276 281
277 struct json_object *event_json = json_object_new_object(); 282 struct json_object *event_json = json_object_new_object();
278 json_object_object_add(event_json, "name", 283 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..03500bdf 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809
2#include <limits.h> 1#include <limits.h>
3#include <poll.h> 2#include <poll.h>
4#include <stdio.h> 3#include <stdio.h>
@@ -147,8 +146,10 @@ static bool ipc_parse_config(
147 146
148 json_object *font = json_object_object_get(bar_config, "font"); 147 json_object *font = json_object_object_get(bar_config, "font");
149 if (font) { 148 if (font) {
150 free(config->font); 149 pango_font_description_free(config->font_description);
151 config->font = parse_font(json_object_get_string(font)); 150 char *font_value = parse_font(json_object_get_string(font));
151 config->font_description = pango_font_description_from_string(font_value);
152 free(font_value);
152 } 153 }
153 154
154 json_object *gaps = json_object_object_get(bar_config, "gaps"); 155 json_object *gaps = json_object_object_get(bar_config, "gaps");
@@ -424,12 +425,9 @@ bool ipc_initialize(struct swaybar *bar) {
424 } 425 }
425 free(res); 426 free(res);
426 427
427 struct swaybar_config *config = bar->config; 428 char *subscribe =
428 char subscribe[128]; // suitably large buffer 429 "[ \"barconfig_update\", \"bar_state_update\", \"mode\", \"workspace\" ]";
429 len = snprintf(subscribe, 128, 430 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, 431 free(ipc_single_command(bar->ipc_event_socketfd,
434 IPC_SUBSCRIBE, subscribe, &len)); 432 IPC_SUBSCRIBE, subscribe, &len));
435 return true; 433 return true;
@@ -485,8 +483,7 @@ static bool handle_barconfig_update(struct swaybar *bar, const char *payload,
485 destroy_layer_surface(output); 483 destroy_layer_surface(output);
486 wl_list_remove(&output->link); 484 wl_list_remove(&output->link);
487 wl_list_insert(&bar->unused_outputs, &output->link); 485 wl_list_insert(&bar->unused_outputs, &output->link);
488 } else if (!oldcfg->font || !newcfg->font || 486 } 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 487 output->height = 0; // force update height
491 } 488 }
492 } 489 }
@@ -547,9 +544,23 @@ bool handle_ipc_readable(struct swaybar *bar) {
547 return false; 544 return false;
548 } 545 }
549 546
550 json_object *result = json_tokener_parse(resp->payload); 547 // The default depth of 32 is too small to represent some nested layouts, but
551 if (!result) { 548 // 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"); 549 // all the memory for its stack.
550 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
551 if (!tok) {
552 sway_log_errno(SWAY_ERROR, "failed to create tokener");
553 free_ipc_response(resp);
554 return false;
555 }
556
557 json_object *result = json_tokener_parse_ex(tok, resp->payload, -1);
558 enum json_tokener_error err = json_tokener_get_error(tok);
559 json_tokener_free(tok);
560
561 if (err != json_tokener_success) {
562 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
563 json_tokener_error_desc(err));
553 free_ipc_response(resp); 564 free_ipc_response(resp);
554 return false; 565 return false;
555 } 566 }
diff --git a/swaybar/main.c b/swaybar/main.c
index 5c36d66b..3dc67233 100644
--- a/swaybar/main.c
+++ b/swaybar/main.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdio.h> 1#include <stdio.h>
3#include <stdlib.h> 2#include <stdlib.h>
4#include <string.h> 3#include <string.h>
@@ -18,7 +17,7 @@ int main(int argc, char **argv) {
18 char *socket_path = NULL; 17 char *socket_path = NULL;
19 bool debug = false; 18 bool debug = false;
20 19
21 static struct option long_options[] = { 20 static const struct option long_options[] = {
22 {"help", no_argument, NULL, 'h'}, 21 {"help", no_argument, NULL, 'h'},
23 {"version", no_argument, NULL, 'v'}, 22 {"version", no_argument, NULL, 'v'},
24 {"socket", required_argument, NULL, 's'}, 23 {"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..879a4e42 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -1,11 +1,10 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <linux/input-event-codes.h> 2#include <linux/input-event-codes.h>
4#include <limits.h> 3#include <limits.h>
5#include <stdlib.h> 4#include <stdlib.h>
6#include <stdint.h> 5#include <stdint.h>
7#include <string.h> 6#include <string.h>
8#include "cairo.h" 7#include "cairo_util.h"
9#include "pango.h" 8#include "pango.h"
10#include "pool-buffer.h" 9#include "pool-buffer.h"
11#include "swaybar/bar.h" 10#include "swaybar/bar.h"
@@ -14,6 +13,7 @@
14#include "swaybar/ipc.h" 13#include "swaybar/ipc.h"
15#include "swaybar/render.h" 14#include "swaybar/render.h"
16#include "swaybar/status_line.h" 15#include "swaybar/status_line.h"
16#include "log.h"
17#if HAVE_TRAY 17#if HAVE_TRAY
18#include "swaybar/tray/tray.h" 18#include "swaybar/tray/tray.h"
19#endif 19#endif
@@ -23,28 +23,51 @@ static const int WS_HORIZONTAL_PADDING = 5;
23static const double WS_VERTICAL_PADDING = 1.5; 23static const double WS_VERTICAL_PADDING = 1.5;
24static const double BORDER_WIDTH = 1; 24static const double BORDER_WIDTH = 1;
25 25
26static uint32_t render_status_line_error(cairo_t *cairo, 26struct render_context {
27 struct swaybar_output *output, double *x) { 27 cairo_t *cairo;
28 struct swaybar_output *output;
29 cairo_font_options_t *textaa_sharp;
30 cairo_font_options_t *textaa_safe;
31 uint32_t background_color;
32};
33
34static void choose_text_aa_mode(struct render_context *ctx, uint32_t fontcolor) {
35 uint32_t salpha = fontcolor & 0xFF;
36 uint32_t balpha = ctx->background_color & 0xFF;
37
38 // Subpixel antialiasing requires blend be done in cairo, not compositor
39 cairo_font_options_t *fo = salpha == balpha ?
40 ctx->textaa_sharp : ctx->textaa_safe;
41 cairo_set_font_options(ctx->cairo, fo);
42
43 // Color emojis, being semitransparent bitmaps, are leaky with 'SOURCE'
44 cairo_operator_t op = salpha == 0xFF ?
45 CAIRO_OPERATOR_OVER : CAIRO_OPERATOR_SOURCE;
46 cairo_set_operator(ctx->cairo, op);
47}
48
49static uint32_t render_status_line_error(struct render_context *ctx, double *x) {
50 struct swaybar_output *output = ctx->output;
28 const char *error = output->bar->status->text; 51 const char *error = output->bar->status->text;
29 if (!error) { 52 if (!error) {
30 return 0; 53 return 0;
31 } 54 }
32 55
33 uint32_t height = output->height * output->scale; 56 uint32_t height = output->height;
34 57
58 cairo_t *cairo = ctx->cairo;
35 cairo_set_source_u32(cairo, 0xFF0000FF); 59 cairo_set_source_u32(cairo, 0xFF0000FF);
36 60
37 int margin = 3 * output->scale; 61 int margin = 3;
38 double ws_vertical_padding = 62 double ws_vertical_padding = output->bar->config->status_padding;
39 output->bar->config->status_padding * output->scale;
40 63
41 char *font = output->bar->config->font; 64 PangoFontDescription *font = output->bar->config->font_description;
42 int text_width, text_height; 65 int text_width, text_height;
43 get_text_size(cairo, font, &text_width, &text_height, NULL, 66 get_text_size(cairo, font, &text_width, &text_height, NULL,
44 output->scale, false, "%s", error); 67 1, false, "%s", error);
45 68
46 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 69 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
47 uint32_t ideal_surface_height = ideal_height / output->scale; 70 uint32_t ideal_surface_height = ideal_height;
48 if (!output->bar->config->height && 71 if (!output->bar->config->height &&
49 output->height < ideal_surface_height) { 72 output->height < ideal_surface_height) {
50 return ideal_surface_height; 73 return ideal_surface_height;
@@ -53,42 +76,45 @@ static uint32_t render_status_line_error(cairo_t *cairo,
53 76
54 double text_y = height / 2.0 - text_height / 2.0; 77 double text_y = height / 2.0 - text_height / 2.0;
55 cairo_move_to(cairo, *x, (int)floor(text_y)); 78 cairo_move_to(cairo, *x, (int)floor(text_y));
56 pango_printf(cairo, font, output->scale, false, "%s", error); 79 choose_text_aa_mode(ctx, 0xFF0000FF);
80 render_text(cairo, font, 1, false, "%s", error);
57 *x -= margin; 81 *x -= margin;
58 return output->height; 82 return output->height;
59} 83}
60 84
61static uint32_t render_status_line_text(cairo_t *cairo, 85static uint32_t render_status_line_text(struct render_context *ctx, double *x) {
62 struct swaybar_output *output, double *x) { 86 struct swaybar_output *output = ctx->output;
63 const char *text = output->bar->status->text; 87 const char *text = output->bar->status->text;
64 if (!text) { 88 if (!text) {
65 return 0; 89 return 0;
66 } 90 }
67 91
92 cairo_t *cairo = ctx->cairo;
68 struct swaybar_config *config = output->bar->config; 93 struct swaybar_config *config = output->bar->config;
69 cairo_set_source_u32(cairo, output->focused ? 94 uint32_t fontcolor = output->focused ?
70 config->colors.focused_statusline : config->colors.statusline); 95 config->colors.focused_statusline : config->colors.statusline;
96 cairo_set_source_u32(cairo, fontcolor);
71 97
72 int text_width, text_height; 98 int text_width, text_height;
73 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 99 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
74 output->scale, config->pango_markup, "%s", text); 100 1, config->pango_markup, "%s", text);
75 101
76 double ws_vertical_padding = config->status_padding * output->scale; 102 double ws_vertical_padding = config->status_padding;
77 int margin = 3 * output->scale; 103 int margin = 3;
78 104
79 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 105 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
80 uint32_t ideal_surface_height = ideal_height / output->scale; 106 uint32_t ideal_surface_height = ideal_height;
81 if (!output->bar->config->height && 107 if (!output->bar->config->height &&
82 output->height < ideal_surface_height) { 108 output->height < ideal_surface_height) {
83 return ideal_surface_height; 109 return ideal_surface_height;
84 } 110 }
85 111
86 *x -= text_width + margin; 112 *x -= text_width + margin;
87 uint32_t height = output->height * output->scale; 113 uint32_t height = output->height;
88 double text_y = height / 2.0 - text_height / 2.0; 114 double text_y = height / 2.0 - text_height / 2.0;
89 cairo_move_to(cairo, *x, (int)floor(text_y)); 115 cairo_move_to(cairo, *x, (int)floor(text_y));
90 pango_printf(cairo, config->font, output->scale, 116 choose_text_aa_mode(ctx, fontcolor);
91 config->pango_markup, "%s", text); 117 render_text(cairo, config->font_description, 1, config->pango_markup, "%s", text);
92 *x -= margin; 118 *x -= margin;
93 return output->height; 119 return output->height;
94} 120}
@@ -96,6 +122,7 @@ static uint32_t render_status_line_text(cairo_t *cairo,
96static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, 122static void render_sharp_rectangle(cairo_t *cairo, uint32_t color,
97 double x, double y, double width, double height) { 123 double x, double y, double width, double height) {
98 cairo_save(cairo); 124 cairo_save(cairo);
125 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
99 cairo_set_source_u32(cairo, color); 126 cairo_set_source_u32(cairo, color);
100 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); 127 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
101 cairo_rectangle(cairo, x, y, width, height); 128 cairo_rectangle(cairo, x, y, width, height);
@@ -109,6 +136,7 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
109 render_sharp_rectangle(cairo, color, x, y, width, height); 136 render_sharp_rectangle(cairo, color, x, y, width, height);
110 } else { 137 } else {
111 cairo_save(cairo); 138 cairo_save(cairo);
139 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
112 cairo_set_source_u32(cairo, color); 140 cairo_set_source_u32(cairo, color);
113 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE); 141 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_NONE);
114 if (width == 1) { 142 if (width == 1) {
@@ -131,24 +159,23 @@ static void render_sharp_line(cairo_t *cairo, uint32_t color,
131 159
132static enum hotspot_event_handling block_hotspot_callback( 160static enum hotspot_event_handling block_hotspot_callback(
133 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 161 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
134 double x, double y, uint32_t button, void *data) { 162 double x, double y, uint32_t button, bool released, void *data) {
135 struct i3bar_block *block = data; 163 struct i3bar_block *block = data;
136 struct status_line *status = output->bar->status; 164 struct status_line *status = output->bar->status;
137 return i3bar_block_send_click(status, block, x, y, 165 return i3bar_block_send_click(status, block, x, y,
138 x - (double)hotspot->x / output->scale, 166 x - (double)hotspot->x,
139 y - (double)hotspot->y / output->scale, 167 y - (double)hotspot->y,
140 (double)hotspot->width / output->scale, 168 (double)hotspot->width,
141 (double)hotspot->height / output->scale, 169 (double)hotspot->height,
142 output->scale, button); 170 output->scale, button, released);
143} 171}
144 172
145static void i3bar_block_unref_callback(void *data) { 173static void i3bar_block_unref_callback(void *data) {
146 i3bar_block_unref(data); 174 i3bar_block_unref(data);
147} 175}
148 176
149static uint32_t render_status_block(cairo_t *cairo, 177static uint32_t render_status_block(struct render_context *ctx,
150 struct swaybar_output *output, struct i3bar_block *block, double *x, 178 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) { 179 if (!block->full_text || !*block->full_text) {
153 return 0; 180 return 0;
154 } 181 }
@@ -158,20 +185,21 @@ static uint32_t render_status_block(cairo_t *cairo,
158 text = block->short_text; 185 text = block->short_text;
159 } 186 }
160 187
188 cairo_t *cairo = ctx->cairo;
189 struct swaybar_output *output = ctx->output;
161 struct swaybar_config *config = output->bar->config; 190 struct swaybar_config *config = output->bar->config;
162
163 int text_width, text_height; 191 int text_width, text_height;
164 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 192 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
165 output->scale, block->markup, "%s", text); 193 block->markup, "%s", text);
166 194
167 int margin = 3 * output->scale; 195 int margin = 3;
168 double ws_vertical_padding = config->status_padding * output->scale; 196 double ws_vertical_padding = config->status_padding;
169 197
170 int width = text_width; 198 int width = text_width;
171 if (block->min_width_str) { 199 if (block->min_width_str) {
172 int w; 200 int w;
173 get_text_size(cairo, config->font, &w, NULL, NULL, 201 get_text_size(cairo, config->font_description, &w, NULL, NULL, 1, block->markup,
174 output->scale, block->markup, "%s", block->min_width_str); 202 "%s", block->min_width_str);
175 block->min_width = w; 203 block->min_width = w;
176 } 204 }
177 if (width < block->min_width) { 205 if (width < block->min_width) {
@@ -180,30 +208,30 @@ static uint32_t render_status_block(cairo_t *cairo,
180 208
181 double block_width = width; 209 double block_width = width;
182 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 210 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
183 uint32_t ideal_surface_height = ideal_height / output->scale; 211 uint32_t ideal_surface_height = ideal_height;
184 if (!output->bar->config->height && 212 if (!output->bar->config->height &&
185 output->height < ideal_surface_height) { 213 output->height < ideal_surface_height) {
186 return ideal_surface_height; 214 return ideal_surface_height;
187 } 215 }
188 216
189 *x -= width; 217 *x -= width;
190 if ((block->border || block->urgent) && block->border_left > 0) { 218 if ((block->border_set || block->urgent) && block->border_left > 0) {
191 *x -= (block->border_left * output->scale + margin); 219 *x -= (block->border_left + margin);
192 block_width += block->border_left * output->scale + margin; 220 block_width += block->border_left + margin;
193 } 221 }
194 if ((block->border || block->urgent) && block->border_right > 0) { 222 if ((block->border_set || block->urgent) && block->border_right > 0) {
195 *x -= (block->border_right * output->scale + margin); 223 *x -= (block->border_right + margin);
196 block_width += block->border_right * output->scale + margin; 224 block_width += block->border_right + margin;
197 } 225 }
198 226
199 int sep_width, sep_height; 227 int sep_width, sep_height;
200 int sep_block_width = block->separator_block_width; 228 int sep_block_width = block->separator_block_width;
201 if (!edge) { 229 if (!edge) {
202 if (config->sep_symbol) { 230 if (config->sep_symbol) {
203 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, 231 get_text_size(cairo, config->font_description, &sep_width, &sep_height, NULL,
204 output->scale, false, "%s", config->sep_symbol); 232 1, false, "%s", config->sep_symbol);
205 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; 233 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
206 uint32_t _ideal_surface_height = _ideal_height / output->scale; 234 uint32_t _ideal_surface_height = _ideal_height;
207 if (!output->bar->config->height && 235 if (!output->bar->config->height &&
208 output->height < _ideal_surface_height) { 236 output->height < _ideal_surface_height) {
209 return _ideal_surface_height; 237 return _ideal_surface_height;
@@ -214,10 +242,10 @@ static uint32_t render_status_block(cairo_t *cairo,
214 } 242 }
215 *x -= sep_block_width; 243 *x -= sep_block_width;
216 } else if (config->status_edge_padding) { 244 } else if (config->status_edge_padding) {
217 *x -= config->status_edge_padding * output->scale; 245 *x -= config->status_edge_padding;
218 } 246 }
219 247
220 uint32_t height = output->height * output->scale; 248 uint32_t height = output->height;
221 if (output->bar->status->click_events) { 249 if (output->bar->status->click_events) {
222 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 250 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
223 hotspot->x = *x; 251 hotspot->x = *x;
@@ -240,27 +268,30 @@ static uint32_t render_status_block(cairo_t *cairo,
240 if (bg_color) { 268 if (bg_color) {
241 render_sharp_rectangle(cairo, bg_color, x_pos, y_pos, 269 render_sharp_rectangle(cairo, bg_color, x_pos, y_pos,
242 block_width, render_height); 270 block_width, render_height);
271 ctx->background_color = bg_color;
243 } 272 }
244 273
245 uint32_t border_color = block->urgent 274 uint32_t border_color = block->urgent
246 ? config->colors.urgent_workspace.border : block->border; 275 ? config->colors.urgent_workspace.border : block->border;
247 if (border_color && block->border_top > 0) { 276 if (block->border_set || block->urgent) {
248 render_sharp_line(cairo, border_color, x_pos, y_pos, 277 if (block->border_top > 0) {
249 block_width, block->border_top * output->scale); 278 render_sharp_line(cairo, border_color, x_pos, y_pos,
250 } 279 block_width, block->border_top);
251 if (border_color && block->border_bottom > 0) { 280 }
252 render_sharp_line(cairo, border_color, x_pos, 281 if (block->border_bottom > 0) {
253 y_pos + render_height - block->border_bottom * output->scale, 282 render_sharp_line(cairo, border_color, x_pos,
254 block_width, block->border_bottom * output->scale); 283 y_pos + render_height - block->border_bottom,
255 } 284 block_width, block->border_bottom);
256 if (border_color && block->border_left > 0) { 285 }
257 render_sharp_line(cairo, border_color, x_pos, y_pos, 286 if (block->border_left > 0) {
258 block->border_left * output->scale, render_height); 287 render_sharp_line(cairo, border_color, x_pos, y_pos,
259 x_pos += block->border_left * output->scale + margin; 288 block->border_left, render_height);
289 }
290 x_pos += block->border_left + margin;
260 } 291 }
261 292
262 double offset = 0; 293 double offset = 0;
263 if (strncmp(block->align, "left", 5) == 0) { 294 if (strncmp(block->align, "left", 4) == 0) {
264 offset = x_pos; 295 offset = x_pos;
265 } else if (strncmp(block->align, "right", 5) == 0) { 296 } else if (strncmp(block->align, "right", 5) == 0) {
266 offset = x_pos + width - text_width; 297 offset = x_pos + width - text_width;
@@ -274,30 +305,35 @@ static uint32_t render_status_block(cairo_t *cairo,
274 color = block->color_set ? block->color : color; 305 color = block->color_set ? block->color : color;
275 color = block->urgent ? config->colors.urgent_workspace.text : color; 306 color = block->urgent ? config->colors.urgent_workspace.text : color;
276 cairo_set_source_u32(cairo, color); 307 cairo_set_source_u32(cairo, color);
277 pango_printf(cairo, config->font, output->scale, 308 choose_text_aa_mode(ctx, color);
278 block->markup, "%s", text); 309 render_text(cairo, config->font_description, 1, block->markup, "%s", text);
279 x_pos += width; 310 x_pos += width;
280 311
281 if (block->border && block->border_right > 0) { 312 if (block->border_set || block->urgent) {
282 x_pos += margin; 313 x_pos += margin;
283 render_sharp_line(cairo, border_color, x_pos, y_pos, 314 if (block->border_right > 0) {
284 block->border_right * output->scale, render_height); 315 render_sharp_line(cairo, border_color, x_pos, y_pos,
285 x_pos += block->border_right * output->scale; 316 block->border_right, render_height);
317 }
318 x_pos += block->border_right;
286 } 319 }
287 320
288 if (!edge && block->separator) { 321 if (!edge && block->separator) {
289 if (output->focused) { 322 if (output->focused) {
290 cairo_set_source_u32(cairo, config->colors.focused_separator); 323 color = config->colors.focused_separator;
291 } else { 324 } else {
292 cairo_set_source_u32(cairo, config->colors.separator); 325 color = config->colors.separator;
293 } 326 }
327 cairo_set_source_u32(cairo, color);
294 if (config->sep_symbol) { 328 if (config->sep_symbol) {
295 offset = x_pos + (sep_block_width - sep_width) / 2; 329 offset = x_pos + (sep_block_width - sep_width) / 2;
296 double sep_y = height / 2.0 - sep_height / 2.0; 330 double sep_y = height / 2.0 - sep_height / 2.0;
297 cairo_move_to(cairo, offset, (int)floor(sep_y)); 331 cairo_move_to(cairo, offset, (int)floor(sep_y));
298 pango_printf(cairo, config->font, output->scale, false, 332 choose_text_aa_mode(ctx, color);
333 render_text(cairo, config->font_description, 1, false,
299 "%s", config->sep_symbol); 334 "%s", config->sep_symbol);
300 } else { 335 } else {
336 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
301 cairo_set_line_width(cairo, 1); 337 cairo_set_line_width(cairo, 1);
302 cairo_move_to(cairo, x_pos + sep_block_width / 2, margin); 338 cairo_move_to(cairo, x_pos + sep_block_width / 2, margin);
303 cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin); 339 cairo_line_to(cairo, x_pos + sep_block_width / 2, height - margin);
@@ -317,18 +353,18 @@ static void predict_status_block_pos(cairo_t *cairo,
317 struct swaybar_config *config = output->bar->config; 353 struct swaybar_config *config = output->bar->config;
318 354
319 int text_width, text_height; 355 int text_width, text_height;
320 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 356 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
321 output->scale, block->markup, "%s", block->full_text); 357 block->markup, "%s", block->full_text);
322 358
323 int margin = 3 * output->scale; 359 int margin = 3;
324 double ws_vertical_padding = config->status_padding * output->scale; 360 double ws_vertical_padding = config->status_padding;
325 361
326 int width = text_width; 362 int width = text_width;
327 363
328 if (block->min_width_str) { 364 if (block->min_width_str) {
329 int w; 365 int w;
330 get_text_size(cairo, config->font, &w, NULL, NULL, 366 get_text_size(cairo, config->font_description, &w, NULL, NULL,
331 output->scale, block->markup, "%s", block->min_width_str); 367 1, block->markup, "%s", block->min_width_str);
332 block->min_width = w; 368 block->min_width = w;
333 } 369 }
334 if (width < block->min_width) { 370 if (width < block->min_width) {
@@ -336,28 +372,28 @@ static void predict_status_block_pos(cairo_t *cairo,
336 } 372 }
337 373
338 uint32_t ideal_height = text_height + ws_vertical_padding * 2; 374 uint32_t ideal_height = text_height + ws_vertical_padding * 2;
339 uint32_t ideal_surface_height = ideal_height / output->scale; 375 uint32_t ideal_surface_height = ideal_height;
340 if (!output->bar->config->height && 376 if (!output->bar->config->height &&
341 output->height < ideal_surface_height) { 377 output->height < ideal_surface_height) {
342 return; 378 return;
343 } 379 }
344 380
345 *x -= width; 381 *x -= width;
346 if ((block->border || block->urgent) && block->border_left > 0) { 382 if ((block->border_set || block->urgent) && block->border_left > 0) {
347 *x -= (block->border_left * output->scale + margin); 383 *x -= (block->border_left + margin);
348 } 384 }
349 if ((block->border || block->urgent) && block->border_right > 0) { 385 if ((block->border_set || block->urgent) && block->border_right > 0) {
350 *x -= (block->border_right * output->scale + margin); 386 *x -= (block->border_right + margin);
351 } 387 }
352 388
353 int sep_width, sep_height; 389 int sep_width, sep_height;
354 int sep_block_width = block->separator_block_width; 390 int sep_block_width = block->separator_block_width;
355 if (!edge) { 391 if (!edge) {
356 if (config->sep_symbol) { 392 if (config->sep_symbol) {
357 get_text_size(cairo, config->font, &sep_width, &sep_height, NULL, 393 get_text_size(cairo, config->font_description, &sep_width, &sep_height, NULL,
358 output->scale, false, "%s", config->sep_symbol); 394 1, false, "%s", config->sep_symbol);
359 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2; 395 uint32_t _ideal_height = sep_height + ws_vertical_padding * 2;
360 uint32_t _ideal_surface_height = _ideal_height / output->scale; 396 uint32_t _ideal_surface_height = _ideal_height;
361 if (!output->bar->config->height && 397 if (!output->bar->config->height &&
362 output->height < _ideal_surface_height) { 398 output->height < _ideal_surface_height) {
363 return; 399 return;
@@ -368,13 +404,13 @@ static void predict_status_block_pos(cairo_t *cairo,
368 } 404 }
369 *x -= sep_block_width; 405 *x -= sep_block_width;
370 } else if (config->status_edge_padding) { 406 } else if (config->status_edge_padding) {
371 *x -= config->status_edge_padding * output->scale; 407 *x -= config->status_edge_padding;
372 } 408 }
373} 409}
374 410
375static double predict_status_line_pos(cairo_t *cairo, 411static double predict_status_line_pos(cairo_t *cairo,
376 struct swaybar_output *output, double x) { 412 struct swaybar_output *output, double x) {
377 bool edge = x == output->width * output->scale; 413 bool edge = x == output->width;
378 struct i3bar_block *block; 414 struct i3bar_block *block;
379 wl_list_for_each(block, &output->bar->status->blocks, link) { 415 wl_list_for_each(block, &output->bar->status->blocks, link) {
380 predict_status_block_pos(cairo, output, block, &x, edge); 416 predict_status_block_pos(cairo, output, block, &x, edge);
@@ -389,24 +425,24 @@ static uint32_t predict_workspace_button_length(cairo_t *cairo,
389 struct swaybar_config *config = output->bar->config; 425 struct swaybar_config *config = output->bar->config;
390 426
391 int text_width, text_height; 427 int text_width, text_height;
392 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 428 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL, 1,
393 output->scale, config->pango_markup, "%s", ws->label); 429 config->pango_markup, "%s", ws->label);
394 430
395 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 431 int ws_vertical_padding = WS_VERTICAL_PADDING;
396 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 432 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
397 int border_width = BORDER_WIDTH * output->scale; 433 int border_width = BORDER_WIDTH;
398 434
399 uint32_t ideal_height = ws_vertical_padding * 2 + text_height 435 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
400 + border_width * 2; 436 + border_width * 2;
401 uint32_t ideal_surface_height = ideal_height / output->scale; 437 uint32_t ideal_surface_height = ideal_height;
402 if (!output->bar->config->height && 438 if (!output->bar->config->height &&
403 output->height < ideal_surface_height) { 439 output->height < ideal_surface_height) {
404 return 0; 440 return 0;
405 } 441 }
406 442
407 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 443 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
408 if (width < config->workspace_min_width * output->scale) { 444 if (width < config->workspace_min_width) {
409 width = config->workspace_min_width * output->scale; 445 width = config->workspace_min_width;
410 } 446 }
411 return width; 447 return width;
412} 448}
@@ -437,39 +473,40 @@ static uint32_t predict_binding_mode_indicator_length(cairo_t *cairo,
437 } 473 }
438 474
439 int text_width, text_height; 475 int text_width, text_height;
440 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 476 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
441 output->scale, output->bar->mode_pango_markup, 477 1, output->bar->mode_pango_markup,
442 "%s", mode); 478 "%s", mode);
443 479
444 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 480 int ws_vertical_padding = WS_VERTICAL_PADDING;
445 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 481 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
446 int border_width = BORDER_WIDTH * output->scale; 482 int border_width = BORDER_WIDTH;
447 483
448 uint32_t ideal_height = text_height + ws_vertical_padding * 2 484 uint32_t ideal_height = text_height + ws_vertical_padding * 2
449 + border_width * 2; 485 + border_width * 2;
450 uint32_t ideal_surface_height = ideal_height / output->scale; 486 uint32_t ideal_surface_height = ideal_height;
451 if (!output->bar->config->height && 487 if (!output->bar->config->height &&
452 output->height < ideal_surface_height) { 488 output->height < ideal_surface_height) {
453 return 0; 489 return 0;
454 } 490 }
455 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 491 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
456 if (width < config->workspace_min_width * output->scale) { 492 if (width < config->workspace_min_width) {
457 width = config->workspace_min_width * output->scale; 493 width = config->workspace_min_width;
458 } 494 }
459 return width; 495 return width;
460} 496}
461 497
462static uint32_t render_status_line_i3bar(cairo_t *cairo, 498static uint32_t render_status_line_i3bar(struct render_context *ctx, double *x) {
463 struct swaybar_output *output, double *x) { 499 struct swaybar_output *output = ctx->output;
464 uint32_t max_height = 0; 500 uint32_t max_height = 0;
465 bool edge = *x == output->width * output->scale; 501 bool edge = *x == output->width;
466 struct i3bar_block *block; 502 struct i3bar_block *block;
467 bool use_short_text = false; 503 bool use_short_text = false;
468 504
505 cairo_t *cairo = ctx->cairo;
469 double reserved_width = 506 double reserved_width =
470 predict_workspace_buttons_length(cairo, output) + 507 predict_workspace_buttons_length(cairo, output) +
471 predict_binding_mode_indicator_length(cairo, output) + 508 predict_binding_mode_indicator_length(cairo, output) +
472 3 * output->scale; // require a bit of space for margin 509 3; // require a bit of space for margin
473 510
474 double predicted_full_pos = 511 double predicted_full_pos =
475 predict_status_line_pos(cairo, output, *x); 512 predict_status_line_pos(cairo, output, *x);
@@ -479,7 +516,7 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
479 } 516 }
480 517
481 wl_list_for_each(block, &output->bar->status->blocks, link) { 518 wl_list_for_each(block, &output->bar->status->blocks, link) {
482 uint32_t h = render_status_block(cairo, output, block, x, edge, 519 uint32_t h = render_status_block(ctx, block, x, edge,
483 use_short_text); 520 use_short_text);
484 max_height = h > max_height ? h : max_height; 521 max_height = h > max_height ? h : max_height;
485 edge = false; 522 edge = false;
@@ -487,53 +524,56 @@ static uint32_t render_status_line_i3bar(cairo_t *cairo,
487 return max_height; 524 return max_height;
488} 525}
489 526
490static uint32_t render_status_line(cairo_t *cairo, 527static uint32_t render_status_line(struct render_context *ctx, double *x) {
491 struct swaybar_output *output, double *x) { 528 struct status_line *status = ctx->output->bar->status;
492 struct status_line *status = output->bar->status;
493 switch (status->protocol) { 529 switch (status->protocol) {
494 case PROTOCOL_ERROR: 530 case PROTOCOL_ERROR:
495 return render_status_line_error(cairo, output, x); 531 return render_status_line_error(ctx, x);
496 case PROTOCOL_TEXT: 532 case PROTOCOL_TEXT:
497 return render_status_line_text(cairo, output, x); 533 return render_status_line_text(ctx, x);
498 case PROTOCOL_I3BAR: 534 case PROTOCOL_I3BAR:
499 return render_status_line_i3bar(cairo, output, x); 535 return render_status_line_i3bar(ctx, x);
500 case PROTOCOL_UNDEF: 536 case PROTOCOL_UNDEF:
501 return 0; 537 return 0;
502 } 538 }
503 return 0; 539 return 0;
504} 540}
505 541
506static uint32_t render_binding_mode_indicator(cairo_t *cairo, 542static uint32_t render_binding_mode_indicator(struct render_context *ctx,
507 struct swaybar_output *output, double x) { 543 double x) {
544 struct swaybar_output *output = ctx->output;
508 const char *mode = output->bar->mode; 545 const char *mode = output->bar->mode;
509 if (!mode) { 546 if (!mode) {
510 return 0; 547 return 0;
511 } 548 }
512 549
550 cairo_t *cairo = ctx->cairo;
513 struct swaybar_config *config = output->bar->config; 551 struct swaybar_config *config = output->bar->config;
514 int text_width, text_height; 552 int text_width, text_height;
515 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 553 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
516 output->scale, output->bar->mode_pango_markup, 554 1, output->bar->mode_pango_markup,
517 "%s", mode); 555 "%s", mode);
518 556
519 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 557 int ws_vertical_padding = WS_VERTICAL_PADDING;
520 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 558 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
521 int border_width = BORDER_WIDTH * output->scale; 559 int border_width = BORDER_WIDTH;
522 560
523 uint32_t ideal_height = text_height + ws_vertical_padding * 2 561 uint32_t ideal_height = text_height + ws_vertical_padding * 2
524 + border_width * 2; 562 + border_width * 2;
525 uint32_t ideal_surface_height = ideal_height / output->scale; 563 uint32_t ideal_surface_height = ideal_height;
526 if (!output->bar->config->height && 564 if (!output->bar->config->height &&
527 output->height < ideal_surface_height) { 565 output->height < ideal_surface_height) {
528 return ideal_surface_height; 566 return ideal_surface_height;
529 } 567 }
530 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 568 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
531 if (width < config->workspace_min_width * output->scale) { 569 if (width < config->workspace_min_width) {
532 width = config->workspace_min_width * output->scale; 570 width = config->workspace_min_width;
533 } 571 }
534 572
535 uint32_t height = output->height * output->scale; 573 uint32_t height = output->height;
574 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
536 cairo_set_source_u32(cairo, config->colors.binding_mode.background); 575 cairo_set_source_u32(cairo, config->colors.binding_mode.background);
576 ctx->background_color = config->colors.binding_mode.background;
537 cairo_rectangle(cairo, x, 0, width, height); 577 cairo_rectangle(cairo, x, 0, width, height);
538 cairo_fill(cairo); 578 cairo_fill(cairo);
539 579
@@ -550,24 +590,30 @@ static uint32_t render_binding_mode_indicator(cairo_t *cairo,
550 double text_y = height / 2.0 - text_height / 2.0; 590 double text_y = height / 2.0 - text_height / 2.0;
551 cairo_set_source_u32(cairo, config->colors.binding_mode.text); 591 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)); 592 cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y));
553 pango_printf(cairo, config->font, output->scale, 593 choose_text_aa_mode(ctx, config->colors.binding_mode.text);
554 output->bar->mode_pango_markup, "%s", mode); 594 render_text(cairo, config->font_description, 1, output->bar->mode_pango_markup,
595 "%s", mode);
555 return output->height; 596 return output->height;
556} 597}
557 598
558static enum hotspot_event_handling workspace_hotspot_callback( 599static enum hotspot_event_handling workspace_hotspot_callback(
559 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 600 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
560 double x, double y, uint32_t button, void *data) { 601 double x, double y, uint32_t button, bool released, void *data) {
561 if (button != BTN_LEFT) { 602 if (button != BTN_LEFT) {
562 return HOTSPOT_PROCESS; 603 return HOTSPOT_PROCESS;
563 } 604 }
605 if (released) {
606 // Since we handle the pressed event, also handle the released event
607 // to block it from falling through to a binding in the bar
608 return HOTSPOT_IGNORE;
609 }
564 ipc_send_workspace_command(output->bar, (const char *)data); 610 ipc_send_workspace_command(output->bar, (const char *)data);
565 return HOTSPOT_IGNORE; 611 return HOTSPOT_IGNORE;
566} 612}
567 613
568static uint32_t render_workspace_button(cairo_t *cairo, 614static uint32_t render_workspace_button(struct render_context *ctx,
569 struct swaybar_output *output,
570 struct swaybar_workspace *ws, double *x) { 615 struct swaybar_workspace *ws, double *x) {
616 struct swaybar_output *output = ctx->output;
571 struct swaybar_config *config = output->bar->config; 617 struct swaybar_config *config = output->bar->config;
572 struct box_colors box_colors; 618 struct box_colors box_colors;
573 if (ws->urgent) { 619 if (ws->urgent) {
@@ -580,30 +626,33 @@ static uint32_t render_workspace_button(cairo_t *cairo,
580 box_colors = config->colors.inactive_workspace; 626 box_colors = config->colors.inactive_workspace;
581 } 627 }
582 628
583 uint32_t height = output->height * output->scale; 629 uint32_t height = output->height;
584 630
631 cairo_t *cairo = ctx->cairo;
585 int text_width, text_height; 632 int text_width, text_height;
586 get_text_size(cairo, config->font, &text_width, &text_height, NULL, 633 get_text_size(cairo, config->font_description, &text_width, &text_height, NULL,
587 output->scale, config->pango_markup, "%s", ws->label); 634 1, config->pango_markup, "%s", ws->label);
588 635
589 int ws_vertical_padding = WS_VERTICAL_PADDING * output->scale; 636 int ws_vertical_padding = WS_VERTICAL_PADDING;
590 int ws_horizontal_padding = WS_HORIZONTAL_PADDING * output->scale; 637 int ws_horizontal_padding = WS_HORIZONTAL_PADDING;
591 int border_width = BORDER_WIDTH * output->scale; 638 int border_width = BORDER_WIDTH;
592 639
593 uint32_t ideal_height = ws_vertical_padding * 2 + text_height 640 uint32_t ideal_height = ws_vertical_padding * 2 + text_height
594 + border_width * 2; 641 + border_width * 2;
595 uint32_t ideal_surface_height = ideal_height / output->scale; 642 uint32_t ideal_surface_height = ideal_height;
596 if (!output->bar->config->height && 643 if (!output->bar->config->height &&
597 output->height < ideal_surface_height) { 644 output->height < ideal_surface_height) {
598 return ideal_surface_height; 645 return ideal_surface_height;
599 } 646 }
600 647
601 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2; 648 uint32_t width = text_width + ws_horizontal_padding * 2 + border_width * 2;
602 if (width < config->workspace_min_width * output->scale) { 649 if (width < config->workspace_min_width) {
603 width = config->workspace_min_width * output->scale; 650 width = config->workspace_min_width;
604 } 651 }
605 652
653 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
606 cairo_set_source_u32(cairo, box_colors.background); 654 cairo_set_source_u32(cairo, box_colors.background);
655 ctx->background_color = box_colors.background;
607 cairo_rectangle(cairo, *x, 0, width, height); 656 cairo_rectangle(cairo, *x, 0, width, height);
608 cairo_fill(cairo); 657 cairo_fill(cairo);
609 658
@@ -620,7 +669,8 @@ static uint32_t render_workspace_button(cairo_t *cairo,
620 double text_y = height / 2.0 - text_height / 2.0; 669 double text_y = height / 2.0 - text_height / 2.0;
621 cairo_set_source_u32(cairo, box_colors.text); 670 cairo_set_source_u32(cairo, box_colors.text);
622 cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); 671 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, 672 choose_text_aa_mode(ctx, box_colors.text);
673 render_text(cairo, config->font_description, 1, config->pango_markup,
624 "%s", ws->label); 674 "%s", ws->label);
625 675
626 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 676 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
@@ -637,20 +687,15 @@ static uint32_t render_workspace_button(cairo_t *cairo,
637 return output->height; 687 return output->height;
638} 688}
639 689
640static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) { 690static uint32_t render_to_cairo(struct render_context *ctx) {
691 cairo_t *cairo = ctx->cairo;
692 struct swaybar_output *output = ctx->output;
641 struct swaybar *bar = output->bar; 693 struct swaybar *bar = output->bar;
642 struct swaybar_config *config = bar->config; 694 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 695
651 int th; 696 int th;
652 get_text_size(cairo, config->font, NULL, &th, NULL, output->scale, false, ""); 697 get_text_size(cairo, config->font_description, NULL, &th, NULL, 1, false, "");
653 uint32_t max_height = (th + WS_VERTICAL_PADDING * 4) / output->scale; 698 uint32_t max_height = (th + WS_VERTICAL_PADDING * 4);
654 /* 699 /*
655 * Each render_* function takes the actual height of the bar, and returns 700 * 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 701 * the ideal height. If the actual height is too short, the render function
@@ -658,7 +703,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 703 * height is too tall, the render function should adapt its drawing to
659 * utilize the available space. 704 * utilize the available space.
660 */ 705 */
661 double x = output->width * output->scale; 706 double x = output->width;
662#if HAVE_TRAY 707#if HAVE_TRAY
663 if (bar->tray) { 708 if (bar->tray) {
664 uint32_t h = render_tray(cairo, output, &x); 709 uint32_t h = render_tray(cairo, output, &x);
@@ -666,19 +711,19 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
666 } 711 }
667#endif 712#endif
668 if (bar->status) { 713 if (bar->status) {
669 uint32_t h = render_status_line(cairo, output, &x); 714 uint32_t h = render_status_line(ctx, &x);
670 max_height = h > max_height ? h : max_height; 715 max_height = h > max_height ? h : max_height;
671 } 716 }
672 x = 0; 717 x = 0;
673 if (config->workspace_buttons) { 718 if (config->workspace_buttons) {
674 struct swaybar_workspace *ws; 719 struct swaybar_workspace *ws;
675 wl_list_for_each(ws, &output->workspaces, link) { 720 wl_list_for_each(ws, &output->workspaces, link) {
676 uint32_t h = render_workspace_button(cairo, output, ws, &x); 721 uint32_t h = render_workspace_button(ctx, ws, &x);
677 max_height = h > max_height ? h : max_height; 722 max_height = h > max_height ? h : max_height;
678 } 723 }
679 } 724 }
680 if (config->binding_mode_indicator) { 725 if (config->binding_mode_indicator) {
681 uint32_t h = render_binding_mode_indicator(cairo, output, x); 726 uint32_t h = render_binding_mode_indicator(ctx, x);
682 max_height = h > max_height ? h : max_height; 727 max_height = h > max_height ? h : max_height;
683 } 728 }
684 729
@@ -708,26 +753,44 @@ void render_frame(struct swaybar_output *output) {
708 753
709 free_hotspots(&output->hotspots); 754 free_hotspots(&output->hotspots);
710 755
756 uint32_t background_color;
757 if (output->focused) {
758 background_color = output->bar->config->colors.focused_background;
759 } else {
760 background_color = output->bar->config->colors.background;
761 }
762
763 struct render_context ctx = { 0 };
764 ctx.output = output;
765 // initial background color used for deciding the best way to antialias text
766 ctx.background_color = background_color;
767
711 cairo_surface_t *recorder = cairo_recording_surface_create( 768 cairo_surface_t *recorder = cairo_recording_surface_create(
712 CAIRO_CONTENT_COLOR_ALPHA, NULL); 769 CAIRO_CONTENT_COLOR_ALPHA, NULL);
713 cairo_t *cairo = cairo_create(recorder); 770 cairo_t *cairo = cairo_create(recorder);
771 cairo_scale(cairo, output->scale, output->scale);
714 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 772 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
773 ctx.cairo = cairo;
774
715 cairo_font_options_t *fo = cairo_font_options_create(); 775 cairo_font_options_t *fo = cairo_font_options_create();
716 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); 776 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY);
777 ctx.textaa_safe = fo;
717 if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) { 778 if (output->subpixel == WL_OUTPUT_SUBPIXEL_NONE) {
718 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY); 779 ctx.textaa_sharp = ctx.textaa_safe;
719 } else { 780 } else {
781 fo = cairo_font_options_create();
720 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); 782 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
721 cairo_font_options_set_subpixel_order(fo, 783 cairo_font_options_set_subpixel_order(fo,
722 to_cairo_subpixel_order(output->subpixel)); 784 to_cairo_subpixel_order(output->subpixel));
785 ctx.textaa_sharp = fo;
723 } 786 }
724 cairo_set_font_options(cairo, fo); 787
725 cairo_font_options_destroy(fo); 788
726 cairo_save(cairo); 789 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
727 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); 790 cairo_set_source_u32(cairo, background_color);
728 cairo_paint(cairo); 791 cairo_paint(cairo);
729 cairo_restore(cairo); 792
730 uint32_t height = render_to_cairo(cairo, output); 793 uint32_t height = render_to_cairo(&ctx);
731 int config_height = output->bar->config->height; 794 int config_height = output->bar->config->height;
732 if (config_height > 0) { 795 if (config_height > 0) {
733 height = config_height; 796 height = config_height;
@@ -753,9 +816,7 @@ void render_frame(struct swaybar_output *output) {
753 output->width * output->scale, 816 output->width * output->scale,
754 output->height * output->scale); 817 output->height * output->scale);
755 if (!output->current_buffer) { 818 if (!output->current_buffer) {
756 cairo_surface_destroy(recorder); 819 goto cleanup;
757 cairo_destroy(cairo);
758 return;
759 } 820 }
760 cairo_t *shm = output->current_buffer->cairo; 821 cairo_t *shm = output->current_buffer->cairo;
761 822
@@ -773,12 +834,29 @@ void render_frame(struct swaybar_output *output) {
773 wl_surface_damage(output->surface, 0, 0, 834 wl_surface_damage(output->surface, 0, 0,
774 output->width, output->height); 835 output->width, output->height);
775 836
837 uint32_t bg_alpha = background_color & 0xFF;
838 if (bg_alpha == 0xFF) {
839 struct wl_region *region =
840 wl_compositor_create_region(output->bar->compositor);
841 wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
842 wl_surface_set_opaque_region(output->surface, region);
843 wl_region_destroy(region);
844 } else {
845 wl_surface_set_opaque_region(output->surface, NULL);
846 }
847
776 struct wl_callback *frame_callback = wl_surface_frame(output->surface); 848 struct wl_callback *frame_callback = wl_surface_frame(output->surface);
777 wl_callback_add_listener(frame_callback, &output_frame_listener, output); 849 wl_callback_add_listener(frame_callback, &output_frame_listener, output);
778 output->frame_scheduled = true; 850 output->frame_scheduled = true;
779 851
780 wl_surface_commit(output->surface); 852 wl_surface_commit(output->surface);
781 } 853 }
854
855cleanup:
856 if (ctx.textaa_sharp != ctx.textaa_safe) {
857 cairo_font_options_destroy(ctx.textaa_sharp);
858 }
859 cairo_font_options_destroy(ctx.textaa_safe);
782 cairo_surface_destroy(recorder); 860 cairo_surface_destroy(recorder);
783 cairo_destroy(cairo); 861 cairo_destroy(cairo);
784} 862}
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index ecd91032..e542e606 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 1#include <assert.h>
3#include <fcntl.h> 2#include <fcntl.h>
4#include <sys/ioctl.h> 3#include <sys/ioctl.h>
@@ -117,11 +116,11 @@ bool status_handle_readable(struct status_line *status) {
117 status->text = status->buffer; 116 status->text = status->buffer;
118 // intentional fall-through 117 // intentional fall-through
119 case PROTOCOL_TEXT: 118 case PROTOCOL_TEXT:
120 errno = 0;
121 while (true) { 119 while (true) {
122 if (status->buffer[read_bytes - 1] == '\n') { 120 if (status->buffer[read_bytes - 1] == '\n') {
123 status->buffer[read_bytes - 1] = '\0'; 121 status->buffer[read_bytes - 1] = '\0';
124 } 122 }
123 errno = 0;
125 read_bytes = getline(&status->buffer, 124 read_bytes = getline(&status->buffer,
126 &status->buffer_size, status->read); 125 &status->buffer_size, status->read);
127 if (errno == EAGAIN) { 126 if (errno == EAGAIN) {
@@ -157,7 +156,12 @@ struct status_line *status_line_init(char *cmd) {
157 assert(!getenv("WAYLAND_SOCKET") && "display must be initialized before " 156 assert(!getenv("WAYLAND_SOCKET") && "display must be initialized before "
158 " starting `status-command`; WAYLAND_SOCKET should not be set"); 157 " starting `status-command`; WAYLAND_SOCKET should not be set");
159 status->pid = fork(); 158 status->pid = fork();
160 if (status->pid == 0) { 159 if (status->pid < 0) {
160 sway_log_errno(SWAY_ERROR, "fork failed");
161 exit(1);
162 } else if (status->pid == 0) {
163 setpgid(0, 0);
164
161 dup2(pipe_read_fd[1], STDOUT_FILENO); 165 dup2(pipe_read_fd[1], STDOUT_FILENO);
162 close(pipe_read_fd[0]); 166 close(pipe_read_fd[0]);
163 close(pipe_read_fd[1]); 167 close(pipe_read_fd[1]);
@@ -185,8 +189,8 @@ struct status_line *status_line_init(char *cmd) {
185 189
186void status_line_free(struct status_line *status) { 190void status_line_free(struct status_line *status) {
187 status_line_close_fds(status); 191 status_line_close_fds(status);
188 kill(status->pid, status->cont_signal); 192 kill(-status->pid, status->cont_signal);
189 kill(status->pid, SIGTERM); 193 kill(-status->pid, SIGTERM);
190 waitpid(status->pid, NULL, 0); 194 waitpid(status->pid, NULL, 0);
191 if (status->protocol == PROTOCOL_I3BAR) { 195 if (status->protocol == PROTOCOL_I3BAR) {
192 struct i3bar_block *block, *tmp; 196 struct i3bar_block *block, *tmp;
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
index ddf2416d..79b54606 100644
--- a/swaybar/tray/host.c
+++ b/swaybar/tray/host.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <stdio.h> 2#include <stdio.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -10,6 +9,7 @@
10#include "swaybar/tray/tray.h" 9#include "swaybar/tray/tray.h"
11#include "list.h" 10#include "list.h"
12#include "log.h" 11#include "log.h"
12#include "stringop.h"
13 13
14static const char *watcher_path = "/StatusNotifierWatcher"; 14static const char *watcher_path = "/StatusNotifierWatcher";
15 15
@@ -138,12 +138,10 @@ static int handle_new_watcher(sd_bus_message *msg,
138 138
139bool init_host(struct swaybar_host *host, char *protocol, 139bool init_host(struct swaybar_host *host, char *protocol,
140 struct swaybar_tray *tray) { 140 struct swaybar_tray *tray) {
141 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; 141 host->watcher_interface = format_str("org.%s.StatusNotifierWatcher", protocol);
142 host->watcher_interface = malloc(len);
143 if (!host->watcher_interface) { 142 if (!host->watcher_interface) {
144 return false; 143 return false;
145 } 144 }
146 snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
147 145
148 sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL; 146 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, 147 int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
@@ -173,13 +171,10 @@ bool init_host(struct swaybar_host *host, char *protocol,
173 } 171 }
174 172
175 pid_t pid = getpid(); 173 pid_t pid = getpid();
176 size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", 174 host->service = format_str("org.%s.StatusNotifierHost-%d", protocol, pid);
177 protocol, pid) + 1;
178 host->service = malloc(service_len);
179 if (!host->service) { 175 if (!host->service) {
180 goto error; 176 goto error;
181 } 177 }
182 snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
183 ret = sd_bus_request_name(tray->bus, host->service, 0); 178 ret = sd_bus_request_name(tray->bus, host->service, 0);
184 if (ret < 0) { 179 if (ret < 0) {
185 sway_log(SWAY_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); 180 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..659edd86 100644
--- a/swaybar/tray/icon.c
+++ b/swaybar/tray/icon.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h> 1#include <ctype.h>
3#include <dirent.h> 2#include <dirent.h>
4#include <stdbool.h> 3#include <stdbool.h>
@@ -40,9 +39,7 @@ static list_t *get_basedirs(void) {
40 data_dirs = strdup(data_dirs); 39 data_dirs = strdup(data_dirs);
41 char *dir = strtok(data_dirs, ":"); 40 char *dir = strtok(data_dirs, ":");
42 do { 41 do {
43 size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1; 42 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); 43 list_add(basedirs, path);
47 } while ((dir = strtok(NULL, ":"))); 44 } while ((dir = strtok(NULL, ":")));
48 free(data_dirs); 45 free(data_dirs);
@@ -206,13 +203,7 @@ static const char *entry_handler(char *group, char *key, char *value,
206 */ 203 */
207static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { 204static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
208 // look for index.theme file 205 // look for index.theme file
209 size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir, 206 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"); 207 FILE *theme_file = fopen(path, "r");
217 free(path); 208 free(path);
218 if (!theme_file) { 209 if (!theme_file) {
@@ -416,26 +407,20 @@ static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
416#endif 407#endif
417 }; 408 };
418 409
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) { 410 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
424 snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir, 411 char *path = format_str("%s/%s/%s/%s.%s",
425 name, extensions[i]); 412 basedir, theme, subdir, name, extensions[i]);
426 if (access(path, R_OK) == 0) { 413 if (access(path, R_OK) == 0) {
427 return path; 414 return path;
428 } 415 }
416 free(path);
429 } 417 }
430 418
431 free(path);
432 return NULL; 419 return NULL;
433} 420}
434 421
435static bool theme_exists_in_basedir(char *theme, char *basedir) { 422static bool theme_exists_in_basedir(char *theme, char *basedir) {
436 size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1; 423 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); 424 bool ret = dir_exists(path);
440 free(path); 425 free(path);
441 return ret; 426 return ret;
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
index a5660f62..ca6c03ad 100644
--- a/swaybar/tray/item.c
+++ b/swaybar/tray/item.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <arpa/inet.h> 1#include <arpa/inet.h>
3#include <cairo.h> 2#include <cairo.h>
4#include <limits.h> 3#include <limits.h>
@@ -7,13 +6,13 @@
7#include <string.h> 6#include <string.h>
8#include "swaybar/bar.h" 7#include "swaybar/bar.h"
9#include "swaybar/config.h" 8#include "swaybar/config.h"
9#include "swaybar/image.h"
10#include "swaybar/input.h" 10#include "swaybar/input.h"
11#include "swaybar/tray/host.h" 11#include "swaybar/tray/host.h"
12#include "swaybar/tray/icon.h" 12#include "swaybar/tray/icon.h"
13#include "swaybar/tray/item.h" 13#include "swaybar/tray/item.h"
14#include "swaybar/tray/tray.h" 14#include "swaybar/tray/tray.h"
15#include "background-image.h" 15#include "cairo_util.h"
16#include "cairo.h"
17#include "list.h" 16#include "list.h"
18#include "log.h" 17#include "log.h"
19#include "wlr-layer-shell-unstable-v1-client-protocol.h" 18#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@@ -118,8 +117,13 @@ static int get_property_callback(sd_bus_message *msg, void *data,
118 117
119 int ret; 118 int ret;
120 if (sd_bus_message_is_method_error(msg, NULL)) { 119 if (sd_bus_message_is_method_error(msg, NULL)) {
121 sway_log(SWAY_ERROR, "%s %s: %s", sni->watcher_id, prop, 120 const sd_bus_error *err = sd_bus_message_get_error(msg);
122 sd_bus_message_get_error(msg)->message); 121 sway_log_importance_t log_lv = SWAY_ERROR;
122 if ((!strcmp(prop, "IconThemePath")) &&
123 (!strcmp(err->name, SD_BUS_ERROR_UNKNOWN_PROPERTY))) {
124 log_lv = SWAY_DEBUG;
125 }
126 sway_log(log_lv, "%s %s: %s", sni->watcher_id, prop, err->message);
123 ret = sd_bus_message_get_errno(msg); 127 ret = sd_bus_message_get_errno(msg);
124 goto cleanup; 128 goto cleanup;
125 } 129 }
@@ -380,13 +384,18 @@ static int cmp_sni_id(const void *item, const void *cmp_to) {
380 384
381static enum hotspot_event_handling icon_hotspot_callback( 385static enum hotspot_event_handling icon_hotspot_callback(
382 struct swaybar_output *output, struct swaybar_hotspot *hotspot, 386 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
383 double x, double y, uint32_t button, void *data) { 387 double x, double y, uint32_t button, bool released, void *data) {
384 sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data); 388 sway_log(SWAY_DEBUG, "Clicked on %s", (char *)data);
385 389
386 struct swaybar_tray *tray = output->bar->tray; 390 struct swaybar_tray *tray = output->bar->tray;
387 int idx = list_seq_find(tray->items, cmp_sni_id, data); 391 int idx = list_seq_find(tray->items, cmp_sni_id, data);
388 392
389 if (idx != -1) { 393 if (idx != -1) {
394 if (released) {
395 // Since we handle the pressed event, also handle the released event
396 // to block it from falling through to a binding in the bar
397 return HOTSPOT_IGNORE;
398 }
390 struct swaybar_sni *sni = tray->items->items[idx]; 399 struct swaybar_sni *sni = tray->items->items[idx];
391 // guess global position since wayland doesn't expose it 400 // guess global position since wayland doesn't expose it
392 struct swaybar_config *config = tray->bar->config; 401 struct swaybar_config *config = tray->bar->config;
@@ -421,7 +430,7 @@ static void reload_sni(struct swaybar_sni *sni, char *icon_theme,
421 list_free(icon_search_paths); 430 list_free(icon_search_paths);
422 if (icon_path) { 431 if (icon_path) {
423 cairo_surface_destroy(sni->icon); 432 cairo_surface_destroy(sni->icon);
424 sni->icon = load_background_image(icon_path); 433 sni->icon = load_image(icon_path);
425 free(icon_path); 434 free(icon_path);
426 return; 435 return;
427 } 436 }
@@ -461,6 +470,11 @@ uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
461 sni->target_size = target_size; 470 sni->target_size = target_size;
462 } 471 }
463 472
473 // Passive
474 if (sni->status && sni->status[0] == 'P') {
475 return 0;
476 }
477
464 int icon_size; 478 int icon_size;
465 cairo_surface_t *icon; 479 cairo_surface_t *icon;
466 if (sni->icon) { 480 if (sni->icon) {
@@ -488,24 +502,36 @@ uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
488 cairo_destroy(cairo_icon); 502 cairo_destroy(cairo_icon);
489 } 503 }
490 504
491 int padded_size = icon_size + 2*padding; 505 double descaled_padding = (double)padding / output->scale;
492 *x -= padded_size; 506 double descaled_icon_size = (double)icon_size / output->scale;
493 int y = floor((height - padded_size) / 2.0); 507
508 int size = descaled_icon_size + 2 * descaled_padding;
509 *x -= size;
510 int icon_y = floor((output->height - size) / 2.0);
494 511
495 cairo_operator_t op = cairo_get_operator(cairo); 512 cairo_operator_t op = cairo_get_operator(cairo);
496 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); 513 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
497 cairo_set_source_surface(cairo, icon, *x + padding, y + padding); 514
498 cairo_rectangle(cairo, *x, y, padded_size, padded_size); 515 cairo_matrix_t scale_matrix;
516 cairo_pattern_t *icon_pattern = cairo_pattern_create_for_surface(icon);
517 // TODO: check cairo_pattern_status for "ENOMEM"
518 cairo_matrix_init_scale(&scale_matrix, output->scale, output->scale);
519 cairo_matrix_translate(&scale_matrix, -(*x + descaled_padding), -(icon_y + descaled_padding));
520 cairo_pattern_set_matrix(icon_pattern, &scale_matrix);
521 cairo_set_source(cairo, icon_pattern);
522 cairo_rectangle(cairo, *x, icon_y, size, size);
499 cairo_fill(cairo); 523 cairo_fill(cairo);
524
500 cairo_set_operator(cairo, op); 525 cairo_set_operator(cairo, op);
501 526
527 cairo_pattern_destroy(icon_pattern);
502 cairo_surface_destroy(icon); 528 cairo_surface_destroy(icon);
503 529
504 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); 530 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
505 hotspot->x = *x; 531 hotspot->x = *x;
506 hotspot->y = 0; 532 hotspot->y = 0;
507 hotspot->width = height; 533 hotspot->width = size;
508 hotspot->height = height; 534 hotspot->height = output->height;
509 hotspot->callback = icon_hotspot_callback; 535 hotspot->callback = icon_hotspot_callback;
510 hotspot->destroy = free; 536 hotspot->destroy = free;
511 hotspot->data = strdup(sni->watcher_id); 537 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..3cfea8d8 100644
--- a/swaybar/tray/watcher.c
+++ b/swaybar/tray/watcher.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h> 1#include <stdbool.h>
3#include <stddef.h> 2#include <stddef.h>
4#include <stdio.h> 3#include <stdio.h>
@@ -6,6 +5,7 @@
6#include <string.h> 5#include <string.h>
7#include "list.h" 6#include "list.h"
8#include "log.h" 7#include "log.h"
8#include "stringop.h"
9#include "swaybar/tray/watcher.h" 9#include "swaybar/tray/watcher.h"
10 10
11static const char *obj_path = "/StatusNotifierWatcher"; 11static const char *obj_path = "/StatusNotifierWatcher";
@@ -76,9 +76,7 @@ static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
76 service = service_or_path; 76 service = service_or_path;
77 path = "/StatusNotifierItem"; 77 path = "/StatusNotifierItem";
78 } 78 }
79 size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1; 79 id = format_str("%s%s", service, path);
80 id = malloc(id_len);
81 snprintf(id, id_len, "%s%s", service, path);
82 } 80 }
83 81
84 if (list_seq_find(watcher->items, cmp_id, id) == -1) { 82 if (list_seq_find(watcher->items, cmp_id, id) == -1) {
@@ -107,7 +105,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); 105 sway_log(SWAY_DEBUG, "Registering Status Notifier Host '%s'", service);
108 list_add(watcher->hosts, strdup(service)); 106 list_add(watcher->hosts, strdup(service));
109 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, 107 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
110 "StatusNotifierHostRegistered", "s", service); 108 "StatusNotifierHostRegistered", "");
111 } else { 109 } else {
112 sway_log(SWAY_DEBUG, "Status Notifier Host '%s' already registered", service); 110 sway_log(SWAY_DEBUG, "Status Notifier Host '%s' already registered", service);
113 } 111 }
@@ -159,9 +157,7 @@ struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
159 return NULL; 157 return NULL;
160 } 158 }
161 159
162 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; 160 watcher->interface = format_str("org.%s.StatusNotifierWatcher", protocol);
163 watcher->interface = malloc(len);
164 snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
165 161
166 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL; 162 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
167 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path, 163 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
diff --git a/swaymsg/main.c b/swaymsg/main.c
index 60536e48..573a7b16 100644
--- a/swaymsg/main.c
+++ b/swaymsg/main.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 200809L 1
2#include <limits.h>
2#include <stdio.h> 3#include <stdio.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <string.h> 5#include <string.h>
@@ -58,7 +59,7 @@ static void pretty_print_cmd(json_object *r) {
58 if (!success_object(r)) { 59 if (!success_object(r)) {
59 json_object *error; 60 json_object *error;
60 if (!json_object_object_get_ex(r, "error", &error)) { 61 if (!json_object_object_get_ex(r, "error", &error)) {
61 printf("An unknkown error occurred"); 62 printf("An unknown error occurred");
62 } else { 63 } else {
63 printf("Error: %s\n", json_object_get_string(error)); 64 printf("Error: %s\n", json_object_get_string(error));
64 } 65 }
@@ -183,12 +184,14 @@ static void pretty_print_seat(json_object *i) {
183} 184}
184 185
185static void pretty_print_output(json_object *o) { 186static void pretty_print_output(json_object *o) {
186 json_object *name, *rect, *focused, *active, *ws, *current_mode; 187 json_object *name, *rect, *focused, *active, *power, *ws, *current_mode, *non_desktop;
187 json_object_object_get_ex(o, "name", &name); 188 json_object_object_get_ex(o, "name", &name);
188 json_object_object_get_ex(o, "rect", &rect); 189 json_object_object_get_ex(o, "rect", &rect);
189 json_object_object_get_ex(o, "focused", &focused); 190 json_object_object_get_ex(o, "focused", &focused);
190 json_object_object_get_ex(o, "active", &active); 191 json_object_object_get_ex(o, "active", &active);
192 json_object_object_get_ex(o, "power", &power);
191 json_object_object_get_ex(o, "current_workspace", &ws); 193 json_object_object_get_ex(o, "current_workspace", &ws);
194 json_object_object_get_ex(o, "non_desktop", &non_desktop);
192 json_object *make, *model, *serial, *scale, *scale_filter, *subpixel, 195 json_object *make, *model, *serial, *scale, *scale_filter, *subpixel,
193 *transform, *max_render_time, *adaptive_sync_status; 196 *transform, *max_render_time, *adaptive_sync_status;
194 json_object_object_get_ex(o, "make", &make); 197 json_object_object_get_ex(o, "make", &make);
@@ -211,10 +214,19 @@ static void pretty_print_output(json_object *o) {
211 json_object_object_get_ex(current_mode, "height", &height); 214 json_object_object_get_ex(current_mode, "height", &height);
212 json_object_object_get_ex(current_mode, "refresh", &refresh); 215 json_object_object_get_ex(current_mode, "refresh", &refresh);
213 216
214 if (json_object_get_boolean(active)) { 217 if (json_object_get_boolean(non_desktop)) {
218 printf(
219 "Output %s '%s %s %s' (non-desktop)\n",
220 json_object_get_string(name),
221 json_object_get_string(make),
222 json_object_get_string(model),
223 json_object_get_string(serial)
224 );
225 } else if (json_object_get_boolean(active)) {
215 printf( 226 printf(
216 "Output %s '%s %s %s'%s\n" 227 "Output %s '%s %s %s'%s\n"
217 " Current mode: %dx%d @ %.3f Hz\n" 228 " Current mode: %dx%d @ %.3f Hz\n"
229 " Power: %s\n"
218 " Position: %d,%d\n" 230 " Position: %d,%d\n"
219 " Scale factor: %f\n" 231 " Scale factor: %f\n"
220 " Scale filter: %s\n" 232 " Scale filter: %s\n"
@@ -229,6 +241,7 @@ static void pretty_print_output(json_object *o) {
229 json_object_get_int(width), 241 json_object_get_int(width),
230 json_object_get_int(height), 242 json_object_get_int(height),
231 (double)json_object_get_int(refresh) / 1000, 243 (double)json_object_get_int(refresh) / 1000,
244 json_object_get_boolean(power) ? "on" : "off",
232 json_object_get_int(x), json_object_get_int(y), 245 json_object_get_int(x), json_object_get_int(y),
233 json_object_get_double(scale), 246 json_object_get_double(scale),
234 json_object_get_string(scale_filter), 247 json_object_get_string(scale_filter),
@@ -245,7 +258,7 @@ static void pretty_print_output(json_object *o) {
245 json_object_get_string(adaptive_sync_status)); 258 json_object_get_string(adaptive_sync_status));
246 } else { 259 } else {
247 printf( 260 printf(
248 "Output %s '%s %s %s' (inactive)\n", 261 "Output %s '%s %s %s' (disabled)\n",
249 json_object_get_string(name), 262 json_object_get_string(name),
250 json_object_get_string(make), 263 json_object_get_string(make),
251 json_object_get_string(model), 264 json_object_get_string(model),
@@ -260,14 +273,22 @@ static void pretty_print_output(json_object *o) {
260 for (size_t i = 0; i < modes_len; ++i) { 273 for (size_t i = 0; i < modes_len; ++i) {
261 json_object *mode = json_object_array_get_idx(modes, i); 274 json_object *mode = json_object_array_get_idx(modes, i);
262 275
263 json_object *mode_width, *mode_height, *mode_refresh; 276 json_object *mode_width, *mode_height, *mode_refresh,
277 *mode_picture_aspect_ratio;
264 json_object_object_get_ex(mode, "width", &mode_width); 278 json_object_object_get_ex(mode, "width", &mode_width);
265 json_object_object_get_ex(mode, "height", &mode_height); 279 json_object_object_get_ex(mode, "height", &mode_height);
266 json_object_object_get_ex(mode, "refresh", &mode_refresh); 280 json_object_object_get_ex(mode, "refresh", &mode_refresh);
281 json_object_object_get_ex(mode, "picture_aspect_ratio",
282 &mode_picture_aspect_ratio);
267 283
268 printf(" %dx%d @ %.3f Hz\n", json_object_get_int(mode_width), 284 printf(" %dx%d @ %.3f Hz", json_object_get_int(mode_width),
269 json_object_get_int(mode_height), 285 json_object_get_int(mode_height),
270 (double)json_object_get_int(mode_refresh) / 1000); 286 (double)json_object_get_int(mode_refresh) / 1000);
287 if (mode_picture_aspect_ratio &&
288 strcmp("none", json_object_get_string(mode_picture_aspect_ratio)) != 0) {
289 printf(" (%s)", json_object_get_string(mode_picture_aspect_ratio));
290 }
291 printf("\n");
271 } 292 }
272 } 293 }
273 294
@@ -286,28 +307,83 @@ static void pretty_print_config(json_object *c) {
286 printf("%s\n", json_object_get_string(config)); 307 printf("%s\n", json_object_get_string(config));
287} 308}
288 309
289static void pretty_print(int type, json_object *resp) { 310static void pretty_print_tree(json_object *obj, int indent) {
290 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES && 311 for (int i = 0; i < indent; i++) {
291 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS && 312 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 } 313 }
298 314
299 if (type == IPC_SEND_TICK) { 315 int id = json_object_get_int(json_object_object_get(obj, "id"));
300 return; 316 const char *name = json_object_get_string(json_object_object_get(obj, "name"));
317 const char *type = json_object_get_string(json_object_object_get(obj, "type"));
318 const char *shell = json_object_get_string(json_object_object_get(obj, "shell"));
319
320 printf("#%d: %s \"%s\"", id, type, name);
321
322 if (shell != NULL) {
323 int pid = json_object_get_int(json_object_object_get(obj, "pid"));
324 const char *app_id = json_object_get_string(json_object_object_get(obj, "app_id"));
325 json_object *window_props_obj = json_object_object_get(obj, "window_properties");
326 const char *instance = json_object_get_string(json_object_object_get(window_props_obj, "instance"));
327 const char *class = json_object_get_string(json_object_object_get(window_props_obj, "class"));
328 int x11_id = json_object_get_int(json_object_object_get(obj, "window"));
329
330 printf(" (%s, pid: %d", shell, pid);
331 if (app_id != NULL) {
332 printf(", app_id: \"%s\"", app_id);
333 }
334 if (instance != NULL) {
335 printf(", instance: \"%s\"", instance);
336 }
337 if (class != NULL) {
338 printf(", class: \"%s\"", class);
339 }
340 if (x11_id != 0) {
341 printf(", X11 window: 0x%X", x11_id);
342 }
343 printf(")");
301 } 344 }
302 345
303 if (type == IPC_GET_VERSION) { 346 printf("\n");
304 pretty_print_version(resp); 347
305 return; 348 json_object *nodes_obj = json_object_object_get(obj, "nodes");
349 size_t len = json_object_array_length(nodes_obj);
350 for (size_t i = 0; i < len; i++) {
351 pretty_print_tree(json_object_array_get_idx(nodes_obj, i), indent + 1);
306 } 352 }
307 353
308 if (type == IPC_GET_CONFIG) { 354 json_object *floating_nodes_obj;
355 json_bool floating_nodes = json_object_object_get_ex(obj, "floating_nodes", &floating_nodes_obj);
356 if (floating_nodes) {
357 size_t len = json_object_array_length(floating_nodes_obj);
358 for (size_t i = 0; i < len; i++) {
359 pretty_print_tree(json_object_array_get_idx(floating_nodes_obj, i), indent + 1);
360 }
361 }
362}
363
364static void pretty_print(int type, json_object *resp) {
365 switch (type) {
366 case IPC_SEND_TICK:
367 return;
368 case IPC_GET_VERSION:
369 pretty_print_version(resp);
370 return;
371 case IPC_GET_CONFIG:
309 pretty_print_config(resp); 372 pretty_print_config(resp);
310 return; 373 return;
374 case IPC_GET_TREE:
375 pretty_print_tree(resp, 0);
376 return;
377 case IPC_COMMAND:
378 case IPC_GET_WORKSPACES:
379 case IPC_GET_INPUTS:
380 case IPC_GET_OUTPUTS:
381 case IPC_GET_SEATS:
382 break;
383 default:
384 printf("%s\n", json_object_to_json_string_ext(resp,
385 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
386 return;
311 } 387 }
312 388
313 json_object *obj; 389 json_object *obj;
@@ -343,7 +419,7 @@ int main(int argc, char **argv) {
343 419
344 sway_log_init(SWAY_INFO, NULL); 420 sway_log_init(SWAY_INFO, NULL);
345 421
346 static struct option long_options[] = { 422 static const struct option long_options[] = {
347 {"help", no_argument, NULL, 'h'}, 423 {"help", no_argument, NULL, 'h'},
348 {"monitor", no_argument, NULL, 'm'}, 424 {"monitor", no_argument, NULL, 'm'},
349 {"pretty", no_argument, NULL, 'p'}, 425 {"pretty", no_argument, NULL, 'p'},
@@ -480,12 +556,20 @@ int main(int argc, char **argv) {
480 char *resp = ipc_single_command(socketfd, type, command, &len); 556 char *resp = ipc_single_command(socketfd, type, command, &len);
481 557
482 // pretty print the json 558 // pretty print the json
483 json_object *obj = json_tokener_parse(resp); 559 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
484 if (obj == NULL) { 560 if (tok == NULL) {
561 if (quiet) {
562 exit(EXIT_FAILURE);
563 }
564 sway_abort("failed allocating json_tokener");
565 }
566 json_object *obj = json_tokener_parse_ex(tok, resp, -1);
567 enum json_tokener_error err = json_tokener_get_error(tok);
568 json_tokener_free(tok);
569 if (obj == NULL || err != json_tokener_success) {
485 if (!quiet) { 570 if (!quiet) {
486 fprintf(stderr, "ERROR: Could not parse json response from ipc. " 571 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
487 "This is a bug in sway."); 572 json_tokener_error_desc(err));
488 printf("%s\n", resp);
489 } 573 }
490 ret = 1; 574 ret = 1;
491 } else { 575 } else {
@@ -517,13 +601,22 @@ int main(int argc, char **argv) {
517 break; 601 break;
518 } 602 }
519 603
520 json_object *obj = json_tokener_parse(reply->payload); 604 json_tokener *tok = json_tokener_new_ex(JSON_MAX_DEPTH);
521 if (obj == NULL) { 605 if (tok == NULL) {
606 if (quiet) {
607 exit(EXIT_FAILURE);
608 }
609 sway_abort("failed allocating json_tokener");
610 }
611 json_object *obj = json_tokener_parse_ex(tok, reply->payload, -1);
612 enum json_tokener_error err = json_tokener_get_error(tok);
613 json_tokener_free(tok);
614 if (obj == NULL || err != json_tokener_success) {
522 if (!quiet) { 615 if (!quiet) {
523 fprintf(stderr, "ERROR: Could not parse json response from" 616 sway_log(SWAY_ERROR, "failed to parse payload as json: %s",
524 " ipc. This is a bug in sway."); 617 json_tokener_error_desc(err));
525 ret = 1;
526 } 618 }
619 ret = 1;
527 break; 620 break;
528 } else if (quiet) { 621 } else if (quiet) {
529 json_object_put(obj); 622 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..efd71ce7 100644
--- a/swaynag/config.c
+++ b/swaynag/config.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <getopt.h> 1#include <getopt.h>
3#include <stdio.h> 2#include <stdio.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -11,24 +10,40 @@
11#include "util.h" 10#include "util.h"
12#include "wlr-layer-shell-unstable-v1-client-protocol.h" 11#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13 12
14static char *read_from_stdin(void) { 13static char *read_and_trim_stdin(void) {
15 char *buffer = NULL; 14 char *buffer = NULL, *line = NULL;
16 size_t buffer_len = 0; 15 size_t buffer_len = 0, line_size = 0;
17 char *line = NULL; 16 while (1) {
18 size_t line_size = 0; 17 ssize_t nread = getline(&line, &line_size, stdin);
19 ssize_t nread; 18 if (nread == -1) {
20 while ((nread = getline(&line, &line_size, stdin)) != -1) { 19 if (feof(stdin)) {
20 break;
21 } else {
22 perror("getline");
23 goto freeline;
24 }
25 }
21 buffer = realloc(buffer, buffer_len + nread + 1); 26 buffer = realloc(buffer, buffer_len + nread + 1);
22 snprintf(&buffer[buffer_len], nread + 1, "%s", line); 27 if (!buffer) {
28 perror("realloc");
29 goto freebuf;
30 }
31 memcpy(&buffer[buffer_len], line, nread + 1);
23 buffer_len += nread; 32 buffer_len += nread;
24 } 33 }
25 free(line); 34 free(line);
26 35
27 while (buffer && buffer[buffer_len - 1] == '\n') { 36 while (buffer_len && buffer[buffer_len - 1] == '\n') {
28 buffer[--buffer_len] = '\0'; 37 buffer[--buffer_len] = '\0';
29 } 38 }
30 39
31 return buffer; 40 return buffer;
41
42freeline:
43 free(line);
44freebuf:
45 free(buffer);
46 return NULL;
32} 47}
33 48
34int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag, 49int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
@@ -51,7 +66,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
51 TO_PADDING_BTN, 66 TO_PADDING_BTN,
52 }; 67 };
53 68
54 static struct option opts[] = { 69 static const struct option opts[] = {
55 {"button", required_argument, NULL, 'b'}, 70 {"button", required_argument, NULL, 'b'},
56 {"button-no-terminal", required_argument, NULL, 'B'}, 71 {"button-no-terminal", required_argument, NULL, 'B'},
57 {"button-dismiss", required_argument, NULL, 'z'}, 72 {"button-dismiss", required_argument, NULL, 'z'},
@@ -59,6 +74,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
59 {"config", required_argument, NULL, 'c'}, 74 {"config", required_argument, NULL, 'c'},
60 {"debug", no_argument, NULL, 'd'}, 75 {"debug", no_argument, NULL, 'd'},
61 {"edge", required_argument, NULL, 'e'}, 76 {"edge", required_argument, NULL, 'e'},
77 {"layer", required_argument, NULL, 'y'},
62 {"font", required_argument, NULL, 'f'}, 78 {"font", required_argument, NULL, 'f'},
63 {"help", no_argument, NULL, 'h'}, 79 {"help", no_argument, NULL, 'h'},
64 {"detailed-message", no_argument, NULL, 'l'}, 80 {"detailed-message", no_argument, NULL, 'l'},
@@ -104,6 +120,8 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
104 " -c, --config <path> Path to config file.\n" 120 " -c, --config <path> Path to config file.\n"
105 " -d, --debug Enable debugging.\n" 121 " -d, --debug Enable debugging.\n"
106 " -e, --edge top|bottom Set the edge to use.\n" 122 " -e, --edge top|bottom Set the edge to use.\n"
123 " -y, --layer overlay|top|bottom|background\n"
124 " Set the layer to use.\n"
107 " -f, --font <font> Set the font to use.\n" 125 " -f, --font <font> Set the font to use.\n"
108 " -h, --help Show help message and quit.\n" 126 " -h, --help Show help message and quit.\n"
109 " -l, --detailed-message Read a detailed message from stdin.\n" 127 " -l, --detailed-message Read a detailed message from stdin.\n"
@@ -133,7 +151,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
133 151
134 optind = 1; 152 optind = 1;
135 while (1) { 153 while (1) {
136 int c = getopt_long(argc, argv, "b:B:z:Z:c:de:f:hlL:m:o:s:t:v", opts, NULL); 154 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) { 155 if (c == -1) {
138 break; 156 break;
139 } 157 }
@@ -147,8 +165,11 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
147 fprintf(stderr, "Missing action for button %s\n", optarg); 165 fprintf(stderr, "Missing action for button %s\n", optarg);
148 return EXIT_FAILURE; 166 return EXIT_FAILURE;
149 } 167 }
150 struct swaynag_button *button; 168 struct swaynag_button *button = calloc(1, sizeof(struct swaynag_button));
151 button = calloc(sizeof(struct swaynag_button), 1); 169 if (!button) {
170 perror("calloc");
171 return EXIT_FAILURE;
172 }
152 button->text = strdup(optarg); 173 button->text = strdup(optarg);
153 button->type = SWAYNAG_ACTION_COMMAND; 174 button->type = SWAYNAG_ACTION_COMMAND;
154 button->action = strdup(argv[optind]); 175 button->action = strdup(argv[optind]);
@@ -184,24 +205,45 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
184 } 205 }
185 } 206 }
186 break; 207 break;
208 case 'y': // Layer
209 if (type) {
210 if (strcmp(optarg, "background") == 0) {
211 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
212 } else if (strcmp(optarg, "bottom") == 0) {
213 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
214 } else if (strcmp(optarg, "top") == 0) {
215 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
216 } else if (strcmp(optarg, "overlay") == 0) {
217 type->layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
218 } else {
219 fprintf(stderr, "Invalid layer: %s\n"
220 "Usage: --layer overlay|top|bottom|background\n",
221 optarg);
222 return EXIT_FAILURE;
223 }
224 }
225 break;
187 case 'f': // Font 226 case 'f': // Font
188 if (type) { 227 if (type) {
189 free(type->font); 228 pango_font_description_free(type->font_description);
190 type->font = strdup(optarg); 229 type->font_description = pango_font_description_from_string(optarg);
191 } 230 }
192 break; 231 break;
193 case 'l': // Detailed Message 232 case 'l': // Detailed Message
194 if (swaynag) { 233 if (swaynag) {
195 free(swaynag->details.message); 234 free(swaynag->details.message);
196 swaynag->details.message = read_from_stdin(); 235 swaynag->details.message = read_and_trim_stdin();
236 if (!swaynag->details.message) {
237 return EXIT_FAILURE;
238 }
197 swaynag->details.button_up.text = strdup("â–²"); 239 swaynag->details.button_up.text = strdup("â–²");
198 swaynag->details.button_down.text = strdup("â–¼"); 240 swaynag->details.button_down.text = strdup("â–¼");
199 } 241 }
200 break; 242 break;
201 case 'L': // Detailed Button Text 243 case 'L': // Detailed Button Text
202 if (swaynag) { 244 if (swaynag) {
203 free(swaynag->details.button_details->text); 245 free(swaynag->details.details_text);
204 swaynag->details.button_details->text = strdup(optarg); 246 swaynag->details.details_text = strdup(optarg);
205 } 247 }
206 break; 248 break;
207 case 'm': // Message 249 case 'm': // Message
@@ -218,8 +260,7 @@ int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
218 break; 260 break;
219 case 's': // Dismiss Button Text 261 case 's': // Dismiss Button Text
220 if (swaynag) { 262 if (swaynag) {
221 struct swaynag_button *button_close; 263 struct swaynag_button *button_close = swaynag->buttons->items[0];
222 button_close = swaynag->buttons->items[0];
223 free(button_close->text); 264 free(button_close->text);
224 button_close->text = strdup(optarg); 265 button_close->text = strdup(optarg);
225 } 266 }
@@ -378,23 +419,24 @@ int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) {
378 419
379 if (line[0] == '[') { 420 if (line[0] == '[') {
380 char *close = strchr(line, ']'); 421 char *close = strchr(line, ']');
381 if (!close) { 422 if (!close || close != &line[nread - 2] || nread <= 3) {
382 fprintf(stderr, "Closing bracket not found on line %d\n", 423 fprintf(stderr, "Line %d is malformed\n", line_number);
383 line_number);
384 result = 1; 424 result = 1;
385 break; 425 break;
386 } 426 }
387 char *name = calloc(1, close - line); 427 *close = '\0';
388 strncat(name, line + 1, close - line - 1); 428 type = swaynag_type_get(types, &line[1]);
389 type = swaynag_type_get(types, name);
390 if (!type) { 429 if (!type) {
391 type = swaynag_type_new(name); 430 type = swaynag_type_new(&line[1]);
392 list_add(types, type); 431 list_add(types, type);
393 } 432 }
394 free(name);
395 } else { 433 } else {
396 char *flag = malloc(sizeof(char) * (nread + 3)); 434 char *flag = malloc(nread + 3);
397 sprintf(flag, "--%s", line); 435 if (!flag) {
436 perror("calloc");
437 return EXIT_FAILURE;
438 }
439 snprintf(flag, nread + 3, "--%s", line);
398 char *argv[] = {"swaynag", flag}; 440 char *argv[] = {"swaynag", flag};
399 result = swaynag_parse_options(2, argv, swaynag, types, type, 441 result = swaynag_parse_options(2, argv, swaynag, types, type,
400 NULL, NULL); 442 NULL, NULL);
diff --git a/swaynag/main.c b/swaynag/main.c
index 88007818..634bddbf 100644
--- a/swaynag/main.c
+++ b/swaynag/main.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <signal.h> 2#include <signal.h>
4#include "log.h" 3#include "log.h"
@@ -20,33 +19,27 @@ void sway_terminate(int code) {
20} 19}
21 20
22int main(int argc, char **argv) { 21int main(int argc, char **argv) {
23 int exit_code = EXIT_SUCCESS; 22 int status = EXIT_SUCCESS;
24 23
25 list_t *types = create_list(); 24 list_t *types = create_list();
26 swaynag_types_add_default(types); 25 swaynag_types_add_default(types);
27 26
28 memset(&swaynag, 0, sizeof(swaynag));
29 swaynag.buttons = create_list(); 27 swaynag.buttons = create_list();
30 wl_list_init(&swaynag.outputs); 28 wl_list_init(&swaynag.outputs);
31 wl_list_init(&swaynag.seats); 29 wl_list_init(&swaynag.seats);
32 30
33 struct swaynag_button *button_close = 31 struct swaynag_button *button_close = calloc(1, sizeof(struct swaynag_button));
34 calloc(sizeof(struct swaynag_button), 1);
35 button_close->text = strdup("X"); 32 button_close->text = strdup("X");
36 button_close->type = SWAYNAG_ACTION_DISMISS; 33 button_close->type = SWAYNAG_ACTION_DISMISS;
37 list_add(swaynag.buttons, button_close); 34 list_add(swaynag.buttons, button_close);
38 35
39 swaynag.details.button_details = 36 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 37
44 char *config_path = NULL; 38 char *config_path = NULL;
45 bool debug = false; 39 bool debug = false;
46 int launch_status = swaynag_parse_options(argc, argv, NULL, NULL, NULL, 40 status = swaynag_parse_options(argc, argv, NULL, NULL, NULL,
47 &config_path, &debug); 41 &config_path, &debug);
48 if (launch_status != 0) { 42 if (status != 0) {
49 exit_code = launch_status;
50 goto cleanup; 43 goto cleanup;
51 } 44 }
52 sway_log_init(debug ? SWAY_DEBUG : SWAY_ERROR, NULL); 45 sway_log_init(debug ? SWAY_DEBUG : SWAY_ERROR, NULL);
@@ -56,29 +49,27 @@ int main(int argc, char **argv) {
56 } 49 }
57 if (config_path) { 50 if (config_path) {
58 sway_log(SWAY_DEBUG, "Loading config file: %s", config_path); 51 sway_log(SWAY_DEBUG, "Loading config file: %s", config_path);
59 int config_status = swaynag_load_config(config_path, &swaynag, types); 52 status = swaynag_load_config(config_path, &swaynag, types);
60 free(config_path); 53 if (status != 0) {
61 if (config_status != 0) {
62 exit_code = config_status;
63 goto cleanup; 54 goto cleanup;
64 } 55 }
65 } 56 }
66 57
58
67 if (argc > 1) { 59 if (argc > 1) {
68 struct swaynag_type *type_args = swaynag_type_new("<args>"); 60 struct swaynag_type *type_args = swaynag_type_new("<args>");
69 list_add(types, type_args); 61 list_add(types, type_args);
70 62
71 int result = swaynag_parse_options(argc, argv, &swaynag, types, 63 status = swaynag_parse_options(argc, argv, &swaynag, types,
72 type_args, NULL, NULL); 64 type_args, NULL, NULL);
73 if (result != 0) { 65 if (status != 0) {
74 exit_code = result;
75 goto cleanup; 66 goto cleanup;
76 } 67 }
77 } 68 }
78 69
79 if (!swaynag.message) { 70 if (!swaynag.message) {
80 sway_log(SWAY_ERROR, "No message passed. Please provide --message/-m"); 71 sway_log(SWAY_ERROR, "No message passed. Please provide --message/-m");
81 exit_code = EXIT_FAILURE; 72 status = EXIT_FAILURE;
82 goto cleanup; 73 goto cleanup;
83 } 74 }
84 75
@@ -96,20 +87,20 @@ int main(int argc, char **argv) {
96 swaynag_type_merge(type, swaynag_type_get(types, "<args>")); 87 swaynag_type_merge(type, swaynag_type_get(types, "<args>"));
97 swaynag.type = type; 88 swaynag.type = type;
98 89
99 swaynag_types_free(types);
100
101 if (swaynag.details.message) { 90 if (swaynag.details.message) {
91 swaynag.details.button_details = calloc(1, sizeof(struct swaynag_button));
92 swaynag.details.button_details->text = strdup(swaynag.details.details_text);
93 swaynag.details.button_details->type = SWAYNAG_ACTION_EXPAND;
102 list_add(swaynag.buttons, swaynag.details.button_details); 94 list_add(swaynag.buttons, swaynag.details.button_details);
103 } else {
104 free(swaynag.details.button_details->text);
105 free(swaynag.details.button_details);
106 } 95 }
107 96
108 sway_log(SWAY_DEBUG, "Output: %s", swaynag.type->output); 97 sway_log(SWAY_DEBUG, "Output: %s", swaynag.type->output);
109 sway_log(SWAY_DEBUG, "Anchors: %" PRIu32, swaynag.type->anchors); 98 sway_log(SWAY_DEBUG, "Anchors: %" PRIu32, swaynag.type->anchors);
110 sway_log(SWAY_DEBUG, "Type: %s", swaynag.type->name); 99 sway_log(SWAY_DEBUG, "Type: %s", swaynag.type->name);
111 sway_log(SWAY_DEBUG, "Message: %s", swaynag.message); 100 sway_log(SWAY_DEBUG, "Message: %s", swaynag.message);
112 sway_log(SWAY_DEBUG, "Font: %s", swaynag.type->font); 101 char *font = pango_font_description_to_string(swaynag.type->font_description);
102 sway_log(SWAY_DEBUG, "Font: %s", font);
103 free(font);
113 sway_log(SWAY_DEBUG, "Buttons"); 104 sway_log(SWAY_DEBUG, "Buttons");
114 for (int i = 0; i < swaynag.buttons->length; i++) { 105 for (int i = 0; i < swaynag.buttons->length; i++) {
115 struct swaynag_button *button = swaynag.buttons->items[i]; 106 struct swaynag_button *button = swaynag.buttons->items[i];
@@ -120,12 +111,9 @@ int main(int argc, char **argv) {
120 111
121 swaynag_setup(&swaynag); 112 swaynag_setup(&swaynag);
122 swaynag_run(&swaynag); 113 swaynag_run(&swaynag);
123 return exit_code;
124 114
125cleanup: 115cleanup:
126 swaynag_types_free(types); 116 swaynag_types_free(types);
127 free(swaynag.details.button_details->text);
128 free(swaynag.details.button_details);
129 swaynag_destroy(&swaynag); 117 swaynag_destroy(&swaynag);
130 return exit_code; 118 return status;
131} 119}
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..50eea148 100644
--- a/swaynag/swaynag.c
+++ b/swaynag/swaynag.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdlib.h> 1#include <stdlib.h>
3#include <assert.h> 2#include <assert.h>
4#include <sys/stat.h> 3#include <sys/stat.h>
@@ -28,10 +27,15 @@ static bool terminal_execute(char *terminal, char *command) {
28 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command); 27 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command);
29 fclose(tmp); 28 fclose(tmp);
30 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR); 29 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR);
31 char *cmd = malloc(sizeof(char) * (strlen(terminal) + strlen(" -e ") + strlen(fname) + 1)); 30 size_t cmd_size = strlen(terminal) + strlen(" -e ") + strlen(fname) + 1;
32 sprintf(cmd, "%s -e %s", terminal, fname); 31 char *cmd = malloc(cmd_size);
33 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); 32 if (!cmd) {
34 sway_log_errno(SWAY_ERROR, "Failed to run command, execl() returned."); 33 perror("malloc");
34 return false;
35 }
36 snprintf(cmd, cmd_size, "%s -e %s", terminal, fname);
37 execlp("sh", "sh", "-c", cmd, NULL);
38 sway_log_errno(SWAY_ERROR, "Failed to run command, execlp() returned.");
35 free(cmd); 39 free(cmd);
36 return false; 40 return false;
37} 41}
@@ -58,7 +62,7 @@ static void swaynag_button_execute(struct swaynag *swaynag,
58 } else if (pid == 0) { 62 } else if (pid == 0) {
59 // Child of the child. Will be reparented to the init process 63 // Child of the child. Will be reparented to the init process
60 char *terminal = getenv("TERMINAL"); 64 char *terminal = getenv("TERMINAL");
61 if (button->terminal && terminal && strlen(terminal)) { 65 if (button->terminal && terminal && *terminal) {
62 sway_log(SWAY_DEBUG, "Found $TERMINAL: %s", terminal); 66 sway_log(SWAY_DEBUG, "Found $TERMINAL: %s", terminal);
63 if (!terminal_execute(terminal, button->action)) { 67 if (!terminal_execute(terminal, button->action)) {
64 swaynag_destroy(swaynag); 68 swaynag_destroy(swaynag);
@@ -69,8 +73,8 @@ static void swaynag_button_execute(struct swaynag *swaynag,
69 sway_log(SWAY_DEBUG, 73 sway_log(SWAY_DEBUG,
70 "$TERMINAL not found. Running directly"); 74 "$TERMINAL not found. Running directly");
71 } 75 }
72 execl("/bin/sh", "/bin/sh", "-c", button->action, NULL); 76 execlp("sh", "sh", "-c", button->action, NULL);
73 sway_log_errno(SWAY_DEBUG, "execl failed"); 77 sway_log_errno(SWAY_DEBUG, "execlp failed");
74 _exit(EXIT_FAILURE); 78 _exit(EXIT_FAILURE);
75 } 79 }
76 } 80 }
@@ -103,7 +107,7 @@ static void layer_surface_closed(void *data,
103 swaynag_destroy(swaynag); 107 swaynag_destroy(swaynag);
104} 108}
105 109
106static struct zwlr_layer_surface_v1_listener layer_surface_listener = { 110static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
107 .configure = layer_surface_configure, 111 .configure = layer_surface_configure,
108 .closed = layer_surface_closed, 112 .closed = layer_surface_closed,
109}; 113};
@@ -124,7 +128,7 @@ static void surface_enter(void *data, struct wl_surface *surface,
124 }; 128 };
125} 129}
126 130
127static struct wl_surface_listener surface_listener = { 131static const struct wl_surface_listener surface_listener = {
128 .enter = surface_enter, 132 .enter = surface_enter,
129 .leave = nop, 133 .leave = nop,
130}; 134};
@@ -138,7 +142,7 @@ static void update_cursor(struct swaynag_seat *seat) {
138 const char *cursor_theme = getenv("XCURSOR_THEME"); 142 const char *cursor_theme = getenv("XCURSOR_THEME");
139 unsigned cursor_size = 24; 143 unsigned cursor_size = 24;
140 const char *env_cursor_size = getenv("XCURSOR_SIZE"); 144 const char *env_cursor_size = getenv("XCURSOR_SIZE");
141 if (env_cursor_size && strlen(env_cursor_size) > 0) { 145 if (env_cursor_size && *env_cursor_size) {
142 errno = 0; 146 errno = 0;
143 char *end; 147 char *end;
144 unsigned size = strtoul(env_cursor_size, &end, 10); 148 unsigned size = strtoul(env_cursor_size, &end, 10);
@@ -148,8 +152,15 @@ static void update_cursor(struct swaynag_seat *seat) {
148 } 152 }
149 pointer->cursor_theme = wl_cursor_theme_load( 153 pointer->cursor_theme = wl_cursor_theme_load(
150 cursor_theme, cursor_size * swaynag->scale, swaynag->shm); 154 cursor_theme, cursor_size * swaynag->scale, swaynag->shm);
151 struct wl_cursor *cursor = 155 if (!pointer->cursor_theme) {
152 wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr"); 156 sway_log(SWAY_ERROR, "Failed to load cursor theme");
157 return;
158 }
159 struct wl_cursor *cursor = wl_cursor_theme_get_cursor(pointer->cursor_theme, "default");
160 if (!cursor) {
161 sway_log(SWAY_ERROR, "Failed to get default cursor from theme");
162 return;
163 }
153 pointer->cursor_image = cursor->images[0]; 164 pointer->cursor_image = cursor->images[0];
154 wl_surface_set_buffer_scale(pointer->cursor_surface, 165 wl_surface_set_buffer_scale(pointer->cursor_surface,
155 swaynag->scale); 166 swaynag->scale);
@@ -177,9 +188,22 @@ static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
177 uint32_t serial, struct wl_surface *surface, 188 uint32_t serial, struct wl_surface *surface,
178 wl_fixed_t surface_x, wl_fixed_t surface_y) { 189 wl_fixed_t surface_x, wl_fixed_t surface_y) {
179 struct swaynag_seat *seat = data; 190 struct swaynag_seat *seat = data;
191
180 struct swaynag_pointer *pointer = &seat->pointer; 192 struct swaynag_pointer *pointer = &seat->pointer;
181 pointer->serial = serial; 193 pointer->x = wl_fixed_to_int(surface_x);
182 update_cursor(seat); 194 pointer->y = wl_fixed_to_int(surface_y);
195
196 if (seat->swaynag->cursor_shape_manager) {
197 struct wp_cursor_shape_device_v1 *device =
198 wp_cursor_shape_manager_v1_get_pointer(
199 seat->swaynag->cursor_shape_manager, wl_pointer);
200 wp_cursor_shape_device_v1_set_shape(device, serial,
201 WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
202 wp_cursor_shape_device_v1_destroy(device);
203 } else {
204 pointer->serial = serial;
205 update_cursor(seat);
206 }
183} 207}
184 208
185static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer, 209static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
@@ -198,8 +222,8 @@ static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
198 return; 222 return;
199 } 223 }
200 224
201 double x = seat->pointer.x * swaynag->scale; 225 double x = seat->pointer.x;
202 double y = seat->pointer.y * swaynag->scale; 226 double y = seat->pointer.y;
203 for (int i = 0; i < swaynag->buttons->length; i++) { 227 for (int i = 0; i < swaynag->buttons->length; i++) {
204 struct swaynag_button *nagbutton = swaynag->buttons->items[i]; 228 struct swaynag_button *nagbutton = swaynag->buttons->items[i];
205 if (x >= nagbutton->x 229 if (x >= nagbutton->x
@@ -263,7 +287,7 @@ static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
263 render_frame(swaynag); 287 render_frame(swaynag);
264} 288}
265 289
266static struct wl_pointer_listener pointer_listener = { 290static const struct wl_pointer_listener pointer_listener = {
267 .enter = wl_pointer_enter, 291 .enter = wl_pointer_enter,
268 .leave = nop, 292 .leave = nop,
269 .motion = wl_pointer_motion, 293 .motion = wl_pointer_motion,
@@ -289,7 +313,7 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
289 } 313 }
290} 314}
291 315
292const struct wl_seat_listener seat_listener = { 316static const struct wl_seat_listener seat_listener = {
293 .capabilities = seat_handle_capabilities, 317 .capabilities = seat_handle_capabilities,
294 .name = nop, 318 .name = nop,
295}; 319};
@@ -305,33 +329,25 @@ static void output_scale(void *data, struct wl_output *output,
305 } 329 }
306} 330}
307 331
308static struct wl_output_listener output_listener = { 332static void output_name(void *data, struct wl_output *output,
309 .geometry = nop, 333 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; 334 struct swaynag_output *swaynag_output = data;
318 char *outname = swaynag_output->swaynag->type->output; 335 swaynag_output->name = strdup(name);
319 sway_log(SWAY_DEBUG, "Checking against output %s for %s", name, outname); 336
320 if (!swaynag_output->swaynag->output && outname && name 337 const char *outname = swaynag_output->swaynag->type->output;
321 && strcmp(outname, name) == 0) { 338 if (!swaynag_output->swaynag->output && outname &&
339 strcmp(outname, name) == 0) {
322 sway_log(SWAY_DEBUG, "Using output %s", name); 340 sway_log(SWAY_DEBUG, "Using output %s", name);
323 swaynag_output->swaynag->output = swaynag_output; 341 swaynag_output->swaynag->output = swaynag_output;
324 } 342 }
325 swaynag_output->name = strdup(name);
326 zxdg_output_v1_destroy(xdg_output);
327 swaynag_output->swaynag->querying_outputs--;
328} 343}
329 344
330static struct zxdg_output_v1_listener xdg_output_listener = { 345static const struct wl_output_listener output_listener = {
331 .logical_position = nop, 346 .geometry = nop,
332 .logical_size = nop, 347 .mode = nop,
333 .done = nop, 348 .done = nop,
334 .name = xdg_output_handle_name, 349 .scale = output_scale,
350 .name = output_name,
335 .description = nop, 351 .description = nop,
336}; 352};
337 353
@@ -345,6 +361,7 @@ static void handle_global(void *data, struct wl_registry *registry,
345 struct swaynag_seat *seat = 361 struct swaynag_seat *seat =
346 calloc(1, sizeof(struct swaynag_seat)); 362 calloc(1, sizeof(struct swaynag_seat));
347 if (!seat) { 363 if (!seat) {
364 perror("calloc");
348 return; 365 return;
349 } 366 }
350 367
@@ -359,33 +376,28 @@ static void handle_global(void *data, struct wl_registry *registry,
359 } else if (strcmp(interface, wl_shm_interface.name) == 0) { 376 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
360 swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); 377 swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
361 } else if (strcmp(interface, wl_output_interface.name) == 0) { 378 } else if (strcmp(interface, wl_output_interface.name) == 0) {
362 if (!swaynag->output && swaynag->xdg_output_manager) { 379 if (!swaynag->output) {
363 swaynag->querying_outputs++;
364 struct swaynag_output *output = 380 struct swaynag_output *output =
365 calloc(1, sizeof(struct swaynag_output)); 381 calloc(1, sizeof(struct swaynag_output));
382 if (!output) {
383 perror("calloc");
384 return;
385 }
366 output->wl_output = wl_registry_bind(registry, name, 386 output->wl_output = wl_registry_bind(registry, name,
367 &wl_output_interface, 3); 387 &wl_output_interface, 4);
368 output->wl_name = name; 388 output->wl_name = name;
369 output->scale = 1; 389 output->scale = 1;
370 output->swaynag = swaynag; 390 output->swaynag = swaynag;
371 wl_list_insert(&swaynag->outputs, &output->link); 391 wl_list_insert(&swaynag->outputs, &output->link);
372 wl_output_add_listener(output->wl_output, 392 wl_output_add_listener(output->wl_output,
373 &output_listener, output); 393 &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 } 394 }
381 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { 395 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
382 swaynag->layer_shell = wl_registry_bind( 396 swaynag->layer_shell = wl_registry_bind(
383 registry, name, &zwlr_layer_shell_v1_interface, 1); 397 registry, name, &zwlr_layer_shell_v1_interface, 1);
384 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 398 } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
385 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { 399 swaynag->cursor_shape_manager = wl_registry_bind(
386 swaynag->xdg_output_manager = wl_registry_bind(registry, name, 400 registry, name, &wp_cursor_shape_manager_v1_interface, 1);
387 &zxdg_output_manager_v1_interface,
388 ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
389 } 401 }
390} 402}
391 403
@@ -451,12 +463,11 @@ void swaynag_setup(struct swaynag *swaynag) {
451 463
452 assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm); 464 assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm);
453 465
454 while (swaynag->querying_outputs > 0) { 466 // Second roundtrip to get wl_output properties
455 if (wl_display_roundtrip(swaynag->display) < 0) { 467 if (wl_display_roundtrip(swaynag->display) < 0) {
456 sway_log(SWAY_ERROR, "Error during outputs init."); 468 sway_log(SWAY_ERROR, "Error during outputs init.");
457 swaynag_destroy(swaynag); 469 swaynag_destroy(swaynag);
458 exit(EXIT_FAILURE); 470 exit(EXIT_FAILURE);
459 }
460 } 471 }
461 472
462 if (!swaynag->output && swaynag->type->output) { 473 if (!swaynag->output && swaynag->type->output) {
@@ -465,7 +476,9 @@ void swaynag_setup(struct swaynag *swaynag) {
465 exit(EXIT_FAILURE); 476 exit(EXIT_FAILURE);
466 } 477 }
467 478
468 swaynag_setup_cursors(swaynag); 479 if (!swaynag->cursor_shape_manager) {
480 swaynag_setup_cursors(swaynag);
481 }
469 482
470 swaynag->surface = wl_compositor_create_surface(swaynag->compositor); 483 swaynag->surface = wl_compositor_create_surface(swaynag->compositor);
471 assert(swaynag->surface); 484 assert(swaynag->surface);
@@ -474,7 +487,8 @@ void swaynag_setup(struct swaynag *swaynag) {
474 swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface( 487 swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
475 swaynag->layer_shell, swaynag->surface, 488 swaynag->layer_shell, swaynag->surface,
476 swaynag->output ? swaynag->output->wl_output : NULL, 489 swaynag->output ? swaynag->output->wl_output : NULL,
477 ZWLR_LAYER_SHELL_V1_LAYER_TOP, "swaynag"); 490 swaynag->type->layer,
491 "swaynag");
478 assert(swaynag->layer_surface); 492 assert(swaynag->layer_surface);
479 zwlr_layer_surface_v1_add_listener(swaynag->layer_surface, 493 zwlr_layer_surface_v1_add_listener(swaynag->layer_surface,
480 &layer_surface_listener, swaynag); 494 &layer_surface_listener, swaynag);
@@ -491,10 +505,6 @@ void swaynag_run(struct swaynag *swaynag) {
491 && wl_display_dispatch(swaynag->display) != -1) { 505 && wl_display_dispatch(swaynag->display) != -1) {
492 // This is intentionally left blank 506 // This is intentionally left blank
493 } 507 }
494
495 if (swaynag->display) {
496 wl_display_disconnect(swaynag->display);
497 }
498} 508}
499 509
500void swaynag_destroy(struct swaynag *swaynag) { 510void swaynag_destroy(struct swaynag *swaynag) {
@@ -509,6 +519,7 @@ void swaynag_destroy(struct swaynag *swaynag) {
509 } 519 }
510 list_free(swaynag->buttons); 520 list_free(swaynag->buttons);
511 free(swaynag->details.message); 521 free(swaynag->details.message);
522 free(swaynag->details.details_text);
512 free(swaynag->details.button_up.text); 523 free(swaynag->details.button_up.text);
513 free(swaynag->details.button_down.text); 524 free(swaynag->details.button_down.text);
514 525
@@ -529,13 +540,8 @@ void swaynag_destroy(struct swaynag *swaynag) {
529 swaynag_seat_destroy(seat); 540 swaynag_seat_destroy(seat);
530 } 541 }
531 542
532 if (&swaynag->buffers[0]) { 543 destroy_buffer(&swaynag->buffers[0]);
533 destroy_buffer(&swaynag->buffers[0]); 544 destroy_buffer(&swaynag->buffers[1]);
534 }
535
536 if (&swaynag->buffers[1]) {
537 destroy_buffer(&swaynag->buffers[1]);
538 }
539 545
540 if (swaynag->outputs.prev || swaynag->outputs.next) { 546 if (swaynag->outputs.prev || swaynag->outputs.next) {
541 struct swaynag_output *output, *temp; 547 struct swaynag_output *output, *temp;
@@ -554,4 +560,8 @@ void swaynag_destroy(struct swaynag *swaynag) {
554 if (swaynag->shm) { 560 if (swaynag->shm) {
555 wl_shm_destroy(swaynag->shm); 561 wl_shm_destroy(swaynag->shm);
556 } 562 }
563
564 if (swaynag->display) {
565 wl_display_disconnect(swaynag->display);
566 }
557} 567}
diff --git a/swaynag/types.c b/swaynag/types.c
index fa045532..821e5b21 100644
--- a/swaynag/types.c
+++ b/swaynag/types.c
@@ -1,4 +1,3 @@
1#define _POSIX_C_SOURCE 200809L
2#include <getopt.h> 1#include <getopt.h>
3#include <stdbool.h> 2#include <stdbool.h>
4#include <stdlib.h> 3#include <stdlib.h>
@@ -26,15 +25,18 @@ struct swaynag_type *swaynag_type_new(const char *name) {
26 type->button_gap_close = -1; 25 type->button_gap_close = -1;
27 type->button_margin_right = -1; 26 type->button_margin_right = -1;
28 type->button_padding = -1; 27 type->button_padding = -1;
28 type->layer = -1;
29 return type; 29 return type;
30} 30}
31 31
32void swaynag_types_add_default(list_t *types) { 32void swaynag_types_add_default(list_t *types) {
33 struct swaynag_type *type_defaults = swaynag_type_new("<defaults>"); 33 struct swaynag_type *type_defaults = swaynag_type_new("<defaults>");
34 type_defaults->font = strdup("pango:Monospace 10"); 34 type_defaults->font_description =
35 pango_font_description_from_string("pango:Monospace 10");
35 type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP 36 type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
36 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT 37 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
37 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; 38 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
39 type_defaults->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
38 type_defaults->button_background = 0x333333FF; 40 type_defaults->button_background = 0x333333FF;
39 type_defaults->details_background = 0x333333FF; 41 type_defaults->details_background = 0x333333FF;
40 type_defaults->background = 0x323232FF; 42 type_defaults->background = 0x323232FF;
@@ -88,8 +90,8 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
88 return; 90 return;
89 } 91 }
90 92
91 if (src->font) { 93 if (src->font_description) {
92 dest->font = strdup(src->font); 94 dest->font_description = pango_font_description_copy(src->font_description);
93 } 95 }
94 96
95 if (src->output) { 97 if (src->output) {
@@ -100,6 +102,10 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
100 dest->anchors = src->anchors; 102 dest->anchors = src->anchors;
101 } 103 }
102 104
105 if (src->layer >= 0) {
106 dest->layer = src->layer;
107 }
108
103 // Colors 109 // Colors
104 if (src->button_background > 0) { 110 if (src->button_background > 0) {
105 dest->button_background = src->button_background; 111 dest->button_background = src->button_background;
@@ -166,7 +172,7 @@ void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
166 172
167void swaynag_type_free(struct swaynag_type *type) { 173void swaynag_type_free(struct swaynag_type *type) {
168 free(type->name); 174 free(type->name);
169 free(type->font); 175 pango_font_description_free(type->font_description);
170 free(type->output); 176 free(type->output);
171 free(type); 177 free(type);
172} 178}