summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar emersion <contact@emersion.fr>2018-08-02 23:49:25 +0100
committerLibravatar GitHub <noreply@github.com>2018-08-02 23:49:25 +0100
commit3a54e2291c017397ceff60511c29fe70d229bc8b (patch)
treed340b7776f945462f5ecffc830ada4d5fbe82f51
parentEnable wlr-gamma-control-unstable-v1 (diff)
parentMerge pull request #2411 from emersion/fullscreen-pointer-input (diff)
downloadsway-3a54e2291c017397ceff60511c29fe70d229bc8b.tar.gz
sway-3a54e2291c017397ceff60511c29fe70d229bc8b.tar.zst
sway-3a54e2291c017397ceff60511c29fe70d229bc8b.zip
Merge branch 'master' into wlr-gamma-control
-rw-r--r--README.de.md2
-rw-r--r--README.el.md2
-rw-r--r--README.fr.md2
-rw-r--r--README.it.md2
-rw-r--r--README.ja.md2
-rw-r--r--README.md2
-rw-r--r--README.pt.md2
-rw-r--r--README.ru.md2
-rw-r--r--README.uk.md2
-rw-r--r--common/util.c16
-rw-r--r--completions/bash/sway46
-rw-r--r--completions/bash/swayidle48
-rw-r--r--completions/bash/swaylock66
-rw-r--r--completions/bash/swaymsg59
-rw-r--r--completions/zsh/_sway2
-rw-r--r--completions/zsh/_swaygrab23
-rw-r--r--completions/zsh/_swaymsg3
-rw-r--r--config.in3
-rw-r--r--include/ipc.h5
-rw-r--r--include/sway/commands.h4
-rw-r--r--include/sway/config.h38
-rw-r--r--include/sway/criteria.h3
-rw-r--r--include/sway/desktop/transaction.h13
-rw-r--r--include/sway/input/cursor.h10
-rw-r--r--include/sway/input/input-manager.h3
-rw-r--r--include/sway/input/keyboard.h3
-rw-r--r--include/sway/input/seat.h35
-rw-r--r--include/sway/ipc-server.h2
-rw-r--r--include/sway/output.h52
-rw-r--r--include/sway/scratchpad.h26
-rw-r--r--include/sway/server.h10
-rw-r--r--include/sway/tree/container.h66
-rw-r--r--include/sway/tree/layout.h15
-rw-r--r--include/sway/tree/view.h59
-rw-r--r--include/sway/tree/workspace.h6
-rw-r--r--include/swaygrab/json.h10
-rw-r--r--include/swaynag/config.h13
-rw-r--r--include/swaynag/render.h7
-rw-r--r--include/swaynag/swaynag.h100
-rw-r--r--include/swaynag/types.h39
-rw-r--r--include/util.h9
-rw-r--r--meson.build25
-rw-r--r--meson_options.txt2
-rw-r--r--security.d/00-defaults.in6
-rw-r--r--sway/commands.c4
-rw-r--r--sway/commands/bind.c218
-rw-r--r--sway/commands/exec_always.c6
-rw-r--r--sway/commands/floating.c21
-rw-r--r--sway/commands/floating_modifier.c30
-rw-r--r--sway/commands/focus.c25
-rw-r--r--sway/commands/focus_follows_mouse.c4
-rw-r--r--sway/commands/focus_wrapping.c12
-rw-r--r--sway/commands/force_focus_wrapping.c8
-rw-r--r--sway/commands/fullscreen.c28
-rw-r--r--sway/commands/input.c21
-rw-r--r--sway/commands/input/drag_lock.c9
-rw-r--r--sway/commands/input/dwt.c9
-rw-r--r--sway/commands/input/left_handed.c11
-rw-r--r--sway/commands/input/middle_emulation.c9
-rw-r--r--sway/commands/input/natural_scroll.c11
-rw-r--r--sway/commands/input/tap.c9
-rw-r--r--sway/commands/input/xkb_capslock.c33
-rw-r--r--sway/commands/input/xkb_numlock.c33
-rw-r--r--sway/commands/mark.c2
-rw-r--r--sway/commands/mode.c1
-rw-r--r--sway/commands/move.c43
-rw-r--r--sway/commands/output/dpms.c8
-rw-r--r--sway/commands/reload.c29
-rw-r--r--sway/commands/scratchpad.c44
-rw-r--r--sway/commands/show_marks.c10
-rw-r--r--sway/commands/split.c4
-rw-r--r--sway/commands/swap.c7
-rw-r--r--sway/commands/urgent.c10
-rw-r--r--sway/config.c10
-rw-r--r--sway/config/bar.c4
-rw-r--r--sway/config/input.c8
-rw-r--r--sway/criteria.c22
-rw-r--r--sway/desktop/layer_shell.c7
-rw-r--r--sway/desktop/output.c339
-rw-r--r--sway/desktop/render.c132
-rw-r--r--sway/desktop/transaction.c87
-rw-r--r--sway/desktop/xdg_shell.c105
-rw-r--r--sway/desktop/xdg_shell_v6.c106
-rw-r--r--sway/desktop/xwayland.c60
-rw-r--r--sway/input/cursor.c413
-rw-r--r--sway/input/input-manager.c36
-rw-r--r--sway/input/keyboard.c108
-rw-r--r--sway/input/seat.c135
-rw-r--r--sway/ipc-json.c9
-rw-r--r--sway/ipc-server.c136
-rw-r--r--sway/main.c1
-rw-r--r--sway/meson.build12
-rw-r--r--sway/scratchpad.c181
-rw-r--r--sway/server.c5
-rw-r--r--sway/sway-input.5.scd10
-rw-r--r--sway/sway.1.scd2
-rw-r--r--sway/tree/arrange.c18
-rw-r--r--sway/tree/container.c369
-rw-r--r--sway/tree/layout.c122
-rw-r--r--sway/tree/output.c2
-rw-r--r--sway/tree/view.c315
-rw-r--r--sway/tree/workspace.c268
-rw-r--r--swayidle/swayidle.1.scd2
-rw-r--r--swaymsg/main.c8
-rw-r--r--swaymsg/swaymsg.1.scd3
-rw-r--r--swaynag/config.c401
-rw-r--r--swaynag/main.c129
-rw-r--r--swaynag/meson.build23
-rw-r--r--swaynag/render.c308
-rw-r--r--swaynag/swaynag.1.scd106
-rw-r--r--swaynag/swaynag.5.scd100
-rw-r--r--swaynag/swaynag.c451
-rw-r--r--swaynag/types.c156
113 files changed, 5187 insertions, 1026 deletions
diff --git a/README.de.md b/README.de.md
index 206a1040..a872e888 100644
--- a/README.de.md
+++ b/README.de.md
@@ -64,8 +64,6 @@ Abhängigkeiten:
64* cairo 64* cairo
65* gdk-pixbuf2 * 65* gdk-pixbuf2 *
66* pam ** 66* pam **
67* imagemagick (erforderlich für Bildaufnahme mit swaygrab)
68* ffmpeg (erforderlich für Videoaufnahme swaygrab)
69* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (erforderlich für man pages) 67* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (erforderlich für man pages)
70 68
71_\*Nur erforderlich für swaybar, swaybg, und swaylock_ 69_\*Nur erforderlich für swaybar, swaybg, und swaylock_
diff --git a/README.el.md b/README.el.md
index 5c70beff..6887fe8e 100644
--- a/README.el.md
+++ b/README.el.md
@@ -57,8 +57,6 @@ To username μου στο Freenode είναι kon14 και θα με βρείτ
57* cairo 57* cairo
58* gdk-pixbuf2 * 58* gdk-pixbuf2 *
59* pam ** 59* pam **
60* imagemagick (αναγκαίο για καταγραφή εικόνας μέσω του swaygrab)
61* ffmpeg (αναγκαίο για καταγραφή video μέσω του swaygrab)
62* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) 60* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages)
63 61
64_\*Απαιτείται μόνο για swaybar, swaybg, and swaylock_ 62_\*Απαιτείται μόνο για swaybar, swaybg, and swaylock_
diff --git a/README.fr.md b/README.fr.md
index 0d2573f9..6ea4d14c 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -59,8 +59,6 @@ Installez les dépendances :
59* cairo 59* cairo
60* gdk-pixbuf2 * 60* gdk-pixbuf2 *
61* pam ** 61* pam **
62* imagemagick (requis pour la capture d'image avec swaygrab)
63* ffmpeg (requis pour la capture vidéo avec swaygrab)
64* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (requis pour les pages man) 62* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (requis pour les pages man)
65 63
66_\*Uniquement requis pour swaybar, swaybg, and swaylock_ 64_\*Uniquement requis pour swaybar, swaybg, and swaylock_
diff --git a/README.it.md b/README.it.md
index 0d81ea54..3b1b1ebc 100644
--- a/README.it.md
+++ b/README.it.md
@@ -60,8 +60,6 @@ Installa queste dipendenze:
60* cairo 60* cairo
61* gdk-pixbuf2 * 61* gdk-pixbuf2 *
62* pam ** 62* pam **
63* imagemagick (richiesto per catturare immagini con swaygrab)
64* ffmpeg (rrichiesto per catturare video con swaygrab)
65* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (rrichiesto per man pages) 63* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (rrichiesto per man pages)
66 64
67_\*Richiesto solo per swaybar, swaybg, e swaylock_ 65_\*Richiesto solo per swaybar, swaybg, e swaylock_
diff --git a/README.ja.md b/README.ja.md
index 476d7472..7b437966 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -50,8 +50,6 @@ Swayは沢山のディストリビューションで提供されています。"
50* cairo 50* cairo
51* gdk-pixbuf2 * 51* gdk-pixbuf2 *
52* pam ** 52* pam **
53* imagemagick (swaygrabでスクリーンショットを撮るのに必要です)
54* ffmpeg (swaygrabで画面を録画するのに必要です)
55* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (manで必要です) 53* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (manで必要です)
56 54
57_\*swaybar,swaybg,swaylockでのみ必要です_ 55_\*swaybar,swaybg,swaylockでのみ必要です_
diff --git a/README.md b/README.md
index 49140fb8..ce753265 100644
--- a/README.md
+++ b/README.md
@@ -58,8 +58,6 @@ Install dependencies:
58* gdk-pixbuf2 * 58* gdk-pixbuf2 *
59* pam ** 59* pam **
60* dbus >= 1.10 *** 60* dbus >= 1.10 ***
61* imagemagick (required for image capture with swaygrab)
62* ffmpeg (required for video capture with swaygrab)
63* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) 61* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages)
64 62
65_\*Only required for swaybar, swaybg, and swaylock_ 63_\*Only required for swaybar, swaybg, and swaylock_
diff --git a/README.pt.md b/README.pt.md
index d1ef245f..9089c8c6 100644
--- a/README.pt.md
+++ b/README.pt.md
@@ -66,8 +66,6 @@ Antes de iniciar a compilação, instale as dependências:
66* cairo 66* cairo
67* gdk-pixbuf2 * 67* gdk-pixbuf2 *
68* pam ** 68* pam **
69* imagemagick (capturar imagem com o swaygrab)
70* ffmpeg (capturar vídeo com o swaygrab)
71* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (man pages) 69* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (man pages)
72 70
73_\*Dependência apenas de swaybar, swaybg, e swaylock_ 71_\*Dependência apenas de swaybar, swaybg, e swaylock_
diff --git a/README.ru.md b/README.ru.md
index 3b3de19a..68675db3 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -62,8 +62,6 @@ Sway доступен во многих дистрибутивах и наход
62* gdk-pixbuf2 * 62* gdk-pixbuf2 *
63* pam ** 63* pam **
64* dbus >= 1.10 *** 64* dbus >= 1.10 ***
65* imagemagick (требуется для захвата изображений через swaygrab)
66* ffmpeg (требуется для захвата видео через swaygrab)
67* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) 65* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages)
68 66
69_\*Требуется только для swaybar, swaybg и swaylock_ 67_\*Требуется только для swaybar, swaybg и swaylock_
diff --git a/README.uk.md b/README.uk.md
index 55698487..c31a3ea9 100644
--- a/README.uk.md
+++ b/README.uk.md
@@ -66,8 +66,6 @@ Sway доступний у багатьох дистрибутивах Linux (а
66* cairo 66* cairo
67* gdk-pixbuf2 * 67* gdk-pixbuf2 *
68* pam ** 68* pam **
69* imagemagick (для захоплення зображень за допомогою swaygrab)
70* ffmpeg (для захоплення відео за допомогою swaygrab)
71* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages) 69* [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (required for man pages)
72 70
73_\*Лише для swaybar, swaybg та swaylock_ 71_\*Лише для swaybar, swaybg та swaylock_
diff --git a/common/util.c b/common/util.c
index e8a88772..467aa4b5 100644
--- a/common/util.c
+++ b/common/util.c
@@ -123,6 +123,22 @@ uint32_t parse_color(const char *color) {
123 return res; 123 return res;
124} 124}
125 125
126bool parse_boolean(const char *boolean, bool current) {
127 if (strcasecmp(boolean, "1") == 0
128 || strcasecmp(boolean, "yes") == 0
129 || strcasecmp(boolean, "on") == 0
130 || strcasecmp(boolean, "true") == 0
131 || strcasecmp(boolean, "enable") == 0
132 || strcasecmp(boolean, "enabled") == 0
133 || strcasecmp(boolean, "active") == 0) {
134 return true;
135 } else if (strcasecmp(boolean, "toggle") == 0) {
136 return !current;
137 }
138 // All other values are false to match i3
139 return false;
140}
141
126char* resolve_path(const char* path) { 142char* resolve_path(const char* path) {
127 struct stat sb; 143 struct stat sb;
128 ssize_t r; 144 ssize_t r;
diff --git a/completions/bash/sway b/completions/bash/sway
new file mode 100644
index 00000000..edd752cd
--- /dev/null
+++ b/completions/bash/sway
@@ -0,0 +1,46 @@
1# sway(1) completion
2
3_sway()
4{
5 local cur prev
6 _get_comp_words_by_ref cur prev
7
8 short=(
9 -h
10 -c
11 -C
12 -d
13 -v
14 -V
15 )
16
17 long=(
18 --help
19 --config
20 --validate
21 --debug
22 --version
23 --verbose
24 --get-socketpath
25 )
26
27 case $prev in
28 -c|--config)
29 _filedir
30 return
31 ;;
32 esac
33
34 if [[ $cur == --* ]]; then
35 COMPREPLY=($(compgen -W "${long[*]}" -- "$cur"))
36 elif [[ $cur == -* ]]; then
37 COMPREPLY=($(compgen -W "${short[*]}" -- "$cur"))
38 COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur"))
39 else
40 COMPREPLY=($(compgen -W "${short[*]}" -- "$cur"))
41 COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur"))
42 COMPREPLY+=($(compgen -c -- "$cur"))
43 fi
44
45} &&
46complete -F _sway sway
diff --git a/completions/bash/swayidle b/completions/bash/swayidle
new file mode 100644
index 00000000..a0cdc8b2
--- /dev/null
+++ b/completions/bash/swayidle
@@ -0,0 +1,48 @@
1# swaymsg(1) completion
2
3_swayidle()
4{
5 local cur prev
6 _get_comp_words_by_ref -n : cur prev
7 local prev2=${COMP_WORDS[COMP_CWORD-2]}
8 local prev3=${COMP_WORDS[COMP_CWORD-3]}
9
10 events=(
11 'timeout'
12 'before-sleep'
13 )
14
15 short=(
16 -h
17 -d
18 )
19
20 if [ "$prev" = timeout ]; then
21 # timeout <timeout>
22 return
23 elif [ "$prev2" = timeout ]; then
24 # timeout <timeout> <timeout command>
25 COMPREPLY=($(compgen -c -- "$cur"))
26 return
27 elif [ "$prev3" = timeout ]; then
28 # timeout <timeout> <timeout command> [resume <resume command>]
29 COMPREPLY=(resume)
30 # optional argument; no return here as user may skip 'resume'
31 fi
32
33 case "$prev" in
34 resume)
35 COMPREPLY=($(compgen -c -- "$cur"))
36 return
37 ;;
38 before-sleep)
39 COMPREPLY=($(compgen -c -- "$cur"))
40 return
41 ;;
42 esac
43
44 COMPREPLY+=($(compgen -W "${events[*]}" -- "$cur"))
45 COMPREPLY+=($(compgen -W "${short[*]}" -- "$cur"))
46
47} &&
48complete -F _swayidle swayidle
diff --git a/completions/bash/swaylock b/completions/bash/swaylock
new file mode 100644
index 00000000..33925480
--- /dev/null
+++ b/completions/bash/swaylock
@@ -0,0 +1,66 @@
1# swaylock(1) completion
2
3_swaylock()
4{
5 local cur prev
6 _get_comp_words_by_ref -n : cur prev
7
8 short=(
9 -h
10 -c
11 -s
12 -t
13 -v
14 -i
15 -u
16 -f
17 )
18
19 long=(
20 --help
21 --color
22 --scaling
23 --tiling
24 --version
25 --image
26 --no-unlock-indicator
27 --daemonize
28 )
29
30 scaling=(
31 'stretch'
32 'fill'
33 'fit'
34 'center'
35 'tile'
36 )
37
38 case $prev in
39 -c|--color)
40 return
41 ;;
42 --scaling)
43 COMPREPLY=($(compgen -W "${scaling[*]}" -- "$cur"))
44 return
45 ;;
46 -i|--image)
47 if grep -q : <<< "$cur"; then
48 output="${cur%%:*}:"
49 cur="${cur#*:}"
50 else
51 output=
52 fi
53 COMPREPLY=($(compgen -f -- "$cur"))
54 return
55 ;;
56 esac
57
58 if [[ $cur == --* ]]; then
59 COMPREPLY=($(compgen -W "${long[*]}" -- "$cur"))
60 else
61 COMPREPLY=($(compgen -W "${short[*]}" -- "$cur"))
62 COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur"))
63 fi
64
65} &&
66complete -F _swaylock swaylock
diff --git a/completions/bash/swaymsg b/completions/bash/swaymsg
new file mode 100644
index 00000000..20092bdc
--- /dev/null
+++ b/completions/bash/swaymsg
@@ -0,0 +1,59 @@
1# swaymsg(1) completion
2
3_swaymsg()
4{
5 local cur prev
6 _get_comp_words_by_ref cur prev
7
8 types=(
9 'get_workspaces'
10 'get_seats'
11 'get_inputs'
12 'get_outputs'
13 'get_tree'
14 'get_marks'
15 'get_bar_config'
16 'get_version'
17 'get_binding_modes'
18 'get_config'
19 'send_tick'
20 )
21
22 short=(
23 -h
24 -q
25 -r
26 -s
27 -t
28 -v
29 )
30
31 long=(
32 --help
33 --quiet
34 --raw
35 --socket
36 --type
37 --verbose
38 )
39
40 case $prev in
41 -s|--socket)
42 _filedir
43 return
44 ;;
45 -t|--type)
46 COMPREPLY=($(compgen -W "${types[*]}" -- "$cur"))
47 return
48 ;;
49 esac
50
51 if [[ $cur == --* ]]; then
52 COMPREPLY=($(compgen -W "${long[*]}" -- "$cur"))
53 else
54 COMPREPLY=($(compgen -W "${short[*]}" -- "$cur"))
55 COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur"))
56 fi
57
58} &&
59complete -F _swaymsg swaymsg
diff --git a/completions/zsh/_sway b/completions/zsh/_sway
index bab90fbf..05112002 100644
--- a/completions/zsh/_sway
+++ b/completions/zsh/_sway
@@ -18,5 +18,5 @@ _arguments -s \
18 '(-c --config)'{-c,--config}'[Specify a config file]:files:_files' \ 18 '(-c --config)'{-c,--config}'[Specify a config file]:files:_files' \
19 '(-C --validate)'{-C,--validate}'[Check validity of the config file, then exit]' \ 19 '(-C --validate)'{-C,--validate}'[Check validity of the config file, then exit]' \
20 '(-d --debug)'{-d,--debug}'[Enables full logging, including debug information]' \ 20 '(-d --debug)'{-d,--debug}'[Enables full logging, including debug information]' \
21 '(-v --verbose)'{-v,--verbose}'[Enables more verbose logging]' \ 21 '(-V --verbose)'{-V,--verbose}'[Enables more verbose logging]' \
22 '(--get-socketpath)'--get-socketpath'[Gets the IPC socket path and prints it, then exits]' 22 '(--get-socketpath)'--get-socketpath'[Gets the IPC socket path and prints it, then exits]'
diff --git a/completions/zsh/_swaygrab b/completions/zsh/_swaygrab
deleted file mode 100644
index 0f9846f4..00000000
--- a/completions/zsh/_swaygrab
+++ /dev/null
@@ -1,23 +0,0 @@
1#compdef swaygrab
2#-----------------
3# Description
4# -----------
5#
6# Completion script for swaygrab in sway wm (http://swaywm.org)
7#
8# -----------------------------------------------------
9# Author
10# ------
11#
12# * Seth Barberee <seth.barberee@gmail.com>
13#
14# ------------------------------------------
15
16_arguments -s \
17 '(-h --help)'{-h,--help}'[Shows help message]' \
18 '(-c --capture)'{-c,--capture}'[Captures multiple frames as video and passes them to ffmpeg]' \
19 '(-o --output)'{-o,--output}'[Use the specified output. If not specified then current focused output will be used]' \
20 '(-v --version)'{-v,--version}'[Print the version (of swaymsg) and quit]' \
21 '(-s --socket)'{-s,--socket}'[Use the specified socket path.]:files:_files' \
22 '(-r --rate)'{-r,--rate}'[Specify a framerate (in fps). Used in combination with -c. Default is 30 and must be an integer]' \
23 '(--raw)--raw[Instead of ImageMagick or ffmpeg, dump raw rgba data to stdout]'
diff --git a/completions/zsh/_swaymsg b/completions/zsh/_swaymsg
index 2e39deb6..a7a1c8e0 100644
--- a/completions/zsh/_swaymsg
+++ b/completions/zsh/_swaymsg
@@ -22,6 +22,9 @@ types=(
22'get_marks' 22'get_marks'
23'get_bar_config' 23'get_bar_config'
24'get_version' 24'get_version'
25'get_binding_modes'
26'get_config'
27'send_tick'
25) 28)
26 29
27_arguments -s \ 30_arguments -s \
diff --git a/config.in b/config.in
index 4a11762a..41f53461 100644
--- a/config.in
+++ b/config.in
@@ -16,7 +16,8 @@ set $right l
16# Your preferred terminal emulator 16# Your preferred terminal emulator
17set $term urxvt 17set $term urxvt
18# Your preferred application launcher 18# Your preferred application launcher
19set $menu dmenu_run 19# Note: it's recommended that you pass the final command to sway
20set $menu dmenu_path | dmenu | xargs swaymsg exec
20 21
21### Output configuration 22### Output configuration
22# 23#
diff --git a/include/ipc.h b/include/ipc.h
index 0010718b..a3f60e19 100644
--- a/include/ipc.h
+++ b/include/ipc.h
@@ -15,6 +15,7 @@ enum ipc_command_type {
15 IPC_GET_VERSION = 7, 15 IPC_GET_VERSION = 7,
16 IPC_GET_BINDING_MODES = 8, 16 IPC_GET_BINDING_MODES = 8,
17 IPC_GET_CONFIG = 9, 17 IPC_GET_CONFIG = 9,
18 IPC_SEND_TICK = 10,
18 19
19 // sway-specific command types 20 // sway-specific command types
20 IPC_GET_INPUTS = 100, 21 IPC_GET_INPUTS = 100,
@@ -27,8 +28,8 @@ enum ipc_command_type {
27 IPC_EVENT_WINDOW = ((1<<31) | 3), 28 IPC_EVENT_WINDOW = ((1<<31) | 3),
28 IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4), 29 IPC_EVENT_BARCONFIG_UPDATE = ((1<<31) | 4),
29 IPC_EVENT_BINDING = ((1<<31) | 5), 30 IPC_EVENT_BINDING = ((1<<31) | 5),
30 IPC_EVENT_MODIFIER = ((1<<31) | 6), 31 IPC_EVENT_SHUTDOWN = ((1<<31) | 6),
31 IPC_EVENT_INPUT = ((1<<31) | 7), 32 IPC_EVENT_TICK = ((1<<31) | 7),
32}; 33};
33 34
34#endif 35#endif
diff --git a/include/sway/commands.h b/include/sway/commands.h
index e71a7228..41858ccc 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -106,7 +106,7 @@ sway_cmd cmd_exit;
106sway_cmd cmd_floating; 106sway_cmd cmd_floating;
107sway_cmd cmd_floating_maximum_size; 107sway_cmd cmd_floating_maximum_size;
108sway_cmd cmd_floating_minimum_size; 108sway_cmd cmd_floating_minimum_size;
109sway_cmd cmd_floating_mod; 109sway_cmd cmd_floating_modifier;
110sway_cmd cmd_floating_scroll; 110sway_cmd cmd_floating_scroll;
111sway_cmd cmd_focus; 111sway_cmd cmd_focus;
112sway_cmd cmd_focus_follows_mouse; 112sway_cmd cmd_focus_follows_mouse;
@@ -213,8 +213,10 @@ sway_cmd input_cmd_scroll_button;
213sway_cmd input_cmd_scroll_method; 213sway_cmd input_cmd_scroll_method;
214sway_cmd input_cmd_tap; 214sway_cmd input_cmd_tap;
215sway_cmd input_cmd_tap_button_map; 215sway_cmd input_cmd_tap_button_map;
216sway_cmd input_cmd_xkb_capslock;
216sway_cmd input_cmd_xkb_layout; 217sway_cmd input_cmd_xkb_layout;
217sway_cmd input_cmd_xkb_model; 218sway_cmd input_cmd_xkb_model;
219sway_cmd input_cmd_xkb_numlock;
218sway_cmd input_cmd_xkb_options; 220sway_cmd input_cmd_xkb_options;
219sway_cmd input_cmd_xkb_rules; 221sway_cmd input_cmd_xkb_rules;
220sway_cmd input_cmd_xkb_variant; 222sway_cmd input_cmd_xkb_variant;
diff --git a/include/sway/config.h b/include/sway/config.h
index b8da29c5..909b6827 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -1,6 +1,5 @@
1#ifndef _SWAY_CONFIG_H 1#ifndef _SWAY_CONFIG_H
2#define _SWAY_CONFIG_H 2#define _SWAY_CONFIG_H
3#define PID_WORKSPACE_TIMEOUT 60
4#include <libinput.h> 3#include <libinput.h>
5#include <stdint.h> 4#include <stdint.h>
6#include <string.h> 5#include <string.h>
@@ -22,14 +21,28 @@ struct sway_variable {
22 char *value; 21 char *value;
23}; 22};
24 23
24
25enum binding_input_type {
26 BINDING_KEYCODE,
27 BINDING_KEYSYM,
28 BINDING_MOUSE,
29};
30
31enum binding_flags {
32 BINDING_RELEASE=1,
33 BINDING_LOCKED=2, // keyboard only
34 BINDING_BORDER=4, // mouse only; trigger on container border
35 BINDING_CONTENTS=8, // mouse only; trigger on container contents
36 BINDING_TITLEBAR=16 // mouse only; trigger on container titlebar
37};
38
25/** 39/**
26 * A key binding and an associated command. 40 * A key binding and an associated command.
27 */ 41 */
28struct sway_binding { 42struct sway_binding {
43 enum binding_input_type type;
29 int order; 44 int order;
30 bool release; 45 uint32_t flags;
31 bool locked;
32 bool bindcode;
33 list_t *keys; // sorted in ascending order 46 list_t *keys; // sorted in ascending order
34 uint32_t modifiers; 47 uint32_t modifiers;
35 char *command; 48 char *command;
@@ -50,6 +63,7 @@ struct sway_mode {
50 char *name; 63 char *name;
51 list_t *keysym_bindings; 64 list_t *keysym_bindings;
52 list_t *keycode_bindings; 65 list_t *keycode_bindings;
66 list_t *mouse_bindings;
53 bool pango; 67 bool pango;
54}; 68};
55 69
@@ -87,6 +101,9 @@ struct input_config {
87 char *xkb_rules; 101 char *xkb_rules;
88 char *xkb_variant; 102 char *xkb_variant;
89 103
104 int xkb_numlock;
105 int xkb_capslock;
106
90 struct input_config_mapped_from_region *mapped_from_region; 107 struct input_config_mapped_from_region *mapped_from_region;
91 char *mapped_to_output; 108 char *mapped_to_output;
92 109
@@ -146,12 +163,6 @@ struct workspace_output {
146 char *workspace; 163 char *workspace;
147}; 164};
148 165
149struct pid_workspace {
150 pid_t *pid;
151 char *workspace;
152 time_t *time_added;
153};
154
155struct bar_config { 166struct bar_config {
156 /** 167 /**
157 * One of "dock", "hide", "invisible" 168 * One of "dock", "hide", "invisible"
@@ -302,7 +313,6 @@ struct sway_config {
302 list_t *bars; 313 list_t *bars;
303 list_t *cmd_queue; 314 list_t *cmd_queue;
304 list_t *workspace_outputs; 315 list_t *workspace_outputs;
305 list_t *pid_workspaces;
306 list_t *output_configs; 316 list_t *output_configs;
307 list_t *input_configs; 317 list_t *input_configs;
308 list_t *seat_configs; 318 list_t *seat_configs;
@@ -313,6 +323,7 @@ struct sway_config {
313 struct bar_config *current_bar; 323 struct bar_config *current_bar;
314 char *swaybg_command; 324 char *swaybg_command;
315 uint32_t floating_mod; 325 uint32_t floating_mod;
326 bool floating_mod_inverse;
316 uint32_t dragging_key; 327 uint32_t dragging_key;
317 uint32_t resizing_key; 328 uint32_t resizing_key;
318 char *floating_scroll_up_cmd; 329 char *floating_scroll_up_cmd;
@@ -388,9 +399,6 @@ struct sway_config {
388 } handler_context; 399 } handler_context;
389}; 400};
390 401
391void pid_workspace_add(struct pid_workspace *pw);
392void free_pid_workspace(struct pid_workspace *pw);
393
394/** 402/**
395 * Loads the main config from the given path. is_active should be true when 403 * Loads the main config from the given path. is_active should be true when
396 * reloading the config. 404 * reloading the config.
@@ -480,7 +488,7 @@ int sway_binding_cmp_keys(const void *a, const void *b);
480 488
481void free_sway_binding(struct sway_binding *sb); 489void free_sway_binding(struct sway_binding *sb);
482 490
483struct sway_binding *sway_binding_dup(struct sway_binding *sb); 491void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding);
484 492
485void load_swaybars(); 493void load_swaybars();
486 494
diff --git a/include/sway/criteria.h b/include/sway/criteria.h
index 6a8337c5..b4ff7d49 100644
--- a/include/sway/criteria.h
+++ b/include/sway/criteria.h
@@ -2,6 +2,7 @@
2#define _SWAY_CRITERIA_H 2#define _SWAY_CRITERIA_H
3 3
4#include <pcre.h> 4#include <pcre.h>
5#include "config.h"
5#include "list.h" 6#include "list.h"
6#include "tree/view.h" 7#include "tree/view.h"
7 8
@@ -25,7 +26,9 @@ struct criteria {
25 pcre *instance; 26 pcre *instance;
26 pcre *con_mark; 27 pcre *con_mark;
27 uint32_t con_id; // internal ID 28 uint32_t con_id; // internal ID
29#ifdef HAVE_XWAYLAND
28 uint32_t id; // X11 window ID 30 uint32_t id; // X11 window ID
31#endif
29 pcre *window_role; 32 pcre *window_role;
30 uint32_t window_type; 33 uint32_t window_type;
31 bool floating; 34 bool floating;
diff --git a/include/sway/desktop/transaction.h b/include/sway/desktop/transaction.h
index cee4afed..56361d94 100644
--- a/include/sway/desktop/transaction.h
+++ b/include/sway/desktop/transaction.h
@@ -42,17 +42,4 @@ void transaction_notify_view_ready(struct sway_view *view, uint32_t serial);
42void transaction_notify_view_ready_by_size(struct sway_view *view, 42void transaction_notify_view_ready_by_size(struct sway_view *view,
43 int width, int height); 43 int width, int height);
44 44
45/**
46 * Get the saved texture that should be rendered for a view.
47 *
48 * The addresses pointed at by the width and height pointers will be populated
49 * with the surface's dimensions, which may be different to the texture's
50 * dimensions if output scaling is used.
51 *
52 * This function should only be called if it is known that the view has
53 * instructions.
54 */
55struct wlr_texture *transaction_get_saved_texture(struct sway_view *view,
56 int *width, int *height);
57
58#endif 45#endif
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 5dd109ca..7ec45120 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -3,6 +3,8 @@
3#include <stdint.h> 3#include <stdint.h>
4#include "sway/input/seat.h" 4#include "sway/input/seat.h"
5 5
6#define SWAY_CURSOR_PRESSED_BUTTONS_CAP 32
7
6struct sway_cursor { 8struct sway_cursor {
7 struct sway_seat *seat; 9 struct sway_seat *seat;
8 struct wlr_cursor *cursor; 10 struct wlr_cursor *cursor;
@@ -11,6 +13,7 @@ struct sway_cursor {
11 } previous; 13 } previous;
12 struct wlr_xcursor_manager *xcursor_manager; 14 struct wlr_xcursor_manager *xcursor_manager;
13 15
16 const char *image;
14 struct wl_client *image_client; 17 struct wl_client *image_client;
15 18
16 struct wl_listener motion; 19 struct wl_listener motion;
@@ -28,6 +31,10 @@ struct sway_cursor {
28 uint32_t tool_buttons; 31 uint32_t tool_buttons;
29 32
30 struct wl_listener request_set_cursor; 33 struct wl_listener request_set_cursor;
34
35 // Mouse binding state
36 uint32_t pressed_buttons[SWAY_CURSOR_PRESSED_BUTTONS_CAP];
37 size_t pressed_button_count;
31}; 38};
32 39
33void sway_cursor_destroy(struct sway_cursor *cursor); 40void sway_cursor_destroy(struct sway_cursor *cursor);
@@ -37,4 +44,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
37void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec, 44void dispatch_cursor_button(struct sway_cursor *cursor, uint32_t time_msec,
38 uint32_t button, enum wlr_button_state state); 45 uint32_t button, enum wlr_button_state state);
39 46
47void cursor_set_image(struct sway_cursor *cursor, const char *image,
48 struct wl_client *client);
49
40#endif 50#endif
diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h
index 89a3ac71..aa2f6f19 100644
--- a/include/sway/input/input-manager.h
+++ b/include/sway/input/input-manager.h
@@ -2,6 +2,7 @@
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> 4#include <wlr/types/wlr_input_inhibitor.h>
5#include <wlr/types/wlr_virtual_keyboard_v1.h>
5#include "sway/server.h" 6#include "sway/server.h"
6#include "sway/config.h" 7#include "sway/config.h"
7#include "list.h" 8#include "list.h"
@@ -25,10 +26,12 @@ struct sway_input_manager {
25 struct wl_list seats; 26 struct wl_list seats;
26 27
27 struct wlr_input_inhibit_manager *inhibit; 28 struct wlr_input_inhibit_manager *inhibit;
29 struct wlr_virtual_keyboard_manager_v1 *virtual_keyboard;
28 30
29 struct wl_listener new_input; 31 struct wl_listener new_input;
30 struct wl_listener inhibit_activate; 32 struct wl_listener inhibit_activate;
31 struct wl_listener inhibit_deactivate; 33 struct wl_listener inhibit_deactivate;
34 struct wl_listener virtual_keyboard_new;
32}; 35};
33 36
34struct sway_input_manager *input_manager_create(struct sway_server *server); 37struct sway_input_manager *input_manager_create(struct sway_server *server);
diff --git a/include/sway/input/keyboard.h b/include/sway/input/keyboard.h
index 6713398e..6d28454c 100644
--- a/include/sway/input/keyboard.h
+++ b/include/sway/input/keyboard.h
@@ -38,6 +38,9 @@ struct sway_keyboard {
38 struct sway_shortcut_state state_keysyms_raw; 38 struct sway_shortcut_state state_keysyms_raw;
39 struct sway_shortcut_state state_keycodes; 39 struct sway_shortcut_state state_keycodes;
40 struct sway_binding *held_binding; 40 struct sway_binding *held_binding;
41
42 struct wl_event_source *key_repeat_source;
43 struct sway_binding *repeat_binding;
41}; 44};
42 45
43struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, 46struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index eac1626b..92387601 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -3,6 +3,7 @@
3 3
4#include <wlr/types/wlr_layer_shell.h> 4#include <wlr/types/wlr_layer_shell.h>
5#include <wlr/types/wlr_seat.h> 5#include <wlr/types/wlr_seat.h>
6#include <wlr/util/edges.h>
6#include "sway/input/input-manager.h" 7#include "sway/input/input-manager.h"
7 8
8struct sway_seat_device { 9struct sway_seat_device {
@@ -52,6 +53,24 @@ struct sway_seat {
52 int32_t touch_id; 53 int32_t touch_id;
53 double touch_x, touch_y; 54 double touch_x, touch_y;
54 55
56 // Operations (drag and resize)
57 enum {
58 OP_NONE,
59 OP_MOVE,
60 OP_RESIZE,
61 } operation;
62
63 struct sway_container *op_container;
64 enum wlr_edges op_resize_edge;
65 uint32_t op_button;
66 bool op_resize_preserve_ratio;
67 double op_ref_lx, op_ref_ly; // cursor's x/y at start of op
68 double op_ref_width, op_ref_height; // container's size at start of op
69 double op_ref_con_lx, op_ref_con_ly; // container's x/y at start of op
70
71 uint32_t last_button;
72 uint32_t last_button_serial;
73
55 struct wl_listener focus_destroy; 74 struct wl_listener focus_destroy;
56 struct wl_listener new_container; 75 struct wl_listener new_container;
57 struct wl_listener new_drag_icon; 76 struct wl_listener new_drag_icon;
@@ -80,7 +99,7 @@ void seat_configure_xcursor(struct sway_seat *seat);
80void seat_set_focus(struct sway_seat *seat, struct sway_container *container); 99void seat_set_focus(struct sway_seat *seat, struct sway_container *container);
81 100
82void seat_set_focus_warp(struct sway_seat *seat, 101void seat_set_focus_warp(struct sway_seat *seat,
83 struct sway_container *container, bool warp); 102 struct sway_container *container, bool warp, bool notify);
84 103
85void seat_set_focus_surface(struct sway_seat *seat, 104void seat_set_focus_surface(struct sway_seat *seat,
86 struct wlr_surface *surface, bool unfocus); 105 struct wlr_surface *surface, bool unfocus);
@@ -105,6 +124,9 @@ struct sway_container *seat_get_focus(struct sway_seat *seat);
105struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, 124struct sway_container *seat_get_focus_inactive(struct sway_seat *seat,
106 struct sway_container *container); 125 struct sway_container *container);
107 126
127struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat,
128 struct sway_container *container);
129
108/** 130/**
109 * Descend into the focus stack to find the focus-inactive view. Useful for 131 * Descend into the focus stack to find the focus-inactive view. Useful for
110 * container placement when they change position in the tree. 132 * container placement when they change position in the tree.
@@ -134,4 +156,15 @@ bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
134 156
135void drag_icon_update_position(struct sway_drag_icon *icon); 157void drag_icon_update_position(struct sway_drag_icon *icon);
136 158
159void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
160 uint32_t button);
161
162void seat_begin_resize(struct sway_seat *seat, struct sway_container *con,
163 uint32_t button, enum wlr_edges edge);
164
165void seat_end_mouse_operation(struct sway_seat *seat);
166
167void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
168 uint32_t button, enum wlr_button_state state);
169
137#endif 170#endif
diff --git a/include/sway/ipc-server.h b/include/sway/ipc-server.h
index 6469f097..4b6d0e25 100644
--- a/include/sway/ipc-server.h
+++ b/include/sway/ipc-server.h
@@ -16,5 +16,7 @@ void ipc_event_workspace(struct sway_container *old,
16void ipc_event_window(struct sway_container *window, const char *change); 16void ipc_event_window(struct sway_container *window, const char *change);
17void ipc_event_barconfig_update(struct bar_config *bar); 17void ipc_event_barconfig_update(struct bar_config *bar);
18void ipc_event_mode(const char *mode, bool pango); 18void ipc_event_mode(const char *mode, bool pango);
19void ipc_event_shutdown(const char *reason);
20void ipc_event_binding(struct sway_binding *binding);
19 21
20#endif 22#endif
diff --git a/include/sway/output.h b/include/sway/output.h
index b6cda83c..80dcd37b 100644
--- a/include/sway/output.h
+++ b/include/sway/output.h
@@ -5,6 +5,7 @@
5#include <wayland-server.h> 5#include <wayland-server.h>
6#include <wlr/types/wlr_box.h> 6#include <wlr/types/wlr_box.h>
7#include <wlr/types/wlr_output.h> 7#include <wlr/types/wlr_output.h>
8#include "config.h"
8#include "sway/tree/view.h" 9#include "sway/tree/view.h"
9 10
10struct sway_server; 11struct sway_server;
@@ -38,15 +39,9 @@ struct sway_output {
38 } events; 39 } events;
39}; 40};
40 41
41/** 42typedef void (*sway_surface_iterator_func_t)(struct sway_output *output,
42 * Contains a surface's root geometry information. For instance, when rendering 43 struct wlr_surface *surface, struct wlr_box *box, float rotation,
43 * a popup, this will contain the parent view's position and size. 44 void *user_data);
44 */
45struct root_geometry {
46 double x, y;
47 int width, height;
48 float rotation;
49};
50 45
51void output_damage_whole(struct sway_output *output); 46void output_damage_whole(struct sway_output *output);
52 47
@@ -65,36 +60,37 @@ struct sway_container *output_by_name(const char *name);
65 60
66void output_enable(struct sway_output *output); 61void output_enable(struct sway_output *output);
67 62
68bool output_has_opaque_lockscreen(struct sway_output *output, 63bool output_has_opaque_overlay_layer_surface(struct sway_output *output);
69 struct sway_seat *seat);
70 64
71struct sway_container *output_get_active_workspace(struct sway_output *output); 65struct sway_container *output_get_active_workspace(struct sway_output *output);
72 66
73void output_render(struct sway_output *output, struct timespec *when, 67void output_render(struct sway_output *output, struct timespec *when,
74 pixman_region32_t *damage); 68 pixman_region32_t *damage);
75 69
76bool output_get_surface_box(struct root_geometry *geo, 70void output_surface_for_each_surface(struct sway_output *output,
77 struct sway_output *output, struct wlr_surface *surface, int sx, int sy, 71 struct wlr_surface *surface, double ox, double oy,
78 struct wlr_box *surface_box); 72 sway_surface_iterator_func_t iterator, void *user_data);
79 73
80void output_surface_for_each_surface(struct wlr_surface *surface, 74void output_view_for_each_surface(struct sway_output *output,
81 double ox, double oy, struct root_geometry *geo, 75 struct sway_view *view, sway_surface_iterator_func_t iterator,
82 wlr_surface_iterator_func_t iterator, void *user_data); 76 void *user_data);
83 77
84void output_view_for_each_surface(struct sway_view *view, 78void output_view_for_each_popup(struct sway_output *output,
85 struct sway_output *output, struct root_geometry *geo, 79 struct sway_view *view, sway_surface_iterator_func_t iterator,
86 wlr_surface_iterator_func_t iterator, void *user_data); 80 void *user_data);
87 81
88void output_layer_for_each_surface(struct wl_list *layer_surfaces, 82void output_layer_for_each_surface(struct sway_output *output,
89 struct root_geometry *geo, wlr_surface_iterator_func_t iterator, 83 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator,
90 void *user_data); 84 void *user_data);
91 85
92void output_unmanaged_for_each_surface(struct wl_list *unmanaged, 86#ifdef HAVE_XWAYLAND
93 struct sway_output *output, struct root_geometry *geo, 87void output_unmanaged_for_each_surface(struct sway_output *output,
94 wlr_surface_iterator_func_t iterator, void *user_data); 88 struct wl_list *unmanaged, sway_surface_iterator_func_t iterator,
89 void *user_data);
90#endif
95 91
96void output_drag_icons_for_each_surface(struct wl_list *drag_icons, 92void output_drag_icons_for_each_surface(struct sway_output *output,
97 struct sway_output *output, struct root_geometry *geo, 93 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator,
98 wlr_surface_iterator_func_t iterator, void *user_data); 94 void *user_data);
99 95
100#endif 96#endif
diff --git a/include/sway/scratchpad.h b/include/sway/scratchpad.h
new file mode 100644
index 00000000..5af5256f
--- /dev/null
+++ b/include/sway/scratchpad.h
@@ -0,0 +1,26 @@
1#ifndef _SWAY_SCRATCHPAD_H
2#define _SWAY_SCRATCHPAD_H
3
4#include "tree/container.h"
5
6/**
7 * Move a container to the scratchpad.
8 */
9void scratchpad_add_container(struct sway_container *con);
10
11/**
12 * Remove a container from the scratchpad.
13 */
14void scratchpad_remove_container(struct sway_container *con);
15
16/**
17 * Show or hide the next container on the scratchpad.
18 */
19void scratchpad_toggle_auto(void);
20
21/**
22 * Show or hide a specific container on the scratchpad.
23 */
24void scratchpad_toggle_container(struct sway_container *con);
25
26#endif
diff --git a/include/sway/server.h b/include/sway/server.h
index 70bde6d4..a3782f91 100644
--- a/include/sway/server.h
+++ b/include/sway/server.h
@@ -12,7 +12,10 @@
12#include <wlr/render/wlr_renderer.h> 12#include <wlr/render/wlr_renderer.h>
13// TODO WLR: make Xwayland optional 13// TODO WLR: make Xwayland optional
14#include "list.h" 14#include "list.h"
15#include "config.h"
16#ifdef HAVE_XWAYLAND
15#include "sway/xwayland.h" 17#include "sway/xwayland.h"
18#endif
16 19
17struct sway_server { 20struct sway_server {
18 struct wl_display *wl_display; 21 struct wl_display *wl_display;
@@ -39,11 +42,11 @@ struct sway_server {
39 42
40 struct wlr_xdg_shell *xdg_shell; 43 struct wlr_xdg_shell *xdg_shell;
41 struct wl_listener xdg_shell_surface; 44 struct wl_listener xdg_shell_surface;
42 45#ifdef HAVE_XWAYLAND
43 struct sway_xwayland xwayland; 46 struct sway_xwayland xwayland;
44 struct wl_listener xwayland_surface; 47 struct wl_listener xwayland_surface;
45 struct wl_listener xwayland_ready; 48 struct wl_listener xwayland_ready;
46 49#endif
47 bool debug_txn_timings; 50 bool debug_txn_timings;
48 51
49 list_t *transactions; 52 list_t *transactions;
@@ -65,6 +68,7 @@ void handle_idle_inhibitor_v1(struct wl_listener *listener, void *data);
65void handle_layer_shell_surface(struct wl_listener *listener, void *data); 68void handle_layer_shell_surface(struct wl_listener *listener, void *data);
66void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); 69void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data);
67void handle_xdg_shell_surface(struct wl_listener *listener, void *data); 70void handle_xdg_shell_surface(struct wl_listener *listener, void *data);
71#ifdef HAVE_XWAYLAND
68void handle_xwayland_surface(struct wl_listener *listener, void *data); 72void handle_xwayland_surface(struct wl_listener *listener, void *data);
69 73#endif
70#endif 74#endif
diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h
index ca7a3288..c3942e9e 100644
--- a/include/sway/tree/container.h
+++ b/include/sway/tree/container.h
@@ -60,6 +60,8 @@ struct sway_container_state {
60 double swayc_x, swayc_y; 60 double swayc_x, swayc_y;
61 double swayc_width, swayc_height; 61 double swayc_width, swayc_height;
62 62
63 bool is_fullscreen;
64
63 bool has_gaps; 65 bool has_gaps;
64 double current_gaps; 66 double current_gaps;
65 double gaps_inner; 67 double gaps_inner;
@@ -74,7 +76,6 @@ struct sway_container_state {
74 // View properties 76 // View properties
75 double view_x, view_y; 77 double view_x, view_y;
76 double view_width, view_height; 78 double view_width, view_height;
77 bool is_fullscreen;
78 79
79 enum sway_container_border border; 80 enum sway_container_border border;
80 int border_thickness; 81 int border_thickness;
@@ -84,7 +85,7 @@ struct sway_container_state {
84 bool border_right; 85 bool border_right;
85 86
86 // Workspace properties 87 // Workspace properties
87 struct sway_view *ws_fullscreen; 88 struct sway_container *ws_fullscreen;
88 struct sway_container *ws_floating; 89 struct sway_container *ws_floating;
89}; 90};
90 91
@@ -124,6 +125,8 @@ struct sway_container {
124 double saved_x, saved_y; 125 double saved_x, saved_y;
125 double saved_width, saved_height; 126 double saved_width, saved_height;
126 127
128 bool is_fullscreen;
129
127 // The gaps currently applied to the container. 130 // The gaps currently applied to the container.
128 double current_gaps; 131 double current_gaps;
129 132
@@ -135,6 +138,11 @@ struct sway_container {
135 138
136 struct sway_container *parent; 139 struct sway_container *parent;
137 140
141 // Indicates that the container is a scratchpad container.
142 // Both hidden and visible scratchpad containers have scratchpad=true.
143 // Hidden scratchpad containers have a NULL parent.
144 bool scratchpad;
145
138 float alpha; 146 float alpha;
139 147
140 struct wlr_texture *title_focused; 148 struct wlr_texture *title_focused;
@@ -222,16 +230,13 @@ struct sway_container *container_parent(struct sway_container *container,
222 * surface-local coordinates of the given layout coordinates if the container 230 * surface-local coordinates of the given layout coordinates if the container
223 * is a view and the view contains a surface at those coordinates. 231 * is a view and the view contains a surface at those coordinates.
224 */ 232 */
225struct sway_container *container_at(struct sway_container *container, 233struct sway_container *container_at(struct sway_container *workspace,
226 double ox, double oy, struct wlr_surface **surface, 234 double lx, double ly, struct wlr_surface **surface,
227 double *sx, double *sy); 235 double *sx, double *sy);
228 236
229/** 237struct sway_container *container_at_view(struct sway_container *view,
230 * Same as container_at, but only checks floating views and expects coordinates 238 double lx, double ly, struct wlr_surface **surface,
231 * to be layout coordinates, as that's what floating views use. 239 double *sx, double *sy);
232 */
233struct sway_container *floating_container_at(double lx, double ly,
234 struct wlr_surface **surface, double *sx, double *sy);
235 240
236/** 241/**
237 * Apply the function for each descendant of the container breadth first. 242 * Apply the function for each descendant of the container breadth first.
@@ -262,6 +267,8 @@ int container_count_descendants_of_type(struct sway_container *con,
262 267
263void container_create_notify(struct sway_container *container); 268void container_create_notify(struct sway_container *container);
264 269
270void container_update_textures_recursive(struct sway_container *con);
271
265void container_damage_whole(struct sway_container *container); 272void container_damage_whole(struct sway_container *container);
266 273
267bool container_reap_empty(struct sway_container *con); 274bool container_reap_empty(struct sway_container *con);
@@ -289,6 +296,11 @@ void container_notify_subtree_changed(struct sway_container *container);
289 */ 296 */
290size_t container_titlebar_height(void); 297size_t container_titlebar_height(void);
291 298
299/**
300 * Resize and center the container in its workspace.
301 */
302void container_init_floating(struct sway_container *container);
303
292void container_set_floating(struct sway_container *container, bool enable); 304void container_set_floating(struct sway_container *container, bool enable);
293 305
294void container_set_geometry_from_floating_view(struct sway_container *con); 306void container_set_geometry_from_floating_view(struct sway_container *con);
@@ -305,6 +317,12 @@ bool container_is_floating(struct sway_container *container);
305void container_get_box(struct sway_container *container, struct wlr_box *box); 317void container_get_box(struct sway_container *container, struct wlr_box *box);
306 318
307/** 319/**
320 * Move a floating container by the specified amount.
321 */
322void container_floating_translate(struct sway_container *con,
323 double x_amount, double y_amount);
324
325/**
308 * Move a floating container to a new layout-local position. 326 * Move a floating container to a new layout-local position.
309 */ 327 */
310void container_floating_move_to(struct sway_container *con, 328void container_floating_move_to(struct sway_container *con,
@@ -318,4 +336,32 @@ void container_set_dirty(struct sway_container *container);
318 336
319bool container_has_urgent_child(struct sway_container *container); 337bool container_has_urgent_child(struct sway_container *container);
320 338
339/**
340 * If the container is involved in a drag or resize operation via a mouse, this
341 * ends the operation.
342 */
343void container_end_mouse_operation(struct sway_container *container);
344
345void container_set_fullscreen(struct sway_container *container, bool enable);
346
347/**
348 * Return true if the container is floating, or a child of a floating split
349 * container.
350 */
351bool container_is_floating_or_child(struct sway_container *container);
352
353/**
354 * Return true if the container is fullscreen, or a child of a fullscreen split
355 * container.
356 */
357bool container_is_fullscreen_or_child(struct sway_container *container);
358
359/**
360 * Wrap the children of parent in a new container. The new container will be the
361 * only child of parent.
362 *
363 * The new container is returned.
364 */
365struct sway_container *container_wrap_children(struct sway_container *parent);
366
321#endif 367#endif
diff --git a/include/sway/tree/layout.h b/include/sway/tree/layout.h
index ba265623..a4c31bf6 100644
--- a/include/sway/tree/layout.h
+++ b/include/sway/tree/layout.h
@@ -3,6 +3,7 @@
3#include <wlr/types/wlr_output_layout.h> 3#include <wlr/types/wlr_output_layout.h>
4#include <wlr/render/wlr_texture.h> 4#include <wlr/render/wlr_texture.h>
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "config.h"
6 7
7enum movement_direction { 8enum movement_direction {
8 MOVE_LEFT, 9 MOVE_LEFT,
@@ -14,10 +15,11 @@ enum movement_direction {
14}; 15};
15 16
16enum resize_edge { 17enum resize_edge {
17 RESIZE_EDGE_LEFT, 18 RESIZE_EDGE_NONE = 0,
18 RESIZE_EDGE_RIGHT, 19 RESIZE_EDGE_LEFT = 1,
19 RESIZE_EDGE_TOP, 20 RESIZE_EDGE_RIGHT = 2,
20 RESIZE_EDGE_BOTTOM, 21 RESIZE_EDGE_TOP = 4,
22 RESIZE_EDGE_BOTTOM = 8,
21}; 23};
22 24
23struct sway_container; 25struct sway_container;
@@ -26,14 +28,17 @@ struct sway_root {
26 struct wlr_output_layout *output_layout; 28 struct wlr_output_layout *output_layout;
27 29
28 struct wl_listener output_layout_change; 30 struct wl_listener output_layout_change;
29 31#ifdef HAVE_XWAYLAND
30 struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link 32 struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link
33#endif
31 struct wl_list drag_icons; // sway_drag_icon::link 34 struct wl_list drag_icons; // sway_drag_icon::link
32 35
33 struct wlr_texture *debug_tree; 36 struct wlr_texture *debug_tree;
34 37
35 struct wl_list outputs; // sway_output::link 38 struct wl_list outputs; // sway_output::link
36 39
40 list_t *scratchpad; // struct sway_container
41
37 struct { 42 struct {
38 struct wl_signal new_container; 43 struct wl_signal new_container;
39 } events; 44 } events;
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 068d92c6..37fd02bc 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -3,7 +3,10 @@
3#include <wayland-server.h> 3#include <wayland-server.h>
4#include <wlr/types/wlr_surface.h> 4#include <wlr/types/wlr_surface.h>
5#include <wlr/types/wlr_xdg_shell_v6.h> 5#include <wlr/types/wlr_xdg_shell_v6.h>
6#include "config.h"
7#ifdef HAVE_XWAYLAND
6#include <wlr/xwayland.h> 8#include <wlr/xwayland.h>
9#endif
7#include "sway/input/input-manager.h" 10#include "sway/input/input-manager.h"
8#include "sway/input/seat.h" 11#include "sway/input/seat.h"
9 12
@@ -12,7 +15,9 @@ struct sway_container;
12enum sway_view_type { 15enum sway_view_type {
13 SWAY_VIEW_XDG_SHELL_V6, 16 SWAY_VIEW_XDG_SHELL_V6,
14 SWAY_VIEW_XDG_SHELL, 17 SWAY_VIEW_XDG_SHELL,
18#ifdef HAVE_XWAYLAND
15 SWAY_VIEW_XWAYLAND, 19 SWAY_VIEW_XWAYLAND,
20#endif
16}; 21};
17 22
18enum sway_view_prop { 23enum sway_view_prop {
@@ -22,10 +27,14 @@ enum sway_view_prop {
22 VIEW_PROP_INSTANCE, 27 VIEW_PROP_INSTANCE,
23 VIEW_PROP_WINDOW_TYPE, 28 VIEW_PROP_WINDOW_TYPE,
24 VIEW_PROP_WINDOW_ROLE, 29 VIEW_PROP_WINDOW_ROLE,
30#ifdef HAVE_XWAYLAND
25 VIEW_PROP_X11_WINDOW_ID, 31 VIEW_PROP_X11_WINDOW_ID,
32#endif
26}; 33};
27 34
28struct sway_view_impl { 35struct sway_view_impl {
36 void (*get_constraints)(struct sway_view *view, double *min_width,
37 double *max_width, double *min_height, double *max_height);
29 const char *(*get_string_prop)(struct sway_view *view, 38 const char *(*get_string_prop)(struct sway_view *view,
30 enum sway_view_prop prop); 39 enum sway_view_prop prop);
31 uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop); 40 uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop);
@@ -38,7 +47,10 @@ struct sway_view_impl {
38 bool (*has_client_side_decorations)(struct sway_view *view); 47 bool (*has_client_side_decorations)(struct sway_view *view);
39 void (*for_each_surface)(struct sway_view *view, 48 void (*for_each_surface)(struct sway_view *view,
40 wlr_surface_iterator_func_t iterator, void *user_data); 49 wlr_surface_iterator_func_t iterator, void *user_data);
50 void (*for_each_popup)(struct sway_view *view,
51 wlr_surface_iterator_func_t iterator, void *user_data);
41 void (*close)(struct sway_view *view); 52 void (*close)(struct sway_view *view);
53 void (*close_popups)(struct sway_view *view);
42 void (*destroy)(struct sway_view *view); 54 void (*destroy)(struct sway_view *view);
43}; 55};
44 56
@@ -60,8 +72,6 @@ struct sway_view {
60 // Used when changing a view from tiled to floating. 72 // Used when changing a view from tiled to floating.
61 int natural_width, natural_height; 73 int natural_width, natural_height;
62 74
63 bool is_fullscreen;
64
65 char *title_format; 75 char *title_format;
66 enum sway_container_border border; 76 enum sway_container_border border;
67 int border_thickness; 77 int border_thickness;
@@ -75,6 +85,9 @@ struct sway_view {
75 bool allow_request_urgent; 85 bool allow_request_urgent;
76 struct wl_event_source *urgent_timer; 86 struct wl_event_source *urgent_timer;
77 87
88 struct wlr_buffer *saved_buffer;
89 int saved_buffer_width, saved_buffer_height;
90
78 bool destroying; 91 bool destroying;
79 92
80 list_t *executed_criteria; // struct criteria * 93 list_t *executed_criteria; // struct criteria *
@@ -88,7 +101,9 @@ struct sway_view {
88 union { 101 union {
89 struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; 102 struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6;
90 struct wlr_xdg_surface *wlr_xdg_surface; 103 struct wlr_xdg_surface *wlr_xdg_surface;
104#ifdef HAVE_XWAYLAND
91 struct wlr_xwayland_surface *wlr_xwayland_surface; 105 struct wlr_xwayland_surface *wlr_xwayland_surface;
106#endif
92 struct wlr_wl_shell_surface *wlr_wl_shell_surface; 107 struct wlr_wl_shell_surface *wlr_wl_shell_surface;
93 }; 108 };
94 109
@@ -108,6 +123,8 @@ struct sway_xdg_shell_v6_view {
108 struct wl_listener request_resize; 123 struct wl_listener request_resize;
109 struct wl_listener request_maximize; 124 struct wl_listener request_maximize;
110 struct wl_listener request_fullscreen; 125 struct wl_listener request_fullscreen;
126 struct wl_listener set_title;
127 struct wl_listener set_app_id;
111 struct wl_listener new_popup; 128 struct wl_listener new_popup;
112 struct wl_listener map; 129 struct wl_listener map;
113 struct wl_listener unmap; 130 struct wl_listener unmap;
@@ -122,12 +139,14 @@ struct sway_xdg_shell_view {
122 struct wl_listener request_resize; 139 struct wl_listener request_resize;
123 struct wl_listener request_maximize; 140 struct wl_listener request_maximize;
124 struct wl_listener request_fullscreen; 141 struct wl_listener request_fullscreen;
142 struct wl_listener set_title;
143 struct wl_listener set_app_id;
125 struct wl_listener new_popup; 144 struct wl_listener new_popup;
126 struct wl_listener map; 145 struct wl_listener map;
127 struct wl_listener unmap; 146 struct wl_listener unmap;
128 struct wl_listener destroy; 147 struct wl_listener destroy;
129}; 148};
130 149#ifdef HAVE_XWAYLAND
131struct sway_xwayland_view { 150struct sway_xwayland_view {
132 struct sway_view view; 151 struct sway_view view;
133 152
@@ -159,7 +178,7 @@ struct sway_xwayland_unmanaged {
159 struct wl_listener unmap; 178 struct wl_listener unmap;
160 struct wl_listener destroy; 179 struct wl_listener destroy;
161}; 180};
162 181#endif
163struct sway_view_child; 182struct sway_view_child;
164 183
165struct sway_view_child_impl { 184struct sway_view_child_impl {
@@ -215,15 +234,13 @@ uint32_t view_get_window_type(struct sway_view *view);
215 234
216const char *view_get_shell(struct sway_view *view); 235const char *view_get_shell(struct sway_view *view);
217 236
237void view_get_constraints(struct sway_view *view, double *min_width,
238 double *max_width, double *min_height, double *max_height);
239
218uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, 240uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
219 int height); 241 int height);
220 242
221/** 243/**
222 * Center the view in its workspace and build the swayc decorations around it.
223 */
224void view_init_floating(struct sway_view *view);
225
226/**
227 * Configure the view's position and size based on the swayc's position and 244 * Configure the view's position and size based on the swayc's position and
228 * size, taking borders into consideration. 245 * size, taking borders into consideration.
229 */ 246 */
@@ -233,17 +250,24 @@ void view_set_activated(struct sway_view *view, bool activated);
233 250
234void view_set_tiled(struct sway_view *view, bool tiled); 251void view_set_tiled(struct sway_view *view, bool tiled);
235 252
236void view_set_fullscreen_raw(struct sway_view *view, bool fullscreen);
237
238void view_set_fullscreen(struct sway_view *view, bool fullscreen);
239
240void view_close(struct sway_view *view); 253void view_close(struct sway_view *view);
241 254
255void view_close_popups(struct sway_view *view);
256
242void view_damage_from(struct sway_view *view); 257void view_damage_from(struct sway_view *view);
243 258
259/**
260 * Iterate all surfaces of a view (toplevels + popups).
261 */
244void view_for_each_surface(struct sway_view *view, 262void view_for_each_surface(struct sway_view *view,
245 wlr_surface_iterator_func_t iterator, void *user_data); 263 wlr_surface_iterator_func_t iterator, void *user_data);
246 264
265/**
266 * Iterate all popups recursively.
267 */
268void view_for_each_popup(struct sway_view *view,
269 wlr_surface_iterator_func_t iterator, void *user_data);
270
247// view implementation 271// view implementation
248 272
249void view_init(struct sway_view *view, enum sway_view_type type, 273void view_init(struct sway_view *view, enum sway_view_type type,
@@ -272,9 +296,10 @@ struct sway_view *view_from_wlr_xdg_surface(
272 struct wlr_xdg_surface *xdg_surface); 296 struct wlr_xdg_surface *xdg_surface);
273struct sway_view *view_from_wlr_xdg_surface_v6( 297struct sway_view *view_from_wlr_xdg_surface_v6(
274 struct wlr_xdg_surface_v6 *xdg_surface_v6); 298 struct wlr_xdg_surface_v6 *xdg_surface_v6);
299#ifdef HAVE_XWAYLAND
275struct sway_view *view_from_wlr_xwayland_surface( 300struct sway_view *view_from_wlr_xwayland_surface(
276 struct wlr_xwayland_surface *xsurface); 301 struct wlr_xwayland_surface *xsurface);
277 302#endif
278struct sway_view *view_from_wlr_surface(struct wlr_surface *surface); 303struct sway_view *view_from_wlr_surface(struct wlr_surface *surface);
279 304
280/** 305/**
@@ -303,6 +328,8 @@ void view_clear_marks(struct sway_view *view);
303 328
304bool view_has_mark(struct sway_view *view, char *mark); 329bool view_has_mark(struct sway_view *view, char *mark);
305 330
331void view_add_mark(struct sway_view *view, char *mark);
332
306void view_update_marks_textures(struct sway_view *view); 333void view_update_marks_textures(struct sway_view *view);
307 334
308/** 335/**
@@ -315,4 +342,8 @@ void view_set_urgent(struct sway_view *view, bool enable);
315 342
316bool view_is_urgent(struct sway_view *view); 343bool view_is_urgent(struct sway_view *view);
317 344
345void view_remove_saved_buffer(struct sway_view *view);
346
347void view_save_buffer(struct sway_view *view);
348
318#endif 349#endif
diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h
index bc95317a..5ae0ae3a 100644
--- a/include/sway/tree/workspace.h
+++ b/include/sway/tree/workspace.h
@@ -7,7 +7,7 @@ struct sway_view;
7 7
8struct sway_workspace { 8struct sway_workspace {
9 struct sway_container *swayc; 9 struct sway_container *swayc;
10 struct sway_view *fullscreen; 10 struct sway_container *fullscreen;
11 struct sway_container *floating; 11 struct sway_container *floating;
12 list_t *output_priority; 12 list_t *output_priority;
13 bool urgent; 13 bool urgent;
@@ -44,6 +44,10 @@ void workspace_output_add_priority(struct sway_container *workspace,
44struct sway_container *workspace_output_get_highest_available( 44struct sway_container *workspace_output_get_highest_available(
45 struct sway_container *ws, struct sway_container *exclude); 45 struct sway_container *ws, struct sway_container *exclude);
46 46
47struct sway_container *workspace_for_pid(pid_t pid);
48
49void workspace_record_pid(pid_t pid);
50
47void workspace_detect_urgent(struct sway_container *workspace); 51void workspace_detect_urgent(struct sway_container *workspace);
48 52
49#endif 53#endif
diff --git a/include/swaygrab/json.h b/include/swaygrab/json.h
deleted file mode 100644
index c1093ef1..00000000
--- a/include/swaygrab/json.h
+++ /dev/null
@@ -1,10 +0,0 @@
1#include <json-c/json.h>
2#include "wlc/wlc.h"
3
4void init_json_tree(int socketfd);
5void free_json_tree();
6char *get_focused_output();
7char *create_payload(const char *output, struct wlc_geometry *g);
8struct wlc_geometry *get_container_geometry(json_object *container);
9json_object *get_focused_container();
10json_object *get_output_container(const char *output);
diff --git a/include/swaynag/config.h b/include/swaynag/config.h
new file mode 100644
index 00000000..0d8889de
--- /dev/null
+++ b/include/swaynag/config.h
@@ -0,0 +1,13 @@
1#ifndef _SWAYNAG_CONFIG_H
2#define _SWAYNAG_CONFIG_H
3#include "swaynag/swaynag.h"
4#include "list.h"
5
6int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
7 list_t *types, struct swaynag_type *type, char **config, bool *debug);
8
9char *swaynag_get_config_path(void);
10
11int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types);
12
13#endif
diff --git a/include/swaynag/render.h b/include/swaynag/render.h
new file mode 100644
index 00000000..d09e5929
--- /dev/null
+++ b/include/swaynag/render.h
@@ -0,0 +1,7 @@
1#ifndef _SWAYNAG_RENDER_H
2#define _SWAYNAG_RENDER_H
3#include "swaynag/swaynag.h"
4
5void render_frame(struct swaynag *swaynag);
6
7#endif
diff --git a/include/swaynag/swaynag.h b/include/swaynag/swaynag.h
new file mode 100644
index 00000000..1bf8b640
--- /dev/null
+++ b/include/swaynag/swaynag.h
@@ -0,0 +1,100 @@
1#ifndef _SWAYNAG_SWAYNAG_H
2#define _SWAYNAG_SWAYNAG_H
3#include <stdint.h>
4#include <strings.h>
5#include "list.h"
6#include "pool-buffer.h"
7#include "swaynag/types.h"
8#include "xdg-output-unstable-v1-client-protocol.h"
9
10#define SWAYNAG_MAX_HEIGHT 500
11
12struct swaynag;
13
14enum swaynag_action_type {
15 SWAYNAG_ACTION_DISMISS,
16 SWAYNAG_ACTION_EXPAND,
17 SWAYNAG_ACTION_COMMAND,
18};
19
20struct swaynag_pointer {
21 struct wl_pointer *pointer;
22 uint32_t serial;
23 struct wl_cursor_theme *cursor_theme;
24 struct wl_cursor_image *cursor_image;
25 struct wl_surface *cursor_surface;
26 int x;
27 int y;
28};
29
30struct swaynag_output {
31 char *name;
32 struct wl_output *wl_output;
33 uint32_t wl_name;
34 uint32_t scale;
35 struct swaynag *swaynag;
36 struct wl_list link;
37};
38
39struct swaynag_button {
40 char *text;
41 enum swaynag_action_type type;
42 char *action;
43 int x;
44 int y;
45 int width;
46 int height;
47};
48
49struct swaynag_details {
50 bool visible;
51 char *message;
52
53 int x;
54 int y;
55 int width;
56 int height;
57
58 int offset;
59 int visible_lines;
60 int total_lines;
61 struct swaynag_button button_details;
62 struct swaynag_button button_up;
63 struct swaynag_button button_down;
64};
65
66struct swaynag {
67 bool run_display;
68 int querying_outputs;
69
70 struct wl_display *display;
71 struct wl_compositor *compositor;
72 struct wl_seat *seat;
73 struct wl_shm *shm;
74 struct swaynag_pointer pointer;
75 struct zxdg_output_manager_v1 *xdg_output_manager;
76 struct wl_list outputs; // swaynag_output::link
77 struct swaynag_output *output;
78 struct zwlr_layer_shell_v1 *layer_shell;
79 struct zwlr_layer_surface_v1 *layer_surface;
80 struct wl_surface *surface;
81
82 uint32_t width;
83 uint32_t height;
84 int32_t scale;
85 struct pool_buffer buffers[2];
86 struct pool_buffer *current_buffer;
87
88 struct swaynag_type *type;
89 char *message;
90 list_t *buttons;
91 struct swaynag_details details;
92};
93
94void swaynag_setup(struct swaynag *swaynag);
95
96void swaynag_run(struct swaynag *swaynag);
97
98void swaynag_destroy(struct swaynag *swaynag);
99
100#endif
diff --git a/include/swaynag/types.h b/include/swaynag/types.h
new file mode 100644
index 00000000..2183ce22
--- /dev/null
+++ b/include/swaynag/types.h
@@ -0,0 +1,39 @@
1#ifndef _SWAYNAG_TYPES_H
2#define _SWAYNAG_TYPES_H
3
4struct swaynag_type {
5 char *name;
6
7 char *font;
8 char *output;
9 uint32_t anchors;
10
11 uint32_t button_background;
12 uint32_t background;
13 uint32_t text;
14 uint32_t border;
15 uint32_t border_bottom;
16
17 uint32_t bar_border_thickness;
18 uint32_t message_padding;
19 uint32_t details_border_thickness;
20 uint32_t button_border_thickness;
21 uint32_t button_gap;
22 uint32_t button_gap_close;
23 uint32_t button_margin_right;
24 uint32_t button_padding;
25};
26
27void swaynag_types_add_default(list_t *types);
28
29struct swaynag_type *swaynag_type_get(list_t *types, char *name);
30
31struct swaynag_type *swaynag_type_clone(struct swaynag_type *type);
32
33void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src);
34
35void swaynag_type_free(struct swaynag_type *type);
36
37void swaynag_types_free(list_t *types);
38
39#endif
diff --git a/include/util.h b/include/util.h
index f68deae8..9277fa6e 100644
--- a/include/util.h
+++ b/include/util.h
@@ -2,6 +2,7 @@
2#define _SWAY_UTIL_H 2#define _SWAY_UTIL_H
3 3
4#include <stdint.h> 4#include <stdint.h>
5#include <stdbool.h>
5#include <unistd.h> 6#include <unistd.h>
6#include <sys/types.h> 7#include <sys/types.h>
7#include <xkbcommon/xkbcommon.h> 8#include <xkbcommon/xkbcommon.h>
@@ -51,6 +52,14 @@ pid_t get_parent_pid(pid_t pid);
51uint32_t parse_color(const char *color); 52uint32_t parse_color(const char *color);
52 53
53/** 54/**
55 * Given a string that represents a boolean, return the boolean value. This
56 * function also takes in the current boolean value to support toggling. If
57 * toggling is not desired, pass in true for current so that toggling values
58 * get parsed as not true.
59 */
60bool parse_boolean(const char *boolean, bool current);
61
62/**
54 * Given a path string, recurseively resolves any symlinks to their targets 63 * Given a path string, recurseively resolves any symlinks to their targets
55 * (which may be a file, directory) and returns the result. 64 * (which may be a file, directory) and returns the result.
56 * argument is returned. Caller must free the returned buffer. 65 * argument is returned. Caller must free the returned buffer.
diff --git a/meson.build b/meson.build
index 1d40581a..2a020323 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@ project(
12add_project_arguments('-Wno-unused-parameter', language: 'c') 12add_project_arguments('-Wno-unused-parameter', language: 'c')
13add_project_arguments('-Wno-unused-function', language: 'c') 13add_project_arguments('-Wno-unused-function', language: 'c')
14add_project_arguments('-Wno-unused-result', language: 'c') 14add_project_arguments('-Wno-unused-result', language: 'c')
15add_project_arguments('-DWLR_USE_UNSTABLE', language: 'c')
15 16
16cc = meson.get_compiler('c') 17cc = meson.get_compiler('c')
17 18
@@ -43,11 +44,17 @@ systemd = dependency('libsystemd', required: false)
43elogind = dependency('libelogind', required: false) 44elogind = dependency('libelogind', required: false)
44math = cc.find_library('m') 45math = cc.find_library('m')
45rt = cc.find_library('rt') 46rt = cc.find_library('rt')
46xcb = dependency('xcb')
47git = find_program('git', required: false) 47git = find_program('git', required: false)
48 48
49conf_data = configuration_data() 49conf_data = configuration_data()
50 50
51if get_option('enable-xwayland')
52 conf_data.set('HAVE_XWAYLAND', true)
53 xcb = dependency('xcb')
54else
55 conf_data.set('HAVE_XWAYLAND', false)
56endif
57
51if gdk_pixbuf.found() 58if gdk_pixbuf.found()
52 conf_data.set('HAVE_GDK_PIXBUF', true) 59 conf_data.set('HAVE_GDK_PIXBUF', true)
53endif 60endif
@@ -75,6 +82,8 @@ if scdoc.found()
75 'swaylock/swaylock.1.scd', 82 'swaylock/swaylock.1.scd',
76 'swaymsg/swaymsg.1.scd', 83 'swaymsg/swaymsg.1.scd',
77 'swayidle/swayidle.1.scd', 84 'swayidle/swayidle.1.scd',
85 'swaynag/swaynag.1.scd',
86 'swaynag/swaynag.5.scd',
78 ] 87 ]
79 foreach filename : man_files 88 foreach filename : man_files
80 topic = filename.split('.')[-3].split('/')[-1] 89 topic = filename.split('.')[-3].split('/')[-1]
@@ -123,6 +132,7 @@ subdir('swaybg')
123subdir('swaybar') 132subdir('swaybar')
124subdir('swaylock') 133subdir('swaylock')
125subdir('swayidle') 134subdir('swayidle')
135subdir('swaynag')
126 136
127config = configuration_data() 137config = configuration_data()
128config.set('sysconfdir', join_paths(prefix, sysconfdir)) 138config.set('sysconfdir', join_paths(prefix, sysconfdir))
@@ -176,7 +186,6 @@ endif
176if (get_option('zsh_completions')) 186if (get_option('zsh_completions'))
177 zsh_files = files( 187 zsh_files = files(
178 'completions/zsh/_sway', 188 'completions/zsh/_sway',
179 'completions/zsh/_swaygrab',
180 'completions/zsh/_swaylock', 189 'completions/zsh/_swaylock',
181 'completions/zsh/_swaymsg', 190 'completions/zsh/_swaymsg',
182 ) 191 )
@@ -184,3 +193,15 @@ if (get_option('zsh_completions'))
184 193
185 install_data(zsh_files, install_dir: zsh_install_dir) 194 install_data(zsh_files, install_dir: zsh_install_dir)
186endif 195endif
196
197if (get_option('bash_completions'))
198 bash_files = files(
199 'completions/bash/sway',
200 'completions/bash/swayidle',
201 'completions/bash/swaylock',
202 'completions/bash/swaymsg',
203 )
204 bash_install_dir = datadir + '/bash-completion/completions'
205
206 install_data(bash_files, install_dir: bash_install_dir)
207endif
diff --git a/meson_options.txt b/meson_options.txt
index 541ccf13..7a23c206 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,5 @@
1option('sway_version', type : 'string', description: 'The version string reported in `sway --version`.') 1option('sway_version', type : 'string', description: 'The version string reported in `sway --version`.')
2option('default_wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.') 2option('default_wallpaper', type: 'boolean', value: true, description: 'Install the default wallpaper.')
3option('zsh_completions', type: 'boolean', value: true, description: 'Install zsh shell completions.') 3option('zsh_completions', type: 'boolean', value: true, description: 'Install zsh shell completions.')
4option('bash_completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
5option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications')
diff --git a/security.d/00-defaults.in b/security.d/00-defaults.in
index e4626477..be7b9d06 100644
--- a/security.d/00-defaults.in
+++ b/security.d/00-defaults.in
@@ -12,7 +12,6 @@
12permit * fullscreen keyboard mouse 12permit * fullscreen keyboard mouse
13permit @prefix@/bin/swaylock lock 13permit @prefix@/bin/swaylock lock
14permit @prefix@/bin/swaybg background 14permit @prefix@/bin/swaybg background
15permit @prefix@/bin/swaygrab screenshot
16permit @prefix@/bin/swaybar panel 15permit @prefix@/bin/swaybar panel
17 16
18# Configures enabled IPC features for specific programs 17# Configures enabled IPC features for specific programs
@@ -36,11 +35,6 @@ ipc @prefix@/bin/swaybar {
36 } 35 }
37} 36}
38 37
39ipc @prefix@/bin/swaygrab {
40 outputs enabled
41 tree enabled
42}
43
44ipc @prefix@/bin/swaylock { 38ipc @prefix@/bin/swaylock {
45 outputs enabled 39 outputs enabled
46} 40}
diff --git a/sway/commands.c b/sway/commands.c
index f1f03574..fdae1961 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -103,6 +103,7 @@ static struct cmd_handler handlers[] = {
103 { "exec_always", cmd_exec_always }, 103 { "exec_always", cmd_exec_always },
104 { "floating_maximum_size", cmd_floating_maximum_size }, 104 { "floating_maximum_size", cmd_floating_maximum_size },
105 { "floating_minimum_size", cmd_floating_minimum_size }, 105 { "floating_minimum_size", cmd_floating_minimum_size },
106 { "floating_modifier", cmd_floating_modifier },
106 { "focus", cmd_focus }, 107 { "focus", cmd_focus },
107 { "focus_follows_mouse", cmd_focus_follows_mouse }, 108 { "focus_follows_mouse", cmd_focus_follows_mouse },
108 { "focus_wrapping", cmd_focus_wrapping }, 109 { "focus_wrapping", cmd_focus_wrapping },
@@ -148,6 +149,7 @@ static struct cmd_handler command_handlers[] = {
148 { "reload", cmd_reload }, 149 { "reload", cmd_reload },
149 { "rename", cmd_rename }, 150 { "rename", cmd_rename },
150 { "resize", cmd_resize }, 151 { "resize", cmd_resize },
152 { "scratchpad", cmd_scratchpad },
151 { "split", cmd_split }, 153 { "split", cmd_split },
152 { "splith", cmd_splith }, 154 { "splith", cmd_splith },
153 { "splitt", cmd_splitt }, 155 { "splitt", cmd_splitt },
@@ -325,7 +327,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
325 } while(head); 327 } while(head);
326cleanup: 328cleanup:
327 free(exec); 329 free(exec);
328 free(views); 330 list_free(views);
329 if (!results) { 331 if (!results) {
330 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 332 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
331 } 333 }
diff --git a/sway/commands/bind.c b/sway/commands/bind.c
index 83e9e432..8270b958 100644
--- a/sway/commands/bind.c
+++ b/sway/commands/bind.c
@@ -1,3 +1,4 @@
1#define _XOPEN_SOURCE 500
1#ifdef __linux__ 2#ifdef __linux__
2#include <linux/input-event-codes.h> 3#include <linux/input-event-codes.h>
3#elif __FreeBSD__ 4#elif __FreeBSD__
@@ -5,9 +6,11 @@
5#endif 6#endif
6#include <xkbcommon/xkbcommon.h> 7#include <xkbcommon/xkbcommon.h>
7#include <xkbcommon/xkbcommon-names.h> 8#include <xkbcommon/xkbcommon-names.h>
9#include <string.h>
8#include <strings.h> 10#include <strings.h>
9#include "sway/commands.h" 11#include "sway/commands.h"
10#include "sway/config.h" 12#include "sway/config.h"
13#include "sway/ipc-server.h"
11#include "list.h" 14#include "list.h"
12#include "log.h" 15#include "log.h"
13#include "stringop.h" 16#include "stringop.h"
@@ -27,6 +30,33 @@ void free_sway_binding(struct sway_binding *binding) {
27 free(binding); 30 free(binding);
28} 31}
29 32
33static struct sway_binding *sway_binding_dup(struct sway_binding *sb) {
34 struct sway_binding *new_sb = calloc(1, sizeof(struct sway_binding));
35 if (!new_sb) {
36 return NULL;
37 }
38
39 new_sb->type = sb->type;
40 new_sb->order = sb->order;
41 new_sb->flags = sb->flags;
42 new_sb->modifiers = sb->modifiers;
43 new_sb->command = strdup(sb->command);
44
45 new_sb->keys = create_list();
46 int i;
47 for (i = 0; i < sb->keys->length; ++i) {
48 xkb_keysym_t *key = malloc(sizeof(xkb_keysym_t));
49 if (!key) {
50 free_sway_binding(new_sb);
51 return NULL;
52 }
53 *key = *(xkb_keysym_t *)sb->keys->items[i];
54 list_add(new_sb->keys, key);
55 }
56
57 return new_sb;
58}
59
30/** 60/**
31 * Returns true if the bindings have the same key and modifier combinations. 61 * Returns true if the bindings have the same key and modifier combinations.
32 * Note that keyboard layout is not considered, so the bindings might actually 62 * Note that keyboard layout is not considered, so the bindings might actually
@@ -34,11 +64,14 @@ void free_sway_binding(struct sway_binding *binding) {
34 */ 64 */
35static bool binding_key_compare(struct sway_binding *binding_a, 65static bool binding_key_compare(struct sway_binding *binding_a,
36 struct sway_binding *binding_b) { 66 struct sway_binding *binding_b) {
37 if (binding_a->release != binding_b->release) { 67 if (binding_a->type != binding_b->type) {
38 return false; 68 return false;
39 } 69 }
40 70
41 if (binding_a->bindcode != binding_b->bindcode) { 71 uint32_t conflict_generating_flags = BINDING_RELEASE | BINDING_BORDER
72 | BINDING_CONTENTS | BINDING_TITLEBAR;
73 if ((binding_a->flags & conflict_generating_flags) !=
74 (binding_b->flags & conflict_generating_flags)) {
42 return false; 75 return false;
43 } 76 }
44 77
@@ -69,6 +102,66 @@ static int key_qsort_cmp(const void *keyp_a, const void *keyp_b) {
69 return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0); 102 return (key_a < key_b) ? -1 : ((key_a > key_b) ? 1 : 0);
70} 103}
71 104
105
106/**
107 * From a keycode, bindcode, or bindsym name and the most likely binding type,
108 * identify the appropriate numeric value corresponding to the key. Return NULL
109 * and set *key_val if successful, otherwise return a specific error. Change
110 * the value of *type if the initial type guess was incorrect and if this
111 * was the first identified key.
112 */
113static struct cmd_results *identify_key(const char* name, bool first_key,
114 uint32_t* key_val, enum binding_input_type* type) {
115 if (*type == BINDING_KEYCODE) {
116 // check for keycode
117 xkb_keycode_t keycode = strtol(name, NULL, 10);
118 if (!xkb_keycode_is_legal_ext(keycode)) {
119 return cmd_results_new(CMD_INVALID, "bindcode",
120 "Invalid keycode '%s'", name);
121 }
122 *key_val = keycode;
123 } else {
124 // check for keysym
125 xkb_keysym_t keysym = xkb_keysym_from_name(name,
126 XKB_KEYSYM_CASE_INSENSITIVE);
127
128 // Check for mouse binding
129 uint32_t button = 0;
130 if (strncasecmp(name, "button", strlen("button")) == 0 &&
131 strlen(name) == strlen("button0")) {
132 button = name[strlen("button")] - '1' + BTN_LEFT;
133 }
134
135 if (*type == BINDING_KEYSYM) {
136 if (button) {
137 if (first_key) {
138 *type = BINDING_MOUSE;
139 *key_val = button;
140 } else {
141 return cmd_results_new(CMD_INVALID, "bindsym",
142 "Mixed button '%s' into key sequence", name);
143 }
144 } else if (keysym) {
145 *key_val = keysym;
146 } else {
147 return cmd_results_new(CMD_INVALID, "bindsym",
148 "Unknown key '%s'", name);
149 }
150 } else {
151 if (button) {
152 *key_val = button;
153 } else if (keysym) {
154 return cmd_results_new(CMD_INVALID, "bindsym",
155 "Mixed keysym '%s' into button sequence", name);
156 } else {
157 return cmd_results_new(CMD_INVALID, "bindsym",
158 "Unknown button '%s'", name);
159 }
160 }
161 }
162 return NULL;
163}
164
72static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv, 165static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
73 bool bindcode) { 166 bool bindcode) {
74 const char *bindtype = bindcode ? "bindcode" : "bindsym"; 167 const char *bindtype = bindcode ? "bindcode" : "bindsym";
@@ -85,22 +178,34 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
85 } 178 }
86 binding->keys = create_list(); 179 binding->keys = create_list();
87 binding->modifiers = 0; 180 binding->modifiers = 0;
88 binding->release = false; 181 binding->flags = 0;
89 binding->locked = false; 182 binding->type = bindcode ? BINDING_KEYCODE : BINDING_KEYSYM;
90 binding->bindcode = bindcode; 183
184 bool exclude_titlebar = false;
91 185
92 // Handle --release and --locked 186 // Handle --release and --locked
93 while (argc > 0) { 187 while (argc > 0) {
94 if (strcmp("--release", argv[0]) == 0) { 188 if (strcmp("--release", argv[0]) == 0) {
95 binding->release = true; 189 binding->flags |= BINDING_RELEASE;
96 } else if (strcmp("--locked", argv[0]) == 0) { 190 } else if (strcmp("--locked", argv[0]) == 0) {
97 binding->locked = true; 191 binding->flags |= BINDING_LOCKED;
192 } else if (strcmp("--whole-window", argv[0]) == 0) {
193 binding->flags |= BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR;
194 } else if (strcmp("--border", argv[0]) == 0) {
195 binding->flags |= BINDING_BORDER;
196 } else if (strcmp("--exclude-titlebar", argv[0]) == 0) {
197 exclude_titlebar = true;
98 } else { 198 } else {
99 break; 199 break;
100 } 200 }
101 argv++; 201 argv++;
102 argc--; 202 argc--;
103 } 203 }
204 if (binding->flags & (BINDING_BORDER | BINDING_CONTENTS | BINDING_TITLEBAR)
205 || exclude_titlebar) {
206 binding->type = BINDING_MOUSE;
207 }
208
104 if (argc < 2) { 209 if (argc < 2) {
105 free_sway_binding(binding); 210 free_sway_binding(binding);
106 return cmd_results_new(CMD_FAILURE, bindtype, 211 return cmd_results_new(CMD_FAILURE, bindtype,
@@ -119,64 +224,47 @@ static struct cmd_results *cmd_bindsym_or_bindcode(int argc, char **argv,
119 continue; 224 continue;
120 } 225 }
121 226
122 xkb_keycode_t keycode; 227 // Identify the key and possibly change binding->type
123 xkb_keysym_t keysym; 228 uint32_t key_val = 0;
124 if (bindcode) { 229 error = identify_key(split->items[i], binding->keys->length == 0,
125 // parse keycode 230 &key_val, &binding->type);
126 keycode = (int)strtol(split->items[i], NULL, 10); 231 if (error) {
127 if (!xkb_keycode_is_legal_ext(keycode)) { 232 free_sway_binding(binding);
128 error = 233 list_free(split);
129 cmd_results_new(CMD_INVALID, "bindcode", 234 return error;
130 "Invalid keycode '%s'", (char *)split->items[i]);
131 free_sway_binding(binding);
132 list_free(split);
133 return error;
134 }
135 } else {
136 // Check for xkb key
137 keysym = xkb_keysym_from_name(split->items[i],
138 XKB_KEYSYM_CASE_INSENSITIVE);
139
140 // Check for mouse binding
141 if (strncasecmp(split->items[i], "button", strlen("button")) == 0 &&
142 strlen(split->items[i]) == strlen("button0")) {
143 keysym = ((char *)split->items[i])[strlen("button")] - '1' + BTN_LEFT;
144 }
145 if (!keysym) {
146 struct cmd_results *ret = cmd_results_new(CMD_INVALID, "bindsym",
147 "Unknown key '%s'", (char *)split->items[i]);
148 free_sway_binding(binding);
149 free_flat_list(split);
150 return ret;
151 }
152 } 235 }
236
153 uint32_t *key = calloc(1, sizeof(uint32_t)); 237 uint32_t *key = calloc(1, sizeof(uint32_t));
154 if (!key) { 238 if (!key) {
155 free_sway_binding(binding); 239 free_sway_binding(binding);
156 free_flat_list(split); 240 free_flat_list(split);
157 return cmd_results_new(CMD_FAILURE, bindtype, 241 return cmd_results_new(CMD_FAILURE, bindtype,
158 "Unable to allocate binding"); 242 "Unable to allocate binding key");
159 }
160
161 if (bindcode) {
162 *key = (uint32_t)keycode;
163 } else {
164 *key = (uint32_t)keysym;
165 } 243 }
166 244 *key = key_val;
167 list_add(binding->keys, key); 245 list_add(binding->keys, key);
168 } 246 }
169 free_flat_list(split); 247 free_flat_list(split);
170 binding->order = binding_order++; 248 binding->order = binding_order++;
171 249
250 // refine region of interest for mouse binding once we are certain
251 // that this is one
252 if (exclude_titlebar) {
253 binding->flags &= ~BINDING_TITLEBAR;
254 } else if (binding->type == BINDING_MOUSE) {
255 binding->flags |= BINDING_TITLEBAR;
256 }
257
172 // sort ascending 258 // sort ascending
173 list_qsort(binding->keys, key_qsort_cmp); 259 list_qsort(binding->keys, key_qsort_cmp);
174 260
175 list_t *mode_bindings; 261 list_t *mode_bindings;
176 if (bindcode) { 262 if (binding->type == BINDING_KEYCODE) {
177 mode_bindings = config->current_mode->keycode_bindings; 263 mode_bindings = config->current_mode->keycode_bindings;
178 } else { 264 } else if (binding->type == BINDING_KEYSYM) {
179 mode_bindings = config->current_mode->keysym_bindings; 265 mode_bindings = config->current_mode->keysym_bindings;
266 } else {
267 mode_bindings = config->current_mode->mouse_bindings;
180 } 268 }
181 269
182 // overwrite the binding if it already exists 270 // overwrite the binding if it already exists
@@ -209,3 +297,39 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) {
209struct cmd_results *cmd_bindcode(int argc, char **argv) { 297struct cmd_results *cmd_bindcode(int argc, char **argv) {
210 return cmd_bindsym_or_bindcode(argc, argv, true); 298 return cmd_bindsym_or_bindcode(argc, argv, true);
211} 299}
300
301
302/**
303 * Execute the command associated to a binding
304 */
305void seat_execute_command(struct sway_seat *seat, struct sway_binding *binding) {
306 wlr_log(WLR_DEBUG, "running command for binding: %s",
307 binding->command);
308
309 struct sway_binding *binding_copy = binding;
310 bool reload = false;
311 // if this is a reload command we need to make a duplicate of the
312 // binding since it will be gone after the reload has completed.
313 if (strcasecmp(binding->command, "reload") == 0) {
314 reload = true;
315 binding_copy = sway_binding_dup(binding);
316 if (!binding_copy) {
317 wlr_log(WLR_ERROR, "Failed to duplicate binding during reload");
318 return;
319 }
320 }
321
322 config->handler_context.seat = seat;
323 struct cmd_results *results = execute_command(binding->command, NULL);
324 if (results->status == CMD_SUCCESS) {
325 ipc_event_binding(binding_copy);
326 } else {
327 wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
328 binding->command, results->error);
329 }
330
331 if (reload) { // free the binding if we made a copy
332 free_sway_binding(binding_copy);
333 }
334 free_cmd_results(results);
335}
diff --git a/sway/commands/exec_always.c b/sway/commands/exec_always.c
index c7727857..c730cb8b 100644
--- a/sway/commands/exec_always.c
+++ b/sway/commands/exec_always.c
@@ -4,6 +4,7 @@
4#include <string.h> 4#include <string.h>
5#include <sys/wait.h> 5#include <sys/wait.h>
6#include <unistd.h> 6#include <unistd.h>
7#include <signal.h>
7#include "sway/commands.h" 8#include "sway/commands.h"
8#include "sway/config.h" 9#include "sway/config.h"
9#include "sway/tree/container.h" 10#include "sway/tree/container.h"
@@ -47,6 +48,9 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
47 if ((pid = fork()) == 0) { 48 if ((pid = fork()) == 0) {
48 // Fork child process again 49 // Fork child process again
49 setsid(); 50 setsid();
51 sigset_t set;
52 sigemptyset(&set);
53 sigprocmask(SIG_SETMASK, &set, NULL);
50 close(fd[0]); 54 close(fd[0]);
51 if ((child = fork()) == 0) { 55 if ((child = fork()) == 0) {
52 close(fd[1]); 56 close(fd[1]);
@@ -74,7 +78,7 @@ struct cmd_results *cmd_exec_always(int argc, char **argv) {
74 waitpid(pid, NULL, 0); 78 waitpid(pid, NULL, 0);
75 if (child > 0) { 79 if (child > 0) {
76 wlr_log(WLR_DEBUG, "Child process created with pid %d", child); 80 wlr_log(WLR_DEBUG, "Child process created with pid %d", child);
77 // TODO: add PID to active workspace 81 workspace_record_pid(child);
78 } else { 82 } else {
79 return cmd_results_new(CMD_FAILURE, "exec_always", 83 return cmd_results_new(CMD_FAILURE, "exec_always",
80 "Second fork() failed"); 84 "Second fork() failed");
diff --git a/sway/commands/floating.c b/sway/commands/floating.c
index 6ab56c3b..31de5ec3 100644
--- a/sway/commands/floating.c
+++ b/sway/commands/floating.c
@@ -17,9 +17,24 @@ struct cmd_results *cmd_floating(int argc, char **argv) {
17 } 17 }
18 struct sway_container *container = 18 struct sway_container *container =
19 config->handler_context.current_container; 19 config->handler_context.current_container;
20 if (container->type != C_VIEW) { 20 if (container->type == C_WORKSPACE && container->children->length == 0) {
21 // TODO: This doesn't strictly speaking have to be true 21 return cmd_results_new(CMD_INVALID, "floating",
22 return cmd_results_new(CMD_INVALID, "float", "Only views can float"); 22 "Can't float an empty workspace");
23 }
24 if (container->type == C_WORKSPACE) {
25 // Wrap the workspace's children in a container so we can float it
26 struct sway_container *workspace = container;
27 container = container_wrap_children(container);
28 workspace->layout = L_HORIZ;
29 seat_set_focus(config->handler_context.seat, container);
30 }
31
32 // If the container is in a floating split container,
33 // operate on the split container instead of the child.
34 if (container_is_floating_or_child(container)) {
35 while (container->parent->layout != L_FLOATING) {
36 container = container->parent;
37 }
23 } 38 }
24 39
25 bool wants_floating; 40 bool wants_floating;
diff --git a/sway/commands/floating_modifier.c b/sway/commands/floating_modifier.c
new file mode 100644
index 00000000..f5d2b3fe
--- /dev/null
+++ b/sway/commands/floating_modifier.c
@@ -0,0 +1,30 @@
1#include "strings.h"
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "util.h"
5
6struct cmd_results *cmd_floating_modifier(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "floating_modifier", EXPECTED_AT_LEAST, 1))) {
9 return error;
10 }
11
12 uint32_t mod = get_modifier_mask_by_name(argv[0]);
13 if (!mod) {
14 return cmd_results_new(CMD_INVALID, "floating_modifier",
15 "Invalid modifier");
16 }
17
18 if (argc == 1 || strcasecmp(argv[1], "normal") == 0) {
19 config->floating_mod_inverse = false;
20 } else if (strcasecmp(argv[1], "inverse") == 0) {
21 config->floating_mod_inverse = true;
22 } else {
23 return cmd_results_new(CMD_INVALID, "floating_modifier",
24 "Usage: floating_modifier <mod> [inverse|normal]");
25 }
26
27 config->floating_mod = mod;
28
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
30}
diff --git a/sway/commands/focus.c b/sway/commands/focus.c
index 9cd8bfae..76d3f1dc 100644
--- a/sway/commands/focus.c
+++ b/sway/commands/focus.c
@@ -35,14 +35,25 @@ static struct cmd_results *focus_mode(struct sway_container *con,
35 struct sway_seat *seat, bool floating) { 35 struct sway_seat *seat, bool floating) {
36 struct sway_container *ws = con->type == C_WORKSPACE ? 36 struct sway_container *ws = con->type == C_WORKSPACE ?
37 con : container_parent(con, C_WORKSPACE); 37 con : container_parent(con, C_WORKSPACE);
38 struct sway_container *new_focus = ws; 38
39 if (floating) { 39 // If the container is in a floating split container,
40 new_focus = ws->sway_workspace->floating; 40 // operate on the split container instead of the child.
41 if (new_focus->children->length == 0) { 41 if (container_is_floating_or_child(con)) {
42 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 42 while (con->parent->layout != L_FLOATING) {
43 con = con->parent;
43 } 44 }
44 } 45 }
45 seat_set_focus(seat, seat_get_active_child(seat, new_focus)); 46
47 struct sway_container *new_focus = NULL;
48 if (floating) {
49 new_focus = seat_get_focus_inactive(seat, ws->sway_workspace->floating);
50 } else {
51 new_focus = seat_get_focus_inactive_tiling(seat, ws);
52 }
53 if (!new_focus) {
54 new_focus = ws;
55 }
56 seat_set_focus(seat, new_focus);
46 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 57 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
47} 58}
48 59
@@ -97,7 +108,7 @@ struct cmd_results *cmd_focus(int argc, char **argv) {
97 } else if (strcmp(argv[0], "tiling") == 0) { 108 } else if (strcmp(argv[0], "tiling") == 0) {
98 return focus_mode(con, seat, false); 109 return focus_mode(con, seat, false);
99 } else if (strcmp(argv[0], "mode_toggle") == 0) { 110 } else if (strcmp(argv[0], "mode_toggle") == 0) {
100 return focus_mode(con, seat, !container_is_floating(con)); 111 return focus_mode(con, seat, !container_is_floating_or_child(con));
101 } 112 }
102 113
103 if (strcmp(argv[0], "output") == 0) { 114 if (strcmp(argv[0], "output") == 0) {
diff --git a/sway/commands/focus_follows_mouse.c b/sway/commands/focus_follows_mouse.c
index 661e7852..0b0e334c 100644
--- a/sway/commands/focus_follows_mouse.c
+++ b/sway/commands/focus_follows_mouse.c
@@ -1,12 +1,14 @@
1#include <string.h> 1#include <string.h>
2#include <strings.h> 2#include <strings.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "util.h"
4 5
5struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) { 6struct cmd_results *cmd_focus_follows_mouse(int argc, char **argv) {
6 struct cmd_results *error = NULL; 7 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) { 8 if ((error = checkarg(argc, "focus_follows_mouse", EXPECTED_EQUAL_TO, 1))) {
8 return error; 9 return error;
9 } 10 }
10 config->focus_follows_mouse = !strcasecmp(argv[0], "yes"); 11 config->focus_follows_mouse =
12 parse_boolean(argv[0], config->focus_follows_mouse);
11 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 13 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
12} 14}
diff --git a/sway/commands/focus_wrapping.c b/sway/commands/focus_wrapping.c
index 0a9e0bf2..562ee4f9 100644
--- a/sway/commands/focus_wrapping.c
+++ b/sway/commands/focus_wrapping.c
@@ -1,6 +1,7 @@
1#include <strings.h> 1#include <strings.h>
2#include "sway/commands.h" 2#include "sway/commands.h"
3#include "sway/config.h" 3#include "sway/config.h"
4#include "util.h"
4 5
5struct cmd_results *cmd_focus_wrapping(int argc, char **argv) { 6struct cmd_results *cmd_focus_wrapping(int argc, char **argv) {
6 struct cmd_results *error = NULL; 7 struct cmd_results *error = NULL;
@@ -8,15 +9,12 @@ struct cmd_results *cmd_focus_wrapping(int argc, char **argv) {
8 return error; 9 return error;
9 } 10 }
10 11
11 if (strcasecmp(argv[0], "no") == 0) { 12 if (strcasecmp(argv[0], "force") == 0) {
12 config->focus_wrapping = WRAP_NO;
13 } else if (strcasecmp(argv[0], "yes") == 0) {
14 config->focus_wrapping = WRAP_YES;
15 } else if (strcasecmp(argv[0], "force") == 0) {
16 config->focus_wrapping = WRAP_FORCE; 13 config->focus_wrapping = WRAP_FORCE;
14 } else if (parse_boolean(argv[0], config->focus_wrapping == WRAP_YES)) {
15 config->focus_wrapping = WRAP_YES;
17 } else { 16 } else {
18 return cmd_results_new(CMD_INVALID, "focus_wrapping", 17 config->focus_wrapping = WRAP_NO;
19 "Expected 'focus_wrapping yes|no|force'");
20 } 18 }
21 19
22 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 20 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/force_focus_wrapping.c b/sway/commands/force_focus_wrapping.c
index bc1d067f..0892d9e9 100644
--- a/sway/commands/force_focus_wrapping.c
+++ b/sway/commands/force_focus_wrapping.c
@@ -1,6 +1,7 @@
1#include <strings.h> 1#include <strings.h>
2#include "sway/commands.h" 2#include "sway/commands.h"
3#include "sway/config.h" 3#include "sway/config.h"
4#include "util.h"
4 5
5struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) { 6struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) {
6 struct cmd_results *error = 7 struct cmd_results *error =
@@ -9,13 +10,10 @@ struct cmd_results *cmd_force_focus_wrapping(int argc, char **argv) {
9 return error; 10 return error;
10 } 11 }
11 12
12 if (strcasecmp(argv[0], "no") == 0) { 13 if (parse_boolean(argv[0], config->focus_wrapping == WRAP_FORCE)) {
13 config->focus_wrapping = WRAP_YES;
14 } else if (strcasecmp(argv[0], "yes") == 0) {
15 config->focus_wrapping = WRAP_FORCE; 14 config->focus_wrapping = WRAP_FORCE;
16 } else { 15 } else {
17 return cmd_results_new(CMD_INVALID, "force_focus_wrapping", 16 config->focus_wrapping = WRAP_YES;
18 "Expected 'force_focus_wrapping yes|no'");
19 } 17 }
20 18
21 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 19 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/fullscreen.c b/sway/commands/fullscreen.c
index 0b5beaa2..5ad06e40 100644
--- a/sway/commands/fullscreen.c
+++ b/sway/commands/fullscreen.c
@@ -5,6 +5,7 @@
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "sway/tree/view.h" 6#include "sway/tree/view.h"
7#include "sway/tree/layout.h" 7#include "sway/tree/layout.h"
8#include "util.h"
8 9
9struct cmd_results *cmd_fullscreen(int argc, char **argv) { 10struct cmd_results *cmd_fullscreen(int argc, char **argv) {
10 struct cmd_results *error = NULL; 11 struct cmd_results *error = NULL;
@@ -13,25 +14,24 @@ struct cmd_results *cmd_fullscreen(int argc, char **argv) {
13 } 14 }
14 struct sway_container *container = 15 struct sway_container *container =
15 config->handler_context.current_container; 16 config->handler_context.current_container;
16 if (container->type != C_VIEW) { 17 if (container->type == C_WORKSPACE && container->children->length == 0) {
17 return cmd_results_new(CMD_INVALID, "fullscreen", 18 return cmd_results_new(CMD_INVALID, "fullscreen",
18 "Only views can fullscreen"); 19 "Can't fullscreen an empty workspace");
19 } 20 }
20 struct sway_view *view = container->sway_view; 21 if (container->type == C_WORKSPACE) {
21 bool wants_fullscreen; 22 // Wrap the workspace's children in a container so we can fullscreen it
23 struct sway_container *workspace = container;
24 container = container_wrap_children(container);
25 workspace->layout = L_HORIZ;
26 seat_set_focus(config->handler_context.seat, container);
27 }
28 bool enable = !container->is_fullscreen;
22 29
23 if (argc == 0 || strcmp(argv[0], "toggle") == 0) { 30 if (argc) {
24 wants_fullscreen = !view->is_fullscreen; 31 enable = parse_boolean(argv[0], container->is_fullscreen);
25 } else if (strcmp(argv[0], "enable") == 0) {
26 wants_fullscreen = true;
27 } else if (strcmp(argv[0], "disable") == 0) {
28 wants_fullscreen = false;
29 } else {
30 return cmd_results_new(CMD_INVALID, "fullscreen",
31 "Expected 'fullscreen' or 'fullscreen <enable|disable|toggle>'");
32 } 32 }
33 33
34 view_set_fullscreen(view, wants_fullscreen); 34 container_set_fullscreen(container, enable);
35 35
36 struct sway_container *workspace = container_parent(container, C_WORKSPACE); 36 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
37 arrange_windows(workspace->parent); 37 arrange_windows(workspace->parent);
diff --git a/sway/commands/input.c b/sway/commands/input.c
index 5b203ea0..84888fbb 100644
--- a/sway/commands/input.c
+++ b/sway/commands/input.c
@@ -31,6 +31,12 @@ static struct cmd_handler input_handlers[] = {
31 { "xkb_variant", input_cmd_xkb_variant }, 31 { "xkb_variant", input_cmd_xkb_variant },
32}; 32};
33 33
34// must be in order for the bsearch
35static struct cmd_handler input_config_handlers[] = {
36 { "xkb_capslock", input_cmd_xkb_capslock },
37 { "xkb_numlock", input_cmd_xkb_numlock },
38};
39
34struct cmd_results *cmd_input(int argc, char **argv) { 40struct cmd_results *cmd_input(int argc, char **argv) {
35 struct cmd_results *error = NULL; 41 struct cmd_results *error = NULL;
36 if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) { 42 if ((error = checkarg(argc, "input", EXPECTED_AT_LEAST, 2))) {
@@ -44,8 +50,21 @@ struct cmd_results *cmd_input(int argc, char **argv) {
44 return cmd_results_new(CMD_FAILURE, NULL, "Couldn't allocate config"); 50 return cmd_results_new(CMD_FAILURE, NULL, "Couldn't allocate config");
45 } 51 }
46 52
47 struct cmd_results *res = config_subcommand(argv + 1, argc - 1, 53 struct cmd_results *res;
54
55 if (find_handler(argv[1], input_config_handlers,
56 sizeof(input_config_handlers))) {
57 if (config->reading) {
58 res = config_subcommand(argv + 1, argc - 1,
59 input_config_handlers, sizeof(input_config_handlers));
60 } else {
61 res = cmd_results_new(CMD_FAILURE, "input",
62 "Can only be used in config file.");
63 }
64 } else {
65 res = config_subcommand(argv + 1, argc - 1,
48 input_handlers, sizeof(input_handlers)); 66 input_handlers, sizeof(input_handlers));
67 }
49 68
50 free_input_config(config->handler_context.input_config); 69 free_input_config(config->handler_context.input_config);
51 config->handler_context.input_config = NULL; 70 config->handler_context.input_config = NULL;
diff --git a/sway/commands/input/drag_lock.c b/sway/commands/input/drag_lock.c
index 9e32816f..f9ddeef2 100644
--- a/sway/commands/input/drag_lock.c
+++ b/sway/commands/input/drag_lock.c
@@ -3,6 +3,7 @@
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 "util.h"
6 7
7struct cmd_results *input_cmd_drag_lock(int argc, char **argv) { 8struct cmd_results *input_cmd_drag_lock(int argc, char **argv) {
8 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -18,14 +19,10 @@ struct cmd_results *input_cmd_drag_lock(int argc, char **argv) {
18 struct input_config *new_config = 19 struct input_config *new_config =
19 new_input_config(current_input_config->identifier); 20 new_input_config(current_input_config->identifier);
20 21
21 if (strcasecmp(argv[0], "enabled") == 0) { 22 if (parse_boolean(argv[0], true)) {
22 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; 23 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_ENABLED;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
25 } else { 24 } else {
26 free_input_config(new_config); 25 new_config->drag_lock = LIBINPUT_CONFIG_DRAG_LOCK_DISABLED;
27 return cmd_results_new(CMD_INVALID, "drag_lock",
28 "Expected 'drag_lock <enabled|disabled>'");
29 } 26 }
30 27
31 apply_input_config(new_config); 28 apply_input_config(new_config);
diff --git a/sway/commands/input/dwt.c b/sway/commands/input/dwt.c
index 73937507..15134268 100644
--- a/sway/commands/input/dwt.c
+++ b/sway/commands/input/dwt.c
@@ -3,6 +3,7 @@
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 "util.h"
6 7
7struct cmd_results *input_cmd_dwt(int argc, char **argv) { 8struct cmd_results *input_cmd_dwt(int argc, char **argv) {
8 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -17,14 +18,10 @@ struct cmd_results *input_cmd_dwt(int argc, char **argv) {
17 struct input_config *new_config = 18 struct input_config *new_config =
18 new_input_config(current_input_config->identifier); 19 new_input_config(current_input_config->identifier);
19 20
20 if (strcasecmp(argv[0], "enabled") == 0) { 21 if (parse_boolean(argv[0], true)) {
21 new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED; 22 new_config->dwt = LIBINPUT_CONFIG_DWT_ENABLED;
22 } else if (strcasecmp(argv[0], "disabled") == 0) {
23 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED;
24 } else { 23 } else {
25 free_input_config(new_config); 24 new_config->dwt = LIBINPUT_CONFIG_DWT_DISABLED;
26 return cmd_results_new(CMD_INVALID, "dwt",
27 "Expected 'dwt <enabled|disabled>'");
28 } 25 }
29 26
30 apply_input_config(new_config); 27 apply_input_config(new_config);
diff --git a/sway/commands/input/left_handed.c b/sway/commands/input/left_handed.c
index 769ce98c..e770043a 100644
--- a/sway/commands/input/left_handed.c
+++ b/sway/commands/input/left_handed.c
@@ -3,6 +3,7 @@
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 "util.h"
6 7
7struct cmd_results *input_cmd_left_handed(int argc, char **argv) { 8struct cmd_results *input_cmd_left_handed(int argc, char **argv) {
8 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -18,15 +19,7 @@ struct cmd_results *input_cmd_left_handed(int argc, char **argv) {
18 struct input_config *new_config = 19 struct input_config *new_config =
19 new_input_config(current_input_config->identifier); 20 new_input_config(current_input_config->identifier);
20 21
21 if (strcasecmp(argv[0], "enabled") == 0) { 22 new_config->left_handed = parse_boolean(argv[0], true);
22 new_config->left_handed = 1;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->left_handed = 0;
25 } else {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "left_handed",
28 "Expected 'left_handed <enabled|disabled>'");
29 }
30 23
31 apply_input_config(new_config); 24 apply_input_config(new_config);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/middle_emulation.c b/sway/commands/input/middle_emulation.c
index 7ca01629..414d4d2b 100644
--- a/sway/commands/input/middle_emulation.c
+++ b/sway/commands/input/middle_emulation.c
@@ -3,6 +3,7 @@
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 "util.h"
6 7
7struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) { 8struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) {
8 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -18,15 +19,11 @@ struct cmd_results *input_cmd_middle_emulation(int argc, char **argv) {
18 struct input_config *new_config = 19 struct input_config *new_config =
19 new_input_config(current_input_config->identifier); 20 new_input_config(current_input_config->identifier);
20 21
21 if (strcasecmp(argv[0], "enabled") == 0) { 22 if (parse_boolean(argv[0], true)) {
22 new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; 23 new_config->middle_emulation = LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED;
23 } else if (strcasecmp(argv[0], "disabled") == 0) { 24 } else {
24 new_config->middle_emulation = 25 new_config->middle_emulation =
25 LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; 26 LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED;
26 } else {
27 free_input_config(new_config);
28 return cmd_results_new(CMD_INVALID, "middle_emulation",
29 "Expected 'middle_emulation <enabled|disabled>'");
30 } 27 }
31 28
32 apply_input_config(new_config); 29 apply_input_config(new_config);
diff --git a/sway/commands/input/natural_scroll.c b/sway/commands/input/natural_scroll.c
index 55236790..77c3ff00 100644
--- a/sway/commands/input/natural_scroll.c
+++ b/sway/commands/input/natural_scroll.c
@@ -3,6 +3,7 @@
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 "util.h"
6 7
7struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) { 8struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) {
8 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
@@ -18,15 +19,7 @@ struct cmd_results *input_cmd_natural_scroll(int argc, char **argv) {
18 struct input_config *new_config = 19 struct input_config *new_config =
19 new_input_config(current_input_config->identifier); 20 new_input_config(current_input_config->identifier);
20 21
21 if (strcasecmp(argv[0], "enabled") == 0) { 22 new_config->natural_scroll = parse_boolean(argv[0], true);
22 new_config->natural_scroll = 1;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->natural_scroll = 0;
25 } else {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "natural_scroll",
28 "Expected 'natural_scroll <enabled|disabled>'");
29 }
30 23
31 apply_input_config(new_config); 24 apply_input_config(new_config);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 25 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/commands/input/tap.c b/sway/commands/input/tap.c
index a8d1a10c..ac3b8237 100644
--- a/sway/commands/input/tap.c
+++ b/sway/commands/input/tap.c
@@ -4,6 +4,7 @@
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#include "util.h"
7 8
8struct cmd_results *input_cmd_tap(int argc, char **argv) { 9struct cmd_results *input_cmd_tap(int argc, char **argv) {
9 struct cmd_results *error = NULL; 10 struct cmd_results *error = NULL;
@@ -18,14 +19,10 @@ struct cmd_results *input_cmd_tap(int argc, char **argv) {
18 struct input_config *new_config = 19 struct input_config *new_config =
19 new_input_config(current_input_config->identifier); 20 new_input_config(current_input_config->identifier);
20 21
21 if (strcasecmp(argv[0], "enabled") == 0) { 22 if (parse_boolean(argv[0], true)) {
22 new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED; 23 new_config->tap = LIBINPUT_CONFIG_TAP_ENABLED;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED;
25 } else { 24 } else {
26 free_input_config(new_config); 25 new_config->tap = LIBINPUT_CONFIG_TAP_DISABLED;
27 return cmd_results_new(CMD_INVALID, "tap",
28 "Expected 'tap <enabled|disabled>'");
29 } 26 }
30 27
31 wlr_log(WLR_DEBUG, "apply-tap for device: %s", 28 wlr_log(WLR_DEBUG, "apply-tap for device: %s",
diff --git a/sway/commands/input/xkb_capslock.c b/sway/commands/input/xkb_capslock.c
new file mode 100644
index 00000000..5442c463
--- /dev/null
+++ b/sway/commands/input/xkb_capslock.c
@@ -0,0 +1,33 @@
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
7struct cmd_results *input_cmd_xkb_capslock(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_capslock", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
14 if (!current_input_config) {
15 return cmd_results_new(CMD_FAILURE, "xkb_capslock",
16 "No input device defined.");
17 }
18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
20
21 if (strcasecmp(argv[0], "enabled") == 0) {
22 new_config->xkb_capslock = 1;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->xkb_capslock = 0;
25 } else {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "xkb_capslock",
28 "Expected 'xkb_capslock <enabled|disabled>'");
29 }
30
31 apply_input_config(new_config);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33}
diff --git a/sway/commands/input/xkb_numlock.c b/sway/commands/input/xkb_numlock.c
new file mode 100644
index 00000000..39675366
--- /dev/null
+++ b/sway/commands/input/xkb_numlock.c
@@ -0,0 +1,33 @@
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
7struct cmd_results *input_cmd_xkb_numlock(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "xkb_numlock", EXPECTED_AT_LEAST, 1))) {
10 return error;
11 }
12 struct input_config *current_input_config =
13 config->handler_context.input_config;
14 if (!current_input_config) {
15 return cmd_results_new(CMD_FAILURE, "xkb_numlock",
16 "No input device defined.");
17 }
18 struct input_config *new_config =
19 new_input_config(current_input_config->identifier);
20
21 if (strcasecmp(argv[0], "enabled") == 0) {
22 new_config->xkb_numlock = 1;
23 } else if (strcasecmp(argv[0], "disabled") == 0) {
24 new_config->xkb_numlock = 0;
25 } else {
26 free_input_config(new_config);
27 return cmd_results_new(CMD_INVALID, "xkb_numlock",
28 "Expected 'xkb_numlock <enabled|disabled>'");
29 }
30
31 apply_input_config(new_config);
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33}
diff --git a/sway/commands/mark.c b/sway/commands/mark.c
index 5a897e69..9ea8c301 100644
--- a/sway/commands/mark.c
+++ b/sway/commands/mark.c
@@ -58,7 +58,7 @@ struct cmd_results *cmd_mark(int argc, char **argv) {
58 view_find_and_unmark(mark); 58 view_find_and_unmark(mark);
59 59
60 if (!toggle || !had_mark) { 60 if (!toggle || !had_mark) {
61 list_add(view->marks, strdup(mark)); 61 view_add_mark(view, mark);
62 } 62 }
63 63
64 free(mark); 64 free(mark);
diff --git a/sway/commands/mode.c b/sway/commands/mode.c
index b460fcb5..637ca45e 100644
--- a/sway/commands/mode.c
+++ b/sway/commands/mode.c
@@ -56,6 +56,7 @@ struct cmd_results *cmd_mode(int argc, char **argv) {
56 mode->name = strdup(mode_name); 56 mode->name = strdup(mode_name);
57 mode->keysym_bindings = create_list(); 57 mode->keysym_bindings = create_list();
58 mode->keycode_bindings = create_list(); 58 mode->keycode_bindings = create_list();
59 mode->mouse_bindings = create_list();
59 mode->pango = pango; 60 mode->pango = pango;
60 list_add(config->modes, mode); 61 list_add(config->modes, mode);
61 } 62 }
diff --git a/sway/commands/move.c b/sway/commands/move.c
index 6ec050a8..702b42d9 100644
--- a/sway/commands/move.c
+++ b/sway/commands/move.c
@@ -9,6 +9,7 @@
9#include "sway/input/cursor.h" 9#include "sway/input/cursor.h"
10#include "sway/input/seat.h" 10#include "sway/input/seat.h"
11#include "sway/output.h" 11#include "sway/output.h"
12#include "sway/scratchpad.h"
12#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
13#include "sway/tree/container.h" 14#include "sway/tree/container.h"
14#include "sway/tree/layout.h" 15#include "sway/tree/layout.h"
@@ -58,8 +59,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
58 && strcasecmp(argv[2], "workspace") == 0) { 59 && strcasecmp(argv[2], "workspace") == 0) {
59 // move container to workspace x 60 // move container to workspace x
60 if (current->type == C_WORKSPACE) { 61 if (current->type == C_WORKSPACE) {
61 // TODO: Wrap children in a container and move that 62 current = container_wrap_children(current);
62 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
63 } else if (current->type != C_CONTAINER && current->type != C_VIEW) { 63 } else if (current->type != C_CONTAINER && current->type != C_VIEW) {
64 return cmd_results_new(CMD_FAILURE, "move", 64 return cmd_results_new(CMD_FAILURE, "move",
65 "Can only move containers and views."); 65 "Can only move containers and views.");
@@ -97,7 +97,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
97 container_move_to(current, destination); 97 container_move_to(current, destination);
98 struct sway_container *focus = seat_get_focus_inactive( 98 struct sway_container *focus = seat_get_focus_inactive(
99 config->handler_context.seat, old_parent); 99 config->handler_context.seat, old_parent);
100 seat_set_focus(config->handler_context.seat, focus); 100 seat_set_focus_warp(config->handler_context.seat, focus, true, false);
101 container_reap_empty(old_parent); 101 container_reap_empty(old_parent);
102 container_reap_empty(destination->parent); 102 container_reap_empty(destination->parent);
103 103
@@ -134,7 +134,7 @@ static struct cmd_results *cmd_move_container(struct sway_container *current,
134 struct sway_container *old_parent = current->parent; 134 struct sway_container *old_parent = current->parent;
135 struct sway_container *old_ws = container_parent(current, C_WORKSPACE); 135 struct sway_container *old_ws = container_parent(current, C_WORKSPACE);
136 container_move_to(current, focus); 136 container_move_to(current, focus);
137 seat_set_focus(config->handler_context.seat, old_parent); 137 seat_set_focus_warp(config->handler_context.seat, old_parent, true, false);
138 container_reap_empty(old_parent); 138 container_reap_empty(old_parent);
139 container_reap_empty(focus->parent); 139 container_reap_empty(focus->parent);
140 140
@@ -195,7 +195,7 @@ static struct cmd_results *move_in_direction(struct sway_container *container,
195 "Cannot move workspaces in a direction"); 195 "Cannot move workspaces in a direction");
196 } 196 }
197 if (container_is_floating(container)) { 197 if (container_is_floating(container)) {
198 if (container->type == C_VIEW && container->sway_view->is_fullscreen) { 198 if (container->is_fullscreen) {
199 return cmd_results_new(CMD_FAILURE, "move", 199 return cmd_results_new(CMD_FAILURE, "move",
200 "Cannot move fullscreen floating container"); 200 "Cannot move fullscreen floating container");
201 } 201 }
@@ -296,6 +296,34 @@ static struct cmd_results *move_to_position(struct sway_container *container,
296 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 296 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
297} 297}
298 298
299static struct cmd_results *move_to_scratchpad(struct sway_container *con) {
300 if (con->type == C_WORKSPACE && con->children->length == 0) {
301 return cmd_results_new(CMD_INVALID, "move",
302 "Can't move an empty workspace to the scratchpad");
303 }
304 if (con->type == C_WORKSPACE) {
305 // Wrap the workspace's children in a container
306 struct sway_container *workspace = con;
307 con = container_wrap_children(con);
308 workspace->layout = L_HORIZ;
309 }
310
311 // If the container is in a floating split container,
312 // operate on the split container instead of the child.
313 if (container_is_floating_or_child(con)) {
314 while (con->parent->layout != L_FLOATING) {
315 con = con->parent;
316 }
317 }
318
319 if (con->scratchpad) {
320 return cmd_results_new(CMD_INVALID, "move",
321 "Container is already in the scratchpad");
322 }
323 scratchpad_add_container(con);
324 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
325}
326
299struct cmd_results *cmd_move(int argc, char **argv) { 327struct cmd_results *cmd_move(int argc, char **argv) {
300 struct cmd_results *error = NULL; 328 struct cmd_results *error = NULL;
301 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) { 329 if ((error = checkarg(argc, "move", EXPECTED_AT_LEAST, 1))) {
@@ -317,10 +345,9 @@ struct cmd_results *cmd_move(int argc, char **argv) {
317 } else if (strcasecmp(argv[0], "workspace") == 0) { 345 } else if (strcasecmp(argv[0], "workspace") == 0) {
318 return cmd_move_workspace(current, argc, argv); 346 return cmd_move_workspace(current, argc, argv);
319 } else if (strcasecmp(argv[0], "scratchpad") == 0 347 } else if (strcasecmp(argv[0], "scratchpad") == 0
320 || (strcasecmp(argv[0], "to") == 0 348 || (strcasecmp(argv[0], "to") == 0 && argc == 2
321 && strcasecmp(argv[1], "scratchpad") == 0)) { 349 && strcasecmp(argv[1], "scratchpad") == 0)) {
322 // TODO: scratchpad 350 return move_to_scratchpad(current);
323 return cmd_results_new(CMD_FAILURE, "move", "Unimplemented");
324 } else if (strcasecmp(argv[0], "position") == 0) { 351 } else if (strcasecmp(argv[0], "position") == 0) {
325 return move_to_position(current, argc, argv); 352 return move_to_position(current, argc, argv);
326 } else if (strcasecmp(argv[0], "absolute") == 0) { 353 } else if (strcasecmp(argv[0], "absolute") == 0) {
diff --git a/sway/commands/output/dpms.c b/sway/commands/output/dpms.c
index 0959ea6b..3492061e 100644
--- a/sway/commands/output/dpms.c
+++ b/sway/commands/output/dpms.c
@@ -1,5 +1,6 @@
1#include "sway/commands.h" 1#include "sway/commands.h"
2#include "sway/config.h" 2#include "sway/config.h"
3#include "util.h"
3 4
4struct cmd_results *output_cmd_dpms(int argc, char **argv) { 5struct cmd_results *output_cmd_dpms(int argc, char **argv) {
5 if (!config->handler_context.output_config) { 6 if (!config->handler_context.output_config) {
@@ -9,13 +10,10 @@ struct cmd_results *output_cmd_dpms(int argc, char **argv) {
9 return cmd_results_new(CMD_INVALID, "output", "Missing dpms argument."); 10 return cmd_results_new(CMD_INVALID, "output", "Missing dpms argument.");
10 } 11 }
11 12
12 if (strcmp(*argv, "on") == 0) { 13 if (parse_boolean(argv[0], true)) {
13 config->handler_context.output_config->dpms_state = DPMS_ON; 14 config->handler_context.output_config->dpms_state = DPMS_ON;
14 } else if (strcmp(*argv, "off") == 0) {
15 config->handler_context.output_config->dpms_state = DPMS_OFF;
16 } else { 15 } else {
17 return cmd_results_new(CMD_INVALID, "output", 16 config->handler_context.output_config->dpms_state = DPMS_OFF;
18 "Invalid dpms state, valid states are on/off.");
19 } 17 }
20 18
21 config->handler_context.leftovers.argc = argc - 1; 19 config->handler_context.leftovers.argc = argc - 1;
diff --git a/sway/commands/reload.c b/sway/commands/reload.c
index cea6a94b..5c1b19b4 100644
--- a/sway/commands/reload.c
+++ b/sway/commands/reload.c
@@ -1,17 +1,46 @@
1#define _XOPEN_SOURCE 500
2#include <string.h>
1#include "sway/commands.h" 3#include "sway/commands.h"
2#include "sway/config.h" 4#include "sway/config.h"
5#include "sway/ipc-server.h"
3#include "sway/tree/arrange.h" 6#include "sway/tree/arrange.h"
7#include "list.h"
4 8
5struct cmd_results *cmd_reload(int argc, char **argv) { 9struct cmd_results *cmd_reload(int argc, char **argv) {
6 struct cmd_results *error = NULL; 10 struct cmd_results *error = NULL;
7 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) { 11 if ((error = checkarg(argc, "reload", EXPECTED_EQUAL_TO, 0))) {
8 return error; 12 return error;
9 } 13 }
14
15 // store bar ids to check against new bars for barconfig_update events
16 list_t *bar_ids = create_list();
17 for (int i = 0; i < config->bars->length; ++i) {
18 struct bar_config *bar = config->bars->items[i];
19 list_add(bar_ids, strdup(bar->id));
20 }
21
10 if (!load_main_config(config->current_config_path, true)) { 22 if (!load_main_config(config->current_config_path, true)) {
11 return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config."); 23 return cmd_results_new(CMD_FAILURE, "reload", "Error(s) reloading config.");
12 } 24 }
25 ipc_event_workspace(NULL, NULL, "reload");
13 26
14 load_swaybars(); 27 load_swaybars();
28
29 for (int i = 0; i < config->bars->length; ++i) {
30 struct bar_config *bar = config->bars->items[i];
31 for (int j = 0; j < bar_ids->length; ++j) {
32 if (strcmp(bar->id, bar_ids->items[j]) == 0) {
33 ipc_event_barconfig_update(bar);
34 break;
35 }
36 }
37 }
38
39 for (int i = 0; i < bar_ids->length; ++i) {
40 free(bar_ids->items[i]);
41 }
42 list_free(bar_ids);
43
15 arrange_windows(&root_container); 44 arrange_windows(&root_container);
16 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 45 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
17} 46}
diff --git a/sway/commands/scratchpad.c b/sway/commands/scratchpad.c
new file mode 100644
index 00000000..01a91d65
--- /dev/null
+++ b/sway/commands/scratchpad.c
@@ -0,0 +1,44 @@
1#include "log.h"
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "sway/scratchpad.h"
5#include "sway/tree/container.h"
6
7struct cmd_results *cmd_scratchpad(int argc, char **argv) {
8 struct cmd_results *error = NULL;
9 if ((error = checkarg(argc, "scratchpad", EXPECTED_EQUAL_TO, 1))) {
10 return error;
11 }
12 if (strcmp(argv[0], "show") != 0) {
13 return cmd_results_new(CMD_INVALID, "scratchpad",
14 "Expected 'scratchpad show'");
15 }
16 if (!root_container.sway_root->scratchpad->length) {
17 return cmd_results_new(CMD_INVALID, "scratchpad",
18 "Scratchpad is empty");
19 }
20
21 if (config->handler_context.using_criteria) {
22 struct sway_container *con = config->handler_context.current_container;
23
24 // If the container is in a floating split container,
25 // operate on the split container instead of the child.
26 if (container_is_floating_or_child(con)) {
27 while (con->parent->layout != L_FLOATING) {
28 con = con->parent;
29 }
30 }
31
32 // If using criteria, this command is executed for every container which
33 // matches the criteria. If this container isn't in the scratchpad,
34 // we'll just silently return a success.
35 if (!con->scratchpad) {
36 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
37 }
38 scratchpad_toggle_container(con);
39 } else {
40 scratchpad_toggle_auto();
41 }
42
43 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
44}
diff --git a/sway/commands/show_marks.c b/sway/commands/show_marks.c
index c7fdc538..434a0e27 100644
--- a/sway/commands/show_marks.c
+++ b/sway/commands/show_marks.c
@@ -7,6 +7,7 @@
7#include "list.h" 7#include "list.h"
8#include "log.h" 8#include "log.h"
9#include "stringop.h" 9#include "stringop.h"
10#include "util.h"
10 11
11static void rebuild_marks_iterator(struct sway_container *con, void *data) { 12static void rebuild_marks_iterator(struct sway_container *con, void *data) {
12 if (con->type == C_VIEW) { 13 if (con->type == C_VIEW) {
@@ -20,14 +21,7 @@ struct cmd_results *cmd_show_marks(int argc, char **argv) {
20 return error; 21 return error;
21 } 22 }
22 23
23 if (strcmp(*argv, "yes") == 0) { 24 config->show_marks = parse_boolean(argv[0], config->show_marks);
24 config->show_marks = true;
25 } else if (strcmp(*argv, "no") == 0) {
26 config->show_marks = false;
27 } else {
28 return cmd_results_new(CMD_INVALID, "show_marks",
29 "Expected 'show_marks <yes|no>'");
30 }
31 25
32 if (config->show_marks) { 26 if (config->show_marks) {
33 container_for_each_descendant_dfs(&root_container, 27 container_for_each_descendant_dfs(&root_container,
diff --git a/sway/commands/split.c b/sway/commands/split.c
index 313799da..a8eddf54 100644
--- a/sway/commands/split.c
+++ b/sway/commands/split.c
@@ -10,10 +10,6 @@
10 10
11static struct cmd_results *do_split(int layout) { 11static struct cmd_results *do_split(int layout) {
12 struct sway_container *con = config->handler_context.current_container; 12 struct sway_container *con = config->handler_context.current_container;
13 if (container_is_floating(con)) {
14 return cmd_results_new(CMD_FAILURE, "split",
15 "Can't split a floating view");
16 }
17 struct sway_container *parent = container_split(con, layout); 13 struct sway_container *parent = container_split(con, layout);
18 container_create_notify(parent); 14 container_create_notify(parent);
19 arrange_windows(parent->parent); 15 arrange_windows(parent->parent);
diff --git a/sway/commands/swap.c b/sway/commands/swap.c
index 2fc88308..4e3a9cce 100644
--- a/sway/commands/swap.c
+++ b/sway/commands/swap.c
@@ -1,5 +1,6 @@
1#include <strings.h> 1#include <strings.h>
2#include <wlr/util/log.h> 2#include <wlr/util/log.h>
3#include "config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
4#include "sway/tree/arrange.h" 5#include "sway/tree/arrange.h"
5#include "sway/tree/layout.h" 6#include "sway/tree/layout.h"
@@ -14,10 +15,14 @@ static bool test_con_id(struct sway_container *container, void *con_id) {
14} 15}
15 16
16static bool test_id(struct sway_container *container, void *id) { 17static bool test_id(struct sway_container *container, void *id) {
18#ifdef HAVE_XWAYLAND
17 xcb_window_t *wid = id; 19 xcb_window_t *wid = id;
18 return (container->type == C_VIEW 20 return (container->type == C_VIEW
19 && container->sway_view->type == SWAY_VIEW_XWAYLAND 21 && container->sway_view->type == SWAY_VIEW_XWAYLAND
20 && container->sway_view->wlr_xwayland_surface->window_id == *wid); 22 && container->sway_view->wlr_xwayland_surface->window_id == *wid);
23#else
24 return false;
25#endif
21} 26}
22 27
23static bool test_mark(struct sway_container *container, void *mark) { 28static bool test_mark(struct sway_container *container, void *mark) {
@@ -43,8 +48,10 @@ struct cmd_results *cmd_swap(int argc, char **argv) {
43 48
44 char *value = join_args(argv + 3, argc - 3); 49 char *value = join_args(argv + 3, argc - 3);
45 if (strcasecmp(argv[2], "id") == 0) { 50 if (strcasecmp(argv[2], "id") == 0) {
51#ifdef HAVE_XWAYLAND
46 xcb_window_t id = strtol(value, NULL, 0); 52 xcb_window_t id = strtol(value, NULL, 0);
47 other = container_find(&root_container, test_id, (void *)&id); 53 other = container_find(&root_container, test_id, (void *)&id);
54#endif
48 } else if (strcasecmp(argv[2], "con_id") == 0) { 55 } else if (strcasecmp(argv[2], "con_id") == 0) {
49 size_t con_id = atoi(value); 56 size_t con_id = atoi(value);
50 other = container_find(&root_container, test_con_id, (void *)con_id); 57 other = container_find(&root_container, test_con_id, (void *)con_id);
diff --git a/sway/commands/urgent.c b/sway/commands/urgent.c
index d199858a..51c497c4 100644
--- a/sway/commands/urgent.c
+++ b/sway/commands/urgent.c
@@ -5,6 +5,7 @@
5#include "sway/tree/container.h" 5#include "sway/tree/container.h"
6#include "sway/tree/view.h" 6#include "sway/tree/view.h"
7#include "sway/tree/layout.h" 7#include "sway/tree/layout.h"
8#include "util.h"
8 9
9struct cmd_results *cmd_urgent(int argc, char **argv) { 10struct cmd_results *cmd_urgent(int argc, char **argv) {
10 struct cmd_results *error = NULL; 11 struct cmd_results *error = NULL;
@@ -19,17 +20,12 @@ struct cmd_results *cmd_urgent(int argc, char **argv) {
19 } 20 }
20 struct sway_view *view = container->sway_view; 21 struct sway_view *view = container->sway_view;
21 22
22 if (strcmp(argv[0], "enable") == 0) { 23 if (strcmp(argv[0], "allow") == 0) {
23 view_set_urgent(view, true);
24 } else if (strcmp(argv[0], "disable") == 0) {
25 view_set_urgent(view, false);
26 } else if (strcmp(argv[0], "allow") == 0) {
27 view->allow_request_urgent = true; 24 view->allow_request_urgent = true;
28 } else if (strcmp(argv[0], "deny") == 0) { 25 } else if (strcmp(argv[0], "deny") == 0) {
29 view->allow_request_urgent = false; 26 view->allow_request_urgent = false;
30 } else { 27 } else {
31 return cmd_results_new(CMD_INVALID, "urgent", 28 view_set_urgent(view, parse_boolean(argv[0], view_is_urgent(view)));
32 "Expected 'urgent <enable|disable|allow|deny>'");
33 } 29 }
34 30
35 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 31 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
diff --git a/sway/config.c b/sway/config.c
index ed624bfa..2afffab1 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -56,6 +56,12 @@ static void free_mode(struct sway_mode *mode) {
56 } 56 }
57 list_free(mode->keycode_bindings); 57 list_free(mode->keycode_bindings);
58 } 58 }
59 if (mode->mouse_bindings) {
60 for (i = 0; i < mode->mouse_bindings->length; i++) {
61 free_sway_binding(mode->mouse_bindings->items[i]);
62 }
63 list_free(mode->mouse_bindings);
64 }
59 free(mode); 65 free(mode);
60} 66}
61 67
@@ -87,7 +93,6 @@ void free_config(struct sway_config *config) {
87 } 93 }
88 list_free(config->cmd_queue); 94 list_free(config->cmd_queue);
89 list_free(config->workspace_outputs); 95 list_free(config->workspace_outputs);
90 list_free(config->pid_workspaces);
91 if (config->output_configs) { 96 if (config->output_configs) {
92 for (int i = 0; i < config->output_configs->length; i++) { 97 for (int i = 0; i < config->output_configs->length; i++) {
93 free_output_config(config->output_configs->items[i]); 98 free_output_config(config->output_configs->items[i]);
@@ -157,7 +162,6 @@ static void config_defaults(struct sway_config *config) {
157 if (!(config->modes = create_list())) goto cleanup; 162 if (!(config->modes = create_list())) goto cleanup;
158 if (!(config->bars = create_list())) goto cleanup; 163 if (!(config->bars = create_list())) goto cleanup;
159 if (!(config->workspace_outputs = create_list())) goto cleanup; 164 if (!(config->workspace_outputs = create_list())) goto cleanup;
160 if (!(config->pid_workspaces = create_list())) goto cleanup;
161 if (!(config->criteria = create_list())) goto cleanup; 165 if (!(config->criteria = create_list())) goto cleanup;
162 if (!(config->no_focus = create_list())) goto cleanup; 166 if (!(config->no_focus = create_list())) goto cleanup;
163 if (!(config->input_configs = create_list())) goto cleanup; 167 if (!(config->input_configs = create_list())) goto cleanup;
@@ -172,9 +176,11 @@ static void config_defaults(struct sway_config *config) {
172 strcpy(config->current_mode->name, "default"); 176 strcpy(config->current_mode->name, "default");
173 if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup; 177 if (!(config->current_mode->keysym_bindings = create_list())) goto cleanup;
174 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup; 178 if (!(config->current_mode->keycode_bindings = create_list())) goto cleanup;
179 if (!(config->current_mode->mouse_bindings = create_list())) goto cleanup;
175 list_add(config->modes, config->current_mode); 180 list_add(config->modes, config->current_mode);
176 181
177 config->floating_mod = 0; 182 config->floating_mod = 0;
183 config->floating_mod_inverse = false;
178 config->dragging_key = BTN_LEFT; 184 config->dragging_key = BTN_LEFT;
179 config->resizing_key = BTN_RIGHT; 185 config->resizing_key = BTN_RIGHT;
180 186
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 3a74331e..ae9383d6 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -10,6 +10,7 @@
10#include <sys/stat.h> 10#include <sys/stat.h>
11#include <signal.h> 11#include <signal.h>
12#include <strings.h> 12#include <strings.h>
13#include <signal.h>
13#include "sway/config.h" 14#include "sway/config.h"
14#include "stringop.h" 15#include "stringop.h"
15#include "list.h" 16#include "list.h"
@@ -175,6 +176,9 @@ void invoke_swaybar(struct bar_config *bar) {
175 if (bar->pid == 0) { 176 if (bar->pid == 0) {
176 setpgid(0, 0); 177 setpgid(0, 0);
177 close(filedes[0]); 178 close(filedes[0]);
179 sigset_t set;
180 sigemptyset(&set);
181 sigprocmask(SIG_SETMASK, &set, NULL);
178 182
179 // run custom swaybar 183 // run custom swaybar
180 size_t len = snprintf(NULL, 0, "%s -b %s", 184 size_t len = snprintf(NULL, 0, "%s -b %s",
diff --git a/sway/config/input.c b/sway/config/input.c
index 8d687a6d..9885e85c 100644
--- a/sway/config/input.c
+++ b/sway/config/input.c
@@ -33,6 +33,8 @@ struct input_config *new_input_config(const char* identifier) {
33 input->left_handed = INT_MIN; 33 input->left_handed = INT_MIN;
34 input->repeat_delay = INT_MIN; 34 input->repeat_delay = INT_MIN;
35 input->repeat_rate = INT_MIN; 35 input->repeat_rate = INT_MIN;
36 input->xkb_numlock = INT_MIN;
37 input->xkb_capslock = INT_MIN;
36 38
37 return input; 39 return input;
38} 40}
@@ -104,6 +106,12 @@ void merge_input_config(struct input_config *dst, struct input_config *src) {
104 free(dst->xkb_variant); 106 free(dst->xkb_variant);
105 dst->xkb_variant = strdup(src->xkb_variant); 107 dst->xkb_variant = strdup(src->xkb_variant);
106 } 108 }
109 if (src->xkb_numlock != INT_MIN) {
110 dst->xkb_numlock = src->xkb_numlock;
111 }
112 if (src->xkb_capslock != INT_MIN) {
113 dst->xkb_capslock = src->xkb_capslock;
114 }
107 if (src->mapped_from_region) { 115 if (src->mapped_from_region) {
108 free(dst->mapped_from_region); 116 free(dst->mapped_from_region);
109 dst->mapped_from_region = 117 dst->mapped_from_region =
diff --git a/sway/criteria.c b/sway/criteria.c
index e2b248de..39d300ea 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -10,6 +10,7 @@
10#include "stringop.h" 10#include "stringop.h"
11#include "list.h" 11#include "list.h"
12#include "log.h" 12#include "log.h"
13#include "config.h"
13 14
14bool criteria_is_empty(struct criteria *criteria) { 15bool criteria_is_empty(struct criteria *criteria) {
15 return !criteria->title 16 return !criteria->title
@@ -19,7 +20,9 @@ bool criteria_is_empty(struct criteria *criteria) {
19 && !criteria->instance 20 && !criteria->instance
20 && !criteria->con_mark 21 && !criteria->con_mark
21 && !criteria->con_id 22 && !criteria->con_id
23#ifdef HAVE_XWAYLAND
22 && !criteria->id 24 && !criteria->id
25#endif
23 && !criteria->window_role 26 && !criteria->window_role
24 && !criteria->window_type 27 && !criteria->window_type
25 && !criteria->floating 28 && !criteria->floating
@@ -127,12 +130,14 @@ static bool criteria_matches_view(struct criteria *criteria,
127 } 130 }
128 } 131 }
129 132
133#ifdef HAVE_XWAYLAND
130 if (criteria->id) { // X11 window ID 134 if (criteria->id) { // X11 window ID
131 uint32_t x11_window_id = view_get_x11_window_id(view); 135 uint32_t x11_window_id = view_get_x11_window_id(view);
132 if (!x11_window_id || x11_window_id != criteria->id) { 136 if (!x11_window_id || x11_window_id != criteria->id) {
133 return false; 137 return false;
134 } 138 }
135 } 139 }
140#endif
136 141
137 if (criteria->window_role) { 142 if (criteria->window_role) {
138 // TODO 143 // TODO
@@ -225,6 +230,15 @@ list_t *criteria_get_views(struct criteria *criteria) {
225 }; 230 };
226 container_for_each_descendant_dfs(&root_container, 231 container_for_each_descendant_dfs(&root_container,
227 criteria_get_views_iterator, &data); 232 criteria_get_views_iterator, &data);
233
234 // Scratchpad items which are hidden are not in the tree.
235 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
236 struct sway_container *con =
237 root_container.sway_root->scratchpad->items[i];
238 if (!con->parent) {
239 criteria_get_views_iterator(con, &data);
240 }
241 }
228 return matches; 242 return matches;
229} 243}
230 244
@@ -256,7 +270,9 @@ enum criteria_token {
256 T_CON_ID, 270 T_CON_ID,
257 T_CON_MARK, 271 T_CON_MARK,
258 T_FLOATING, 272 T_FLOATING,
273#ifdef HAVE_XWAYLAND
259 T_ID, 274 T_ID,
275#endif
260 T_INSTANCE, 276 T_INSTANCE,
261 T_SHELL, 277 T_SHELL,
262 T_TILING, 278 T_TILING,
@@ -278,8 +294,10 @@ static enum criteria_token token_from_name(char *name) {
278 return T_CON_ID; 294 return T_CON_ID;
279 } else if (strcmp(name, "con_mark") == 0) { 295 } else if (strcmp(name, "con_mark") == 0) {
280 return T_CON_MARK; 296 return T_CON_MARK;
297#ifdef HAVE_XWAYLAND
281 } else if (strcmp(name, "id") == 0) { 298 } else if (strcmp(name, "id") == 0) {
282 return T_ID; 299 return T_ID;
300#endif
283 } else if (strcmp(name, "instance") == 0) { 301 } else if (strcmp(name, "instance") == 0) {
284 return T_INSTANCE; 302 return T_INSTANCE;
285 } else if (strcmp(name, "shell") == 0) { 303 } else if (strcmp(name, "shell") == 0) {
@@ -346,7 +364,9 @@ static char *get_focused_prop(enum criteria_token token) {
346 case T_CON_ID: // These do not support __focused__ 364 case T_CON_ID: // These do not support __focused__
347 case T_CON_MARK: 365 case T_CON_MARK:
348 case T_FLOATING: 366 case T_FLOATING:
367#ifdef HAVE_XWAYLAND
349 case T_ID: 368 case T_ID:
369#endif
350 case T_TILING: 370 case T_TILING:
351 case T_URGENT: 371 case T_URGENT:
352 case T_WINDOW_TYPE: 372 case T_WINDOW_TYPE:
@@ -417,12 +437,14 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) {
417 case T_WINDOW_TYPE: 437 case T_WINDOW_TYPE:
418 // TODO: This is a string but will be stored as an enum or integer 438 // TODO: This is a string but will be stored as an enum or integer
419 break; 439 break;
440#ifdef HAVE_XWAYLAND
420 case T_ID: 441 case T_ID:
421 criteria->id = strtoul(effective_value, &endptr, 10); 442 criteria->id = strtoul(effective_value, &endptr, 10);
422 if (*endptr != 0) { 443 if (*endptr != 0) {
423 error = strdup("The value for 'id' should be numeric"); 444 error = strdup("The value for 'id' should be numeric");
424 } 445 }
425 break; 446 break;
447#endif
426 case T_FLOATING: 448 case T_FLOATING:
427 criteria->floating = true; 449 criteria->floating = true;
428 break; 450 break;
diff --git a/sway/desktop/layer_shell.c b/sway/desktop/layer_shell.c
index a7d96717..a2935883 100644
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -7,11 +7,13 @@
7#include <wlr/types/wlr_output_damage.h> 7#include <wlr/types/wlr_output_damage.h>
8#include <wlr/types/wlr_output.h> 8#include <wlr/types/wlr_output.h>
9#include <wlr/util/log.h> 9#include <wlr/util/log.h>
10#include "sway/desktop/transaction.h"
10#include "sway/input/input-manager.h" 11#include "sway/input/input-manager.h"
11#include "sway/input/seat.h" 12#include "sway/input/seat.h"
12#include "sway/layers.h" 13#include "sway/layers.h"
13#include "sway/output.h" 14#include "sway/output.h"
14#include "sway/server.h" 15#include "sway/server.h"
16#include "sway/tree/arrange.h"
15#include "sway/tree/layout.h" 17#include "sway/tree/layout.h"
16#include "log.h" 18#include "log.h"
17 19
@@ -245,6 +247,9 @@ static void handle_surface_commit(struct wl_listener *listener, void *data) {
245 output_damage_surface(output, layer->geo.x, layer->geo.y, 247 output_damage_surface(output, layer->geo.x, layer->geo.y,
246 layer_surface->surface, false); 248 layer_surface->surface, false);
247 } 249 }
250
251 arrange_windows(output->swayc);
252 transaction_commit_dirty();
248} 253}
249 254
250static void unmap(struct sway_layer_surface *sway_layer) { 255static void unmap(struct sway_layer_surface *sway_layer) {
@@ -282,6 +287,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
282 struct sway_output *output = sway_layer->layer_surface->output->data; 287 struct sway_output *output = sway_layer->layer_surface->output->data;
283 if (output != NULL && output->swayc != NULL) { 288 if (output != NULL && output->swayc != NULL) {
284 arrange_layers(output); 289 arrange_layers(output);
290 arrange_windows(output->swayc);
291 transaction_commit_dirty();
285 } 292 }
286 wl_list_remove(&sway_layer->output_destroy.link); 293 wl_list_remove(&sway_layer->output_destroy.link);
287 sway_layer->layer_surface->output = NULL; 294 sway_layer->layer_surface->output = NULL;
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index a9808406..66747a3f 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -14,6 +14,7 @@
14#include <wlr/types/wlr_surface.h> 14#include <wlr/types/wlr_surface.h>
15#include <wlr/util/region.h> 15#include <wlr/util/region.h>
16#include "log.h" 16#include "log.h"
17#include "config.h"
17#include "sway/config.h" 18#include "sway/config.h"
18#include "sway/input/input-manager.h" 19#include "sway/input/input-manager.h"
19#include "sway/input/seat.h" 20#include "sway/input/seat.h"
@@ -56,9 +57,21 @@ static void rotate_child_position(double *sx, double *sy, double sw, double sh,
56 *sy = ry + ph/2 - sh/2; 57 *sy = ry + ph/2 - sh/2;
57} 58}
58 59
59bool output_get_surface_box(struct root_geometry *geo, 60struct surface_iterator_data {
60 struct sway_output *output, struct wlr_surface *surface, int sx, int sy, 61 sway_surface_iterator_func_t user_iterator;
62 void *user_data;
63
64 struct sway_output *output;
65 double ox, oy;
66 int width, height;
67 float rotation;
68};
69
70static bool get_surface_box(struct surface_iterator_data *data,
71 struct wlr_surface *surface, int sx, int sy,
61 struct wlr_box *surface_box) { 72 struct wlr_box *surface_box) {
73 struct sway_output *output = data->output;
74
62 if (!wlr_surface_has_buffer(surface)) { 75 if (!wlr_surface_has_buffer(surface)) {
63 return false; 76 return false;
64 } 77 }
@@ -67,12 +80,12 @@ bool output_get_surface_box(struct root_geometry *geo,
67 int sh = surface->current.height; 80 int sh = surface->current.height;
68 81
69 double _sx = sx, _sy = sy; 82 double _sx = sx, _sy = sy;
70 rotate_child_position(&_sx, &_sy, sw, sh, geo->width, geo->height, 83 rotate_child_position(&_sx, &_sy, sw, sh, data->width, data->height,
71 geo->rotation); 84 data->rotation);
72 85
73 struct wlr_box box = { 86 struct wlr_box box = {
74 .x = geo->x + _sx, 87 .x = data->ox + _sx,
75 .y = geo->y + _sy, 88 .y = data->oy + _sy,
76 .width = sw, 89 .width = sw,
77 .height = sh, 90 .height = sh,
78 }; 91 };
@@ -81,7 +94,7 @@ bool output_get_surface_box(struct root_geometry *geo,
81 } 94 }
82 95
83 struct wlr_box rotated_box; 96 struct wlr_box rotated_box;
84 wlr_box_rotated_bounds(&box, geo->rotation, &rotated_box); 97 wlr_box_rotated_bounds(&box, data->rotation, &rotated_box);
85 98
86 struct wlr_box output_box = { 99 struct wlr_box output_box = {
87 .width = output->swayc->current.swayc_width, 100 .width = output->swayc->current.swayc_width,
@@ -92,46 +105,90 @@ bool output_get_surface_box(struct root_geometry *geo,
92 return wlr_box_intersection(&output_box, &rotated_box, &intersection); 105 return wlr_box_intersection(&output_box, &rotated_box, &intersection);
93} 106}
94 107
95void output_surface_for_each_surface(struct wlr_surface *surface, 108static void output_for_each_surface_iterator(struct wlr_surface *surface,
96 double ox, double oy, struct root_geometry *geo, 109 int sx, int sy, void *_data) {
97 wlr_surface_iterator_func_t iterator, void *user_data) { 110 struct surface_iterator_data *data = _data;
98 geo->x = ox;
99 geo->y = oy;
100 geo->width = surface->current.width;
101 geo->height = surface->current.height;
102 geo->rotation = 0;
103 111
104 wlr_surface_for_each_surface(surface, iterator, user_data); 112 struct wlr_box box;
113 bool intersects = get_surface_box(data, surface, sx, sy, &box);
114 if (!intersects) {
115 return;
116 }
117
118 data->user_iterator(data->output, surface, &box, data->rotation,
119 data->user_data);
120}
121
122void output_surface_for_each_surface(struct sway_output *output,
123 struct wlr_surface *surface, double ox, double oy,
124 sway_surface_iterator_func_t iterator, void *user_data) {
125 struct surface_iterator_data data = {
126 .user_iterator = iterator,
127 .user_data = user_data,
128 .output = output,
129 .ox = ox,
130 .oy = oy,
131 .width = surface->current.width,
132 .height = surface->current.height,
133 .rotation = 0,
134 };
135
136 wlr_surface_for_each_surface(surface,
137 output_for_each_surface_iterator, &data);
105} 138}
106 139
107void output_view_for_each_surface(struct sway_view *view, 140void output_view_for_each_surface(struct sway_output *output,
108 struct sway_output *output, struct root_geometry *geo, 141 struct sway_view *view, sway_surface_iterator_func_t iterator,
109 wlr_surface_iterator_func_t iterator, void *user_data) { 142 void *user_data) {
110 geo->x = view->swayc->current.view_x - output->swayc->current.swayc_x; 143 struct surface_iterator_data data = {
111 geo->y = view->swayc->current.view_y - output->swayc->current.swayc_y; 144 .user_iterator = iterator,
112 geo->width = view->swayc->current.view_width; 145 .user_data = user_data,
113 geo->height = view->swayc->current.view_height; 146 .output = output,
114 geo->rotation = 0; // TODO 147 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x,
148 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y,
149 .width = view->swayc->current.view_width,
150 .height = view->swayc->current.view_height,
151 .rotation = 0, // TODO
152 };
115 153
116 view_for_each_surface(view, iterator, user_data); 154 view_for_each_surface(view,
155 output_for_each_surface_iterator, &data);
117} 156}
118 157
119void output_layer_for_each_surface(struct wl_list *layer_surfaces, 158void output_view_for_each_popup(struct sway_output *output,
120 struct root_geometry *geo, wlr_surface_iterator_func_t iterator, 159 struct sway_view *view, sway_surface_iterator_func_t iterator,
160 void *user_data) {
161 struct surface_iterator_data data = {
162 .user_iterator = iterator,
163 .user_data = user_data,
164 .output = output,
165 .ox = view->swayc->current.view_x - output->swayc->current.swayc_x,
166 .oy = view->swayc->current.view_y - output->swayc->current.swayc_y,
167 .width = view->swayc->current.view_width,
168 .height = view->swayc->current.view_height,
169 .rotation = 0, // TODO
170 };
171
172 view_for_each_popup(view, output_for_each_surface_iterator, &data);
173}
174
175void output_layer_for_each_surface(struct sway_output *output,
176 struct wl_list *layer_surfaces, sway_surface_iterator_func_t iterator,
121 void *user_data) { 177 void *user_data) {
122 struct sway_layer_surface *layer_surface; 178 struct sway_layer_surface *layer_surface;
123 wl_list_for_each(layer_surface, layer_surfaces, link) { 179 wl_list_for_each(layer_surface, layer_surfaces, link) {
124 struct wlr_layer_surface *wlr_layer_surface = 180 struct wlr_layer_surface *wlr_layer_surface =
125 layer_surface->layer_surface; 181 layer_surface->layer_surface;
126 output_surface_for_each_surface(wlr_layer_surface->surface, 182 output_surface_for_each_surface(output, wlr_layer_surface->surface,
127 layer_surface->geo.x, layer_surface->geo.y, geo, iterator, 183 layer_surface->geo.x, layer_surface->geo.y, iterator,
128 user_data); 184 user_data);
129 } 185 }
130} 186}
131 187
132void output_unmanaged_for_each_surface(struct wl_list *unmanaged, 188#ifdef HAVE_XWAYLAND
133 struct sway_output *output, struct root_geometry *geo, 189void output_unmanaged_for_each_surface(struct sway_output *output,
134 wlr_surface_iterator_func_t iterator, void *user_data) { 190 struct wl_list *unmanaged, sway_surface_iterator_func_t iterator,
191 void *user_data) {
135 struct sway_xwayland_unmanaged *unmanaged_surface; 192 struct sway_xwayland_unmanaged *unmanaged_surface;
136 wl_list_for_each(unmanaged_surface, unmanaged, link) { 193 wl_list_for_each(unmanaged_surface, unmanaged, link) {
137 struct wlr_xwayland_surface *xsurface = 194 struct wlr_xwayland_surface *xsurface =
@@ -139,22 +196,24 @@ void output_unmanaged_for_each_surface(struct wl_list *unmanaged,
139 double ox = unmanaged_surface->lx - output->swayc->current.swayc_x; 196 double ox = unmanaged_surface->lx - output->swayc->current.swayc_x;
140 double oy = unmanaged_surface->ly - output->swayc->current.swayc_y; 197 double oy = unmanaged_surface->ly - output->swayc->current.swayc_y;
141 198
142 output_surface_for_each_surface(xsurface->surface, ox, oy, geo, 199 output_surface_for_each_surface(output, xsurface->surface, ox, oy,
143 iterator, user_data); 200 iterator, user_data);
144 } 201 }
145} 202}
203#endif
146 204
147void output_drag_icons_for_each_surface(struct wl_list *drag_icons, 205void output_drag_icons_for_each_surface(struct sway_output *output,
148 struct sway_output *output, struct root_geometry *geo, 206 struct wl_list *drag_icons, sway_surface_iterator_func_t iterator,
149 wlr_surface_iterator_func_t iterator, void *user_data) { 207 void *user_data) {
150 struct sway_drag_icon *drag_icon; 208 struct sway_drag_icon *drag_icon;
151 wl_list_for_each(drag_icon, drag_icons, link) { 209 wl_list_for_each(drag_icon, drag_icons, link) {
152 double ox = drag_icon->x - output->swayc->x; 210 double ox = drag_icon->x - output->swayc->x;
153 double oy = drag_icon->y - output->swayc->y; 211 double oy = drag_icon->y - output->swayc->y;
154 212
155 if (drag_icon->wlr_drag_icon->mapped) { 213 if (drag_icon->wlr_drag_icon->mapped) {
156 output_surface_for_each_surface(drag_icon->wlr_drag_icon->surface, 214 output_surface_for_each_surface(output,
157 ox, oy, geo, iterator, user_data); 215 drag_icon->wlr_drag_icon->surface, ox, oy,
216 iterator, user_data);
158 } 217 }
159 } 218 }
160} 219}
@@ -181,21 +240,14 @@ struct sway_container *output_get_active_workspace(struct sway_output *output) {
181 return workspace; 240 return workspace;
182} 241}
183 242
184bool output_has_opaque_lockscreen(struct sway_output *output, 243bool output_has_opaque_overlay_layer_surface(struct sway_output *output) {
185 struct sway_seat *seat) {
186 if (!seat->exclusive_client) {
187 return false;
188 }
189
190 struct wlr_layer_surface *wlr_layer_surface; 244 struct wlr_layer_surface *wlr_layer_surface;
191 wl_list_for_each(wlr_layer_surface, &server.layer_shell->surfaces, link) { 245 wl_list_for_each(wlr_layer_surface, &server.layer_shell->surfaces, link) {
192 if (wlr_layer_surface->output != output->wlr_output) { 246 if (wlr_layer_surface->output != output->wlr_output ||
247 wlr_layer_surface->layer != ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY) {
193 continue; 248 continue;
194 } 249 }
195 struct wlr_surface *wlr_surface = wlr_layer_surface->surface; 250 struct wlr_surface *wlr_surface = wlr_layer_surface->surface;
196 if (wlr_surface->resource->client != seat->exclusive_client) {
197 continue;
198 }
199 struct sway_layer_surface *sway_layer_surface = 251 struct sway_layer_surface *sway_layer_surface =
200 layer_from_wlr_layer_surface(wlr_layer_surface); 252 layer_from_wlr_layer_surface(wlr_layer_surface);
201 pixman_box32_t output_box = { 253 pixman_box32_t output_box = {
@@ -217,46 +269,38 @@ bool output_has_opaque_lockscreen(struct sway_output *output,
217 return false; 269 return false;
218} 270}
219 271
220struct send_frame_done_data { 272static void send_frame_done_iterator(struct sway_output *output,
221 struct root_geometry root_geo; 273 struct wlr_surface *surface, struct wlr_box *box, float rotation,
222 struct sway_output *output; 274 void *_data) {
223 struct timespec *when; 275 struct timespec *when = _data;
224 struct wl_client *exclusive_client; 276 wlr_surface_send_frame_done(surface, when);
225};
226
227static void send_frame_done_iterator(struct wlr_surface *surface,
228 int sx, int sy, void *_data) {
229 struct send_frame_done_data *data = _data;
230 if (data->exclusive_client &&
231 data->exclusive_client != surface->resource->client) {
232 return;
233 }
234
235 bool intersects = output_get_surface_box(&data->root_geo, data->output, surface,
236 sx, sy, NULL);
237 if (intersects) {
238 wlr_surface_send_frame_done(surface, data->when);
239 }
240} 277}
241 278
242static void send_frame_done_layer(struct send_frame_done_data *data, 279static void send_frame_done_layer(struct sway_output *output,
243 struct wl_list *layer_surfaces) { 280 struct wl_list *layer_surfaces, struct timespec *when) {
244 output_layer_for_each_surface(layer_surfaces, &data->root_geo, 281 output_layer_for_each_surface(output, layer_surfaces,
245 send_frame_done_iterator, data); 282 send_frame_done_iterator, when);
246} 283}
247 284
248static void send_frame_done_unmanaged(struct send_frame_done_data *data, 285#ifdef HAVE_XWAYLAND
249 struct wl_list *unmanaged) { 286static void send_frame_done_unmanaged(struct sway_output *output,
250 output_unmanaged_for_each_surface(unmanaged, data->output, &data->root_geo, 287 struct wl_list *unmanaged, struct timespec *when) {
251 send_frame_done_iterator, data); 288 output_unmanaged_for_each_surface(output, unmanaged,
289 send_frame_done_iterator, when);
252} 290}
291#endif
253 292
254static void send_frame_done_drag_icons(struct send_frame_done_data *data, 293static void send_frame_done_drag_icons(struct sway_output *output,
255 struct wl_list *drag_icons) { 294 struct wl_list *drag_icons, struct timespec *when) {
256 output_drag_icons_for_each_surface(drag_icons, data->output, &data->root_geo, 295 output_drag_icons_for_each_surface(output, drag_icons,
257 send_frame_done_iterator, data); 296 send_frame_done_iterator, when);
258} 297}
259 298
299struct send_frame_done_data {
300 struct sway_output *output;
301 struct timespec *when;
302};
303
260static void send_frame_done_container_iterator(struct sway_container *con, 304static void send_frame_done_container_iterator(struct sway_container *con,
261 void *_data) { 305 void *_data) {
262 struct send_frame_done_data *data = _data; 306 struct send_frame_done_data *data = _data;
@@ -268,52 +312,62 @@ static void send_frame_done_container_iterator(struct sway_container *con,
268 return; 312 return;
269 } 313 }
270 314
271 output_view_for_each_surface(con->sway_view, data->output, &data->root_geo, 315 output_view_for_each_surface(data->output, con->sway_view,
272 send_frame_done_iterator, data); 316 send_frame_done_iterator, data->when);
273}
274
275static void send_frame_done_container(struct send_frame_done_data *data,
276 struct sway_container *con) {
277 container_descendants(con, C_VIEW,
278 send_frame_done_container_iterator, data);
279} 317}
280 318
281static void send_frame_done(struct sway_output *output, struct timespec *when) { 319static void send_frame_done_container(struct sway_output *output,
282 struct sway_seat *seat = input_manager_current_seat(input_manager); 320 struct sway_container *con, struct timespec *when) {
283 struct send_frame_done_data data = { 321 struct send_frame_done_data data = {
284 .output = output, 322 .output = output,
285 .when = when, 323 .when = when,
286 .exclusive_client = output_has_opaque_lockscreen(output, seat) ?
287 seat->exclusive_client : NULL,
288 }; 324 };
325 container_descendants(con, C_VIEW,
326 send_frame_done_container_iterator, &data);
327}
328
329static void send_frame_done(struct sway_output *output, struct timespec *when) {
330 if (output_has_opaque_overlay_layer_surface(output)) {
331 goto send_frame_overlay;
332 }
289 333
290 struct sway_container *workspace = output_get_active_workspace(output); 334 struct sway_container *workspace = output_get_active_workspace(output);
291 if (workspace->current.ws_fullscreen) { 335 if (workspace->current.ws_fullscreen) {
292 send_frame_done_container_iterator( 336 if (workspace->current.ws_fullscreen->type == C_VIEW) {
293 workspace->current.ws_fullscreen->swayc, &data); 337 output_view_for_each_surface(output,
294 338 workspace->current.ws_fullscreen->sway_view,
295 if (workspace->current.ws_fullscreen->type == SWAY_VIEW_XWAYLAND) { 339 send_frame_done_iterator, when);
296 send_frame_done_unmanaged(&data, 340 } else {
297 &root_container.sway_root->xwayland_unmanaged); 341 send_frame_done_container(output, workspace->current.ws_fullscreen,
342 when);
298 } 343 }
344#ifdef HAVE_XWAYLAND
345 send_frame_done_unmanaged(output,
346 &root_container.sway_root->xwayland_unmanaged, when);
347#endif
299 } else { 348 } else {
300 send_frame_done_layer(&data, 349 send_frame_done_layer(output,
301 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]); 350 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], when);
302 send_frame_done_layer(&data, 351 send_frame_done_layer(output,
303 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]); 352 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], when);
304 353
305 send_frame_done_container(&data, workspace); 354 send_frame_done_container(output, workspace, when);
306 send_frame_done_container(&data, workspace->sway_workspace->floating); 355 send_frame_done_container(output, workspace->sway_workspace->floating,
307 356 when);
308 send_frame_done_unmanaged(&data, 357
309 &root_container.sway_root->xwayland_unmanaged); 358#ifdef HAVE_XWAYLAND
310 send_frame_done_layer(&data, 359 send_frame_done_unmanaged(output,
311 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); 360 &root_container.sway_root->xwayland_unmanaged, when);
361#endif
362 send_frame_done_layer(output,
363 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], when);
312 } 364 }
313 365
314 send_frame_done_layer(&data, 366send_frame_overlay:
315 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); 367 send_frame_done_layer(output,
316 send_frame_done_drag_icons(&data, &root_container.sway_root->drag_icons); 368 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], when);
369 send_frame_done_drag_icons(output, &root_container.sway_root->drag_icons,
370 when);
317} 371}
318 372
319static void damage_handle_frame(struct wl_listener *listener, void *data) { 373static void damage_handle_frame(struct wl_listener *listener, void *data) {
@@ -348,26 +402,13 @@ void output_damage_whole(struct sway_output *output) {
348 wlr_output_damage_add_whole(output->damage); 402 wlr_output_damage_add_whole(output->damage);
349} 403}
350 404
351struct damage_data { 405static void damage_surface_iterator(struct sway_output *output,
352 struct root_geometry root_geo; 406 struct wlr_surface *surface, struct wlr_box *_box, float rotation,
353 struct sway_output *output;
354 bool whole;
355};
356
357static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy,
358 void *_data) { 407 void *_data) {
359 struct damage_data *data = _data; 408 bool *data = _data;
360 struct sway_output *output = data->output; 409 bool whole = *data;
361 float rotation = data->root_geo.rotation;
362 bool whole = data->whole;
363
364 struct wlr_box box;
365 bool intersects = output_get_surface_box(&data->root_geo, data->output, surface,
366 sx, sy, &box);
367 if (!intersects) {
368 return;
369 }
370 410
411 struct wlr_box box = *_box;
371 scale_box(&box, output->wlr_output->scale); 412 scale_box(&box, output->wlr_output->scale);
372 413
373 int center_x = box.x + box.width/2; 414 int center_x = box.x + box.width/2;
@@ -407,13 +448,8 @@ static void damage_surface_iterator(struct wlr_surface *surface, int sx, int sy,
407 448
408void output_damage_surface(struct sway_output *output, double ox, double oy, 449void output_damage_surface(struct sway_output *output, double ox, double oy,
409 struct wlr_surface *surface, bool whole) { 450 struct wlr_surface *surface, bool whole) {
410 struct damage_data data = { 451 output_surface_for_each_surface(output, surface, ox, oy,
411 .output = output, 452 damage_surface_iterator, &whole);
412 .whole = whole,
413 };
414
415 output_surface_for_each_surface(surface, ox, oy, &data.root_geo,
416 damage_surface_iterator, &data);
417} 453}
418 454
419static void output_damage_view(struct sway_output *output, 455static void output_damage_view(struct sway_output *output,
@@ -426,13 +462,7 @@ static void output_damage_view(struct sway_output *output,
426 return; 462 return;
427 } 463 }
428 464
429 struct damage_data data = { 465 output_view_for_each_surface(output, view, damage_surface_iterator, &whole);
430 .output = output,
431 .whole = whole,
432 };
433
434 output_view_for_each_surface(view, output, &data.root_geo,
435 damage_surface_iterator, &data);
436} 466}
437 467
438void output_damage_from_view(struct sway_output *output, 468void output_damage_from_view(struct sway_output *output,
@@ -463,11 +493,12 @@ static void output_damage_whole_container_iterator(struct sway_container *con,
463 493
464void output_damage_whole_container(struct sway_output *output, 494void output_damage_whole_container(struct sway_output *output,
465 struct sway_container *con) { 495 struct sway_container *con) {
496 // Pad the box by 1px, because the width is a double and might be a fraction
466 struct wlr_box box = { 497 struct wlr_box box = {
467 .x = con->current.swayc_x - output->wlr_output->lx, 498 .x = con->current.swayc_x - output->wlr_output->lx - 1,
468 .y = con->current.swayc_y - output->wlr_output->ly, 499 .y = con->current.swayc_y - output->wlr_output->ly - 1,
469 .width = con->current.swayc_width, 500 .width = con->current.swayc_width + 2,
470 .height = con->current.swayc_height, 501 .height = con->current.swayc_height + 2,
471 }; 502 };
472 scale_box(&box, output->wlr_output->scale); 503 scale_box(&box, output->wlr_output->scale);
473 wlr_output_damage_add_box(output->damage, &box); 504 wlr_output_damage_add_box(output->damage, &box);
@@ -509,22 +540,14 @@ static void handle_transform(struct wl_listener *listener, void *data) {
509 transaction_commit_dirty(); 540 transaction_commit_dirty();
510} 541}
511 542
512static void handle_scale_iterator(struct sway_container *view, void *data) {
513 view_update_marks_textures(view->sway_view);
514}
515
516static void handle_scale(struct wl_listener *listener, void *data) { 543static void handle_scale(struct wl_listener *listener, void *data) {
517 struct sway_output *output = wl_container_of(listener, output, scale); 544 struct sway_output *output = wl_container_of(listener, output, scale);
518 arrange_layers(output); 545 arrange_layers(output);
519 container_descendants(output->swayc, C_VIEW, handle_scale_iterator, NULL); 546 container_update_textures_recursive(output->swayc);
520 arrange_windows(output->swayc); 547 arrange_windows(output->swayc);
521 transaction_commit_dirty(); 548 transaction_commit_dirty();
522} 549}
523 550
524struct sway_output *output_from_wlr_output(struct wlr_output *wlr_output) {
525 return wlr_output->data;
526}
527
528void handle_new_output(struct wl_listener *listener, void *data) { 551void handle_new_output(struct wl_listener *listener, void *data) {
529 struct sway_server *server = wl_container_of(listener, server, new_output); 552 struct sway_server *server = wl_container_of(listener, server, new_output);
530 struct wlr_output *wlr_output = data; 553 struct wlr_output *wlr_output = data;
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index 4c85e516..cdac9c72 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -14,6 +14,7 @@
14#include <wlr/types/wlr_surface.h> 14#include <wlr/types/wlr_surface.h>
15#include <wlr/util/region.h> 15#include <wlr/util/region.h>
16#include "log.h" 16#include "log.h"
17#include "config.h"
17#include "sway/config.h" 18#include "sway/config.h"
18#include "sway/debug.h" 19#include "sway/debug.h"
19#include "sway/input/input-manager.h" 20#include "sway/input/input-manager.h"
@@ -28,10 +29,7 @@
28#include "sway/tree/workspace.h" 29#include "sway/tree/workspace.h"
29 30
30struct render_data { 31struct render_data {
31 struct root_geometry root_geo;
32 struct sway_output *output;
33 pixman_region32_t *damage; 32 pixman_region32_t *damage;
34 struct sway_view *view;
35 float alpha; 33 float alpha;
36}; 34};
37 35
@@ -91,11 +89,11 @@ damage_finish:
91 pixman_region32_fini(&damage); 89 pixman_region32_fini(&damage);
92} 90}
93 91
94static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy, 92static void render_surface_iterator(struct sway_output *output,
93 struct wlr_surface *surface, struct wlr_box *_box, float rotation,
95 void *_data) { 94 void *_data) {
96 struct render_data *data = _data; 95 struct render_data *data = _data;
97 struct wlr_output *wlr_output = data->output->wlr_output; 96 struct wlr_output *wlr_output = output->wlr_output;
98 float rotation = data->root_geo.rotation;
99 pixman_region32_t *output_damage = data->damage; 97 pixman_region32_t *output_damage = data->damage;
100 float alpha = data->alpha; 98 float alpha = data->alpha;
101 99
@@ -104,13 +102,7 @@ static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy,
104 return; 102 return;
105 } 103 }
106 104
107 struct wlr_box box; 105 struct wlr_box box = *_box;
108 bool intersects = output_get_surface_box(&data->root_geo, data->output,
109 surface, sx, sy, &box);
110 if (!intersects) {
111 return;
112 }
113
114 scale_box(&box, wlr_output->scale); 106 scale_box(&box, wlr_output->scale);
115 107
116 float matrix[9]; 108 float matrix[9];
@@ -125,33 +117,32 @@ static void render_surface_iterator(struct wlr_surface *surface, int sx, int sy,
125static void render_layer(struct sway_output *output, 117static void render_layer(struct sway_output *output,
126 pixman_region32_t *damage, struct wl_list *layer_surfaces) { 118 pixman_region32_t *damage, struct wl_list *layer_surfaces) {
127 struct render_data data = { 119 struct render_data data = {
128 .output = output,
129 .damage = damage, 120 .damage = damage,
130 .alpha = 1.0f, 121 .alpha = 1.0f,
131 }; 122 };
132 output_layer_for_each_surface(layer_surfaces, &data.root_geo, 123 output_layer_for_each_surface(output, layer_surfaces,
133 render_surface_iterator, &data); 124 render_surface_iterator, &data);
134} 125}
135 126
127#ifdef HAVE_XWAYLAND
136static void render_unmanaged(struct sway_output *output, 128static void render_unmanaged(struct sway_output *output,
137 pixman_region32_t *damage, struct wl_list *unmanaged) { 129 pixman_region32_t *damage, struct wl_list *unmanaged) {
138 struct render_data data = { 130 struct render_data data = {
139 .output = output,
140 .damage = damage, 131 .damage = damage,
141 .alpha = 1.0f, 132 .alpha = 1.0f,
142 }; 133 };
143 output_unmanaged_for_each_surface(unmanaged, output, &data.root_geo, 134 output_unmanaged_for_each_surface(output, unmanaged,
144 render_surface_iterator, &data); 135 render_surface_iterator, &data);
145} 136}
137#endif
146 138
147static void render_drag_icons(struct sway_output *output, 139static void render_drag_icons(struct sway_output *output,
148 pixman_region32_t *damage, struct wl_list *drag_icons) { 140 pixman_region32_t *damage, struct wl_list *drag_icons) {
149 struct render_data data = { 141 struct render_data data = {
150 .output = output,
151 .damage = damage, 142 .damage = damage,
152 .alpha = 1.0f, 143 .alpha = 1.0f,
153 }; 144 };
154 output_drag_icons_for_each_surface(drag_icons, output, &data.root_geo, 145 output_drag_icons_for_each_surface(output, drag_icons,
155 render_surface_iterator, &data); 146 render_surface_iterator, &data);
156} 147}
157 148
@@ -195,33 +186,51 @@ static void premultiply_alpha(float color[4], float opacity) {
195 color[2] *= color[3]; 186 color[2] *= color[3];
196} 187}
197 188
198static void render_view_surfaces(struct sway_view *view, 189static void render_view_toplevels(struct sway_view *view,
199 struct sway_output *output, pixman_region32_t *damage, float alpha) { 190 struct sway_output *output, pixman_region32_t *damage, float alpha) {
200 struct render_data data = { 191 struct render_data data = {
201 .output = output,
202 .damage = damage, 192 .damage = damage,
203 .view = view,
204 .alpha = alpha, 193 .alpha = alpha,
205 }; 194 };
206 output_view_for_each_surface(view, output, &data.root_geo, 195 // Render all toplevels without descending into popups
207 render_surface_iterator, &data); 196 output_surface_for_each_surface(output, view->surface,
197 view->swayc->current.view_x - output->wlr_output->lx,
198 view->swayc->current.view_y - output->wlr_output->ly,
199 render_surface_iterator, &data);
200}
201
202static void render_popup_iterator(struct sway_output *output,
203 struct wlr_surface *surface, struct wlr_box *box, float rotation,
204 void *data) {
205 // Render this popup's surface
206 render_surface_iterator(output, surface, box, rotation, data);
207
208 // Render this popup's child toplevels
209 output_surface_for_each_surface(output, surface, box->x, box->y,
210 render_surface_iterator, data);
211}
212
213static void render_view_popups(struct sway_view *view,
214 struct sway_output *output, pixman_region32_t *damage, float alpha) {
215 struct render_data data = {
216 .damage = damage,
217 .alpha = alpha,
218 };
219 output_view_for_each_popup(output, view, render_popup_iterator, &data);
208} 220}
209 221
210static void render_saved_view(struct sway_view *view, 222static void render_saved_view(struct sway_view *view,
211 struct sway_output *output, pixman_region32_t *damage, float alpha) { 223 struct sway_output *output, pixman_region32_t *damage, float alpha) {
212 struct wlr_output *wlr_output = output->wlr_output; 224 struct wlr_output *wlr_output = output->wlr_output;
213 225
214 int width, height; 226 if (!view->saved_buffer || !view->saved_buffer->texture) {
215 struct wlr_texture *texture =
216 transaction_get_saved_texture(view, &width, &height);
217 if (!texture) {
218 return; 227 return;
219 } 228 }
220 struct wlr_box box = { 229 struct wlr_box box = {
221 .x = view->swayc->current.view_x - output->swayc->current.swayc_x, 230 .x = view->swayc->current.view_x - output->swayc->current.swayc_x,
222 .y = view->swayc->current.view_y - output->swayc->current.swayc_y, 231 .y = view->swayc->current.view_y - output->swayc->current.swayc_y,
223 .width = width, 232 .width = view->saved_buffer_width,
224 .height = height, 233 .height = view->saved_buffer_height,
225 }; 234 };
226 235
227 struct wlr_box output_box = { 236 struct wlr_box output_box = {
@@ -241,7 +250,8 @@ static void render_saved_view(struct sway_view *view,
241 wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0, 250 wlr_matrix_project_box(matrix, &box, WL_OUTPUT_TRANSFORM_NORMAL, 0,
242 wlr_output->transform_matrix); 251 wlr_output->transform_matrix);
243 252
244 render_texture(wlr_output, damage, texture, &box, matrix, alpha); 253 render_texture(wlr_output, damage, view->saved_buffer->texture,
254 &box, matrix, alpha);
245} 255}
246 256
247/** 257/**
@@ -250,10 +260,10 @@ static void render_saved_view(struct sway_view *view,
250static void render_view(struct sway_output *output, pixman_region32_t *damage, 260static void render_view(struct sway_output *output, pixman_region32_t *damage,
251 struct sway_container *con, struct border_colors *colors) { 261 struct sway_container *con, struct border_colors *colors) {
252 struct sway_view *view = con->sway_view; 262 struct sway_view *view = con->sway_view;
253 if (view->swayc->instructions->length) { 263 if (view->saved_buffer) {
254 render_saved_view(view, output, damage, view->swayc->alpha); 264 render_saved_view(view, output, damage, view->swayc->alpha);
255 } else { 265 } else {
256 render_view_surfaces(view, output, damage, view->swayc->alpha); 266 render_view_toplevels(view, output, damage, view->swayc->alpha);
257 } 267 }
258 268
259 if (view->using_csd) { 269 if (view->using_csd) {
@@ -778,7 +788,7 @@ static void render_floating_container(struct sway_output *soutput,
778 } 788 }
779 render_view(soutput, damage, con, colors); 789 render_view(soutput, damage, con, colors);
780 } else { 790 } else {
781 render_container(soutput, damage, con, false); 791 render_container(soutput, damage, con, con->current.focused);
782 } 792 }
783} 793}
784 794
@@ -835,22 +845,13 @@ void output_render(struct sway_output *output, struct timespec *when,
835 } 845 }
836 846
837 struct sway_container *workspace = output_get_active_workspace(output); 847 struct sway_container *workspace = output_get_active_workspace(output);
838 struct sway_view *fullscreen_view = workspace->current.ws_fullscreen; 848 struct sway_container *fullscreen_con = workspace->current.ws_fullscreen;
839 struct sway_seat *seat = input_manager_current_seat(input_manager); 849
850 if (output_has_opaque_overlay_layer_surface(output)) {
851 goto render_overlay;
852 }
840 853
841 if (output_has_opaque_lockscreen(output, seat)) { 854 if (fullscreen_con) {
842 struct wlr_layer_surface *wlr_layer_surface = seat->focused_layer;
843 struct sway_layer_surface *sway_layer_surface =
844 layer_from_wlr_layer_surface(seat->focused_layer);
845 struct render_data data = {
846 .output = output,
847 .damage = damage,
848 .alpha = 1.0f,
849 };
850 output_surface_for_each_surface(wlr_layer_surface->surface,
851 sway_layer_surface->geo.x, sway_layer_surface->geo.y,
852 &data.root_geo, render_surface_iterator, &data);
853 } else if (fullscreen_view) {
854 float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; 855 float clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f};
855 856
856 int nrects; 857 int nrects;
@@ -861,16 +862,22 @@ void output_render(struct sway_output *output, struct timespec *when,
861 } 862 }
862 863
863 // TODO: handle views smaller than the output 864 // TODO: handle views smaller than the output
864 if (fullscreen_view->swayc->instructions->length) { 865 if (fullscreen_con->type == C_VIEW) {
865 render_saved_view(fullscreen_view, output, damage, 1.0f); 866 if (fullscreen_con->sway_view->saved_buffer) {
867 render_saved_view(fullscreen_con->sway_view,
868 output, damage, 1.0f);
869 } else {
870 render_view_toplevels(fullscreen_con->sway_view,
871 output, damage, 1.0f);
872 }
866 } else { 873 } else {
867 render_view_surfaces(fullscreen_view, output, damage, 1.0f); 874 render_container(output, damage, fullscreen_con,
868 } 875 fullscreen_con->current.focused);
869
870 if (fullscreen_view->type == SWAY_VIEW_XWAYLAND) {
871 render_unmanaged(output, damage,
872 &root_container.sway_root->xwayland_unmanaged);
873 } 876 }
877#ifdef HAVE_XWAYLAND
878 render_unmanaged(output, damage,
879 &root_container.sway_root->xwayland_unmanaged);
880#endif
874 } else { 881 } else {
875 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f}; 882 float clear_color[] = {0.25f, 0.25f, 0.25f, 1.0f};
876 883
@@ -888,12 +895,21 @@ void output_render(struct sway_output *output, struct timespec *when,
888 895
889 render_container(output, damage, workspace, workspace->current.focused); 896 render_container(output, damage, workspace, workspace->current.focused);
890 render_floating(output, damage); 897 render_floating(output, damage);
891 898#ifdef HAVE_XWAYLAND
892 render_unmanaged(output, damage, 899 render_unmanaged(output, damage,
893 &root_container.sway_root->xwayland_unmanaged); 900 &root_container.sway_root->xwayland_unmanaged);
901#endif
894 render_layer(output, damage, 902 render_layer(output, damage,
895 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]); 903 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]);
896 } 904 }
905
906 struct sway_seat *seat = input_manager_current_seat(input_manager);
907 struct sway_container *focus = seat_get_focus(seat);
908 if (focus && focus->type == C_VIEW) {
909 render_view_popups(focus->sway_view, output, damage, focus->alpha);
910 }
911
912render_overlay:
897 render_layer(output, damage, 913 render_layer(output, damage,
898 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]); 914 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]);
899 render_drag_icons(output, damage, &root_container.sway_root->drag_icons); 915 render_drag_icons(output, damage, &root_container.sway_root->drag_icons);
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index 19f41efc..4e6af86a 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <errno.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <string.h> 5#include <string.h>
@@ -40,8 +41,6 @@ struct sway_transaction_instruction {
40 struct sway_transaction *transaction; 41 struct sway_transaction *transaction;
41 struct sway_container *container; 42 struct sway_container *container;
42 struct sway_container_state state; 43 struct sway_container_state state;
43 struct wlr_buffer *saved_buffer;
44 int saved_buffer_width, saved_buffer_height;
45 uint32_t serial; 44 uint32_t serial;
46 bool ready; 45 bool ready;
47}; 46};
@@ -56,27 +55,6 @@ static struct sway_transaction *transaction_create() {
56 return transaction; 55 return transaction;
57} 56}
58 57
59static void remove_saved_view_buffer(
60 struct sway_transaction_instruction *instruction) {
61 if (instruction->saved_buffer) {
62 wlr_buffer_unref(instruction->saved_buffer);
63 instruction->saved_buffer = NULL;
64 }
65}
66
67static void save_view_buffer(struct sway_view *view,
68 struct sway_transaction_instruction *instruction) {
69 if (!sway_assert(instruction->saved_buffer == NULL,
70 "Didn't expect instruction to have a saved buffer already")) {
71 remove_saved_view_buffer(instruction);
72 }
73 if (view->surface && wlr_surface_has_buffer(view->surface)) {
74 instruction->saved_buffer = wlr_buffer_ref(view->surface->buffer);
75 instruction->saved_buffer_width = view->surface->current.width;
76 instruction->saved_buffer_height = view->surface->current.height;
77 }
78}
79
80static void transaction_destroy(struct sway_transaction *transaction) { 58static void transaction_destroy(struct sway_transaction *transaction) {
81 // Free instructions 59 // Free instructions
82 for (int i = 0; i < transaction->instructions->length; ++i) { 60 for (int i = 0; i < transaction->instructions->length; ++i) {
@@ -92,7 +70,6 @@ static void transaction_destroy(struct sway_transaction *transaction) {
92 if (con->destroying && !con->instructions->length) { 70 if (con->destroying && !con->instructions->length) {
93 container_free(con); 71 container_free(con);
94 } 72 }
95 remove_saved_view_buffer(instruction);
96 free(instruction); 73 free(instruction);
97 } 74 }
98 list_free(transaction->instructions); 75 list_free(transaction->instructions);
@@ -110,6 +87,7 @@ static void copy_pending_state(struct sway_container *container,
110 state->swayc_y = container->y; 87 state->swayc_y = container->y;
111 state->swayc_width = container->width; 88 state->swayc_width = container->width;
112 state->swayc_height = container->height; 89 state->swayc_height = container->height;
90 state->is_fullscreen = container->is_fullscreen;
113 state->has_gaps = container->has_gaps; 91 state->has_gaps = container->has_gaps;
114 state->current_gaps = container->current_gaps; 92 state->current_gaps = container->current_gaps;
115 state->gaps_inner = container->gaps_inner; 93 state->gaps_inner = container->gaps_inner;
@@ -122,7 +100,6 @@ static void copy_pending_state(struct sway_container *container,
122 state->view_y = view->y; 100 state->view_y = view->y;
123 state->view_width = view->width; 101 state->view_width = view->width;
124 state->view_height = view->height; 102 state->view_height = view->height;
125 state->is_fullscreen = view->is_fullscreen;
126 state->border = view->border; 103 state->border = view->border;
127 state->border_thickness = view->border_thickness; 104 state->border_thickness = view->border_thickness;
128 state->border_top = view->border_top; 105 state->border_top = view->border_top;
@@ -157,9 +134,6 @@ static void transaction_add_container(struct sway_transaction *transaction,
157 134
158 copy_pending_state(container, &instruction->state); 135 copy_pending_state(container, &instruction->state);
159 136
160 if (container->type == C_VIEW) {
161 save_view_buffer(container->sway_view, instruction);
162 }
163 list_add(transaction->instructions, instruction); 137 list_add(transaction->instructions, instruction);
164} 138}
165 139
@@ -219,27 +193,35 @@ static void transaction_apply(struct sway_transaction *transaction) {
219 193
220 memcpy(&container->current, &instruction->state, 194 memcpy(&container->current, &instruction->state,
221 sizeof(struct sway_container_state)); 195 sizeof(struct sway_container_state));
196
197 if (container->type == C_VIEW) {
198 if (container->destroying) {
199 if (container->instructions->length == 1 &&
200 container->sway_view->saved_buffer) {
201 view_remove_saved_buffer(container->sway_view);
202 }
203 } else {
204 if (container->sway_view->saved_buffer) {
205 view_remove_saved_buffer(container->sway_view);
206 }
207 if (container->instructions->length > 1) {
208 view_save_buffer(container->sway_view);
209 }
210 }
211 }
222 } 212 }
223} 213}
224 214
225/**
226 * For simplicity, we only progress the queue if it can be completely flushed.
227 */
228static void transaction_progress_queue() { 215static void transaction_progress_queue() {
229 // We iterate this list in reverse because we're more likely to find a 216 while (server.transactions->length) {
230 // waiting transactions at the end of the list. 217 struct sway_transaction *transaction = server.transactions->items[0];
231 for (int i = server.transactions->length - 1; i >= 0; --i) {
232 struct sway_transaction *transaction = server.transactions->items[i];
233 if (transaction->num_waiting) { 218 if (transaction->num_waiting) {
234 return; 219 return;
235 } 220 }
236 }
237 for (int i = 0; i < server.transactions->length; ++i) {
238 struct sway_transaction *transaction = server.transactions->items[i];
239 transaction_apply(transaction); 221 transaction_apply(transaction);
240 transaction_destroy(transaction); 222 transaction_destroy(transaction);
223 list_del(server.transactions, 0);
241 } 224 }
242 server.transactions->length = 0;
243 idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1); 225 idle_inhibit_v1_check_active(server.idle_inhibit_manager_v1);
244} 226}
245 227
@@ -302,6 +284,9 @@ static void transaction_commit(struct sway_transaction *transaction) {
302 struct timespec when; 284 struct timespec when;
303 wlr_surface_send_frame_done(con->sway_view->surface, &when); 285 wlr_surface_send_frame_done(con->sway_view->surface, &when);
304 } 286 }
287 if (con->type == C_VIEW && !con->sway_view->saved_buffer) {
288 view_save_buffer(con->sway_view);
289 }
305 list_add(con->instructions, instruction); 290 list_add(con->instructions, instruction);
306 } 291 }
307 transaction->num_configures = transaction->num_waiting; 292 transaction->num_configures = transaction->num_waiting;
@@ -324,7 +309,14 @@ static void transaction_commit(struct sway_transaction *transaction) {
324 // Set up a timer which the views must respond within 309 // Set up a timer which the views must respond within
325 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop, 310 transaction->timer = wl_event_loop_add_timer(server.wl_event_loop,
326 handle_timeout, transaction); 311 handle_timeout, transaction);
327 wl_event_source_timer_update(transaction->timer, txn_timeout_ms); 312 if (transaction->timer) {
313 wl_event_source_timer_update(transaction->timer, txn_timeout_ms);
314 } else {
315 wlr_log(WLR_ERROR, "Unable to create transaction timer (%s). "
316 "Some imperfect frames might be rendered.",
317 strerror(errno));
318 handle_timeout(transaction);
319 }
328 } 320 }
329 321
330 // The debug tree shows the pending/live tree. Here is a good place to 322 // The debug tree shows the pending/live tree. Here is a good place to
@@ -352,13 +344,11 @@ static void set_instruction_ready(
352 344
353 } 345 }
354 346
355 // If all views are ready, apply the transaction.
356 // If the transaction has timed out then its num_waiting will be 0 already. 347 // If the transaction has timed out then its num_waiting will be 0 already.
357 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) { 348 if (transaction->num_waiting > 0 && --transaction->num_waiting == 0) {
358 if (!txn_debug) { 349 if (!txn_debug) {
359 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction); 350 wlr_log(WLR_DEBUG, "Transaction %p is ready", transaction);
360 wl_event_source_timer_update(transaction->timer, 0); 351 wl_event_source_timer_update(transaction->timer, 0);
361 transaction_progress_queue();
362 } 352 }
363 } 353 }
364} 354}
@@ -375,6 +365,7 @@ static void set_instructions_ready(struct sway_view *view, int index) {
375 set_instruction_ready(instruction); 365 set_instruction_ready(instruction);
376 } 366 }
377 } 367 }
368 transaction_progress_queue();
378} 369}
379 370
380void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) { 371void transaction_notify_view_ready(struct sway_view *view, uint32_t serial) {
@@ -401,18 +392,6 @@ void transaction_notify_view_ready_by_size(struct sway_view *view,
401 } 392 }
402} 393}
403 394
404struct wlr_texture *transaction_get_saved_texture(struct sway_view *view,
405 int *width, int *height) {
406 struct sway_transaction_instruction *instruction =
407 view->swayc->instructions->items[0];
408 if (!instruction->saved_buffer) {
409 return NULL;
410 }
411 *width = instruction->saved_buffer_width;
412 *height = instruction->saved_buffer_height;
413 return instruction->saved_buffer->texture;
414}
415
416void transaction_commit_dirty(void) { 395void transaction_commit_dirty(void) {
417 if (!server.dirty_containers->length) { 396 if (!server.dirty_containers->length) {
418 return; 397 return;
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c
index 98c16faf..b364663d 100644
--- a/sway/desktop/xdg_shell.c
+++ b/sway/desktop/xdg_shell.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 199309L 1#define _POSIX_C_SOURCE 199309L
2#include <float.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <wayland-server.h> 5#include <wayland-server.h>
@@ -95,6 +96,16 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view(
95 return (struct sway_xdg_shell_view *)view; 96 return (struct sway_xdg_shell_view *)view;
96} 97}
97 98
99static void get_constraints(struct sway_view *view, double *min_width,
100 double *max_width, double *min_height, double *max_height) {
101 struct wlr_xdg_toplevel_state *state =
102 &view->wlr_xdg_surface->toplevel->current;
103 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
104 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
105 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
106 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
107}
108
98static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 109static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
99 if (xdg_shell_view_from_view(view) == NULL) { 110 if (xdg_shell_view_from_view(view) == NULL) {
100 return NULL; 111 return NULL;
@@ -168,6 +179,14 @@ static void for_each_surface(struct sway_view *view,
168 user_data); 179 user_data);
169} 180}
170 181
182static void for_each_popup(struct sway_view *view,
183 wlr_surface_iterator_func_t iterator, void *user_data) {
184 if (xdg_shell_view_from_view(view) == NULL) {
185 return;
186 }
187 wlr_xdg_surface_for_each_popup(view->wlr_xdg_surface, iterator, user_data);
188}
189
171static void _close(struct sway_view *view) { 190static void _close(struct sway_view *view) {
172 if (xdg_shell_view_from_view(view) == NULL) { 191 if (xdg_shell_view_from_view(view) == NULL) {
173 return; 192 return;
@@ -178,6 +197,18 @@ static void _close(struct sway_view *view) {
178 } 197 }
179} 198}
180 199
200static void close_popups_iterator(struct wlr_surface *surface,
201 int sx, int sy, void *data) {
202 struct wlr_xdg_surface *xdg_surface =
203 wlr_xdg_surface_from_wlr_surface(surface);
204 wlr_xdg_surface_send_close(xdg_surface);
205}
206
207static void close_popups(struct sway_view *view) {
208 wlr_xdg_surface_for_each_popup(view->wlr_xdg_surface,
209 close_popups_iterator, NULL);
210}
211
181static void destroy(struct sway_view *view) { 212static void destroy(struct sway_view *view) {
182 struct sway_xdg_shell_view *xdg_shell_view = 213 struct sway_xdg_shell_view *xdg_shell_view =
183 xdg_shell_view_from_view(view); 214 xdg_shell_view_from_view(view);
@@ -188,6 +219,7 @@ static void destroy(struct sway_view *view) {
188} 219}
189 220
190static const struct sway_view_impl view_impl = { 221static const struct sway_view_impl view_impl = {
222 .get_constraints = get_constraints,
191 .get_string_prop = get_string_prop, 223 .get_string_prop = get_string_prop,
192 .configure = configure, 224 .configure = configure,
193 .set_activated = set_activated, 225 .set_activated = set_activated,
@@ -195,7 +227,9 @@ static const struct sway_view_impl view_impl = {
195 .set_fullscreen = set_fullscreen, 227 .set_fullscreen = set_fullscreen,
196 .wants_floating = wants_floating, 228 .wants_floating = wants_floating,
197 .for_each_surface = for_each_surface, 229 .for_each_surface = for_each_surface,
230 .for_each_popup = for_each_popup,
198 .close = _close, 231 .close = _close,
232 .close_popups = close_popups,
199 .destroy = destroy, 233 .destroy = destroy,
200}; 234};
201 235
@@ -213,10 +247,24 @@ static void handle_commit(struct wl_listener *listener, void *data) {
213 transaction_notify_view_ready(view, xdg_surface->configure_serial); 247 transaction_notify_view_ready(view, xdg_surface->configure_serial);
214 } 248 }
215 249
216 view_update_title(view, false);
217 view_damage_from(view); 250 view_damage_from(view);
218} 251}
219 252
253static void handle_set_title(struct wl_listener *listener, void *data) {
254 struct sway_xdg_shell_view *xdg_shell_view =
255 wl_container_of(listener, xdg_shell_view, set_title);
256 struct sway_view *view = &xdg_shell_view->view;
257 view_update_title(view, false);
258 view_execute_criteria(view);
259}
260
261static void handle_set_app_id(struct wl_listener *listener, void *data) {
262 struct sway_xdg_shell_view *xdg_shell_view =
263 wl_container_of(listener, xdg_shell_view, set_app_id);
264 struct sway_view *view = &xdg_shell_view->view;
265 view_execute_criteria(view);
266}
267
220static void handle_new_popup(struct wl_listener *listener, void *data) { 268static void handle_new_popup(struct wl_listener *listener, void *data) {
221 struct sway_xdg_shell_view *xdg_shell_view = 269 struct sway_xdg_shell_view *xdg_shell_view =
222 wl_container_of(listener, xdg_shell_view, new_popup); 270 wl_container_of(listener, xdg_shell_view, new_popup);
@@ -241,13 +289,41 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
241 return; 289 return;
242 } 290 }
243 291
244 view_set_fullscreen(view, e->fullscreen); 292 container_set_fullscreen(view->swayc, e->fullscreen);
245 293
246 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 294 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
247 arrange_windows(output); 295 arrange_windows(output);
248 transaction_commit_dirty(); 296 transaction_commit_dirty();
249} 297}
250 298
299static void handle_request_move(struct wl_listener *listener, void *data) {
300 struct sway_xdg_shell_view *xdg_shell_view =
301 wl_container_of(listener, xdg_shell_view, request_move);
302 struct sway_view *view = &xdg_shell_view->view;
303 if (!container_is_floating(view->swayc)) {
304 return;
305 }
306 struct wlr_xdg_toplevel_move_event *e = data;
307 struct sway_seat *seat = e->seat->seat->data;
308 if (e->serial == seat->last_button_serial) {
309 seat_begin_move(seat, view->swayc, seat->last_button);
310 }
311}
312
313static void handle_request_resize(struct wl_listener *listener, void *data) {
314 struct sway_xdg_shell_view *xdg_shell_view =
315 wl_container_of(listener, xdg_shell_view, request_resize);
316 struct sway_view *view = &xdg_shell_view->view;
317 if (!container_is_floating(view->swayc)) {
318 return;
319 }
320 struct wlr_xdg_toplevel_resize_event *e = data;
321 struct sway_seat *seat = e->seat->seat->data;
322 if (e->serial == seat->last_button_serial) {
323 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
324 }
325}
326
251static void handle_unmap(struct wl_listener *listener, void *data) { 327static void handle_unmap(struct wl_listener *listener, void *data) {
252 struct sway_xdg_shell_view *xdg_shell_view = 328 struct sway_xdg_shell_view *xdg_shell_view =
253 wl_container_of(listener, xdg_shell_view, unmap); 329 wl_container_of(listener, xdg_shell_view, unmap);
@@ -262,6 +338,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
262 wl_list_remove(&xdg_shell_view->commit.link); 338 wl_list_remove(&xdg_shell_view->commit.link);
263 wl_list_remove(&xdg_shell_view->new_popup.link); 339 wl_list_remove(&xdg_shell_view->new_popup.link);
264 wl_list_remove(&xdg_shell_view->request_fullscreen.link); 340 wl_list_remove(&xdg_shell_view->request_fullscreen.link);
341 wl_list_remove(&xdg_shell_view->request_move.link);
342 wl_list_remove(&xdg_shell_view->request_resize.link);
343 wl_list_remove(&xdg_shell_view->set_title.link);
344 wl_list_remove(&xdg_shell_view->set_app_id.link);
265} 345}
266 346
267static void handle_map(struct wl_listener *listener, void *data) { 347static void handle_map(struct wl_listener *listener, void *data) {
@@ -280,7 +360,7 @@ static void handle_map(struct wl_listener *listener, void *data) {
280 view_map(view, view->wlr_xdg_surface->surface); 360 view_map(view, view->wlr_xdg_surface->surface);
281 361
282 if (xdg_surface->toplevel->client_pending.fullscreen) { 362 if (xdg_surface->toplevel->client_pending.fullscreen) {
283 view_set_fullscreen(view, true); 363 container_set_fullscreen(view->swayc, true);
284 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 364 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
285 arrange_windows(ws); 365 arrange_windows(ws);
286 } else { 366 } else {
@@ -299,6 +379,22 @@ static void handle_map(struct wl_listener *listener, void *data) {
299 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen; 379 xdg_shell_view->request_fullscreen.notify = handle_request_fullscreen;
300 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, 380 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen,
301 &xdg_shell_view->request_fullscreen); 381 &xdg_shell_view->request_fullscreen);
382
383 xdg_shell_view->request_move.notify = handle_request_move;
384 wl_signal_add(&xdg_surface->toplevel->events.request_move,
385 &xdg_shell_view->request_move);
386
387 xdg_shell_view->request_resize.notify = handle_request_resize;
388 wl_signal_add(&xdg_surface->toplevel->events.request_resize,
389 &xdg_shell_view->request_resize);
390
391 xdg_shell_view->set_title.notify = handle_set_title;
392 wl_signal_add(&xdg_surface->toplevel->events.set_title,
393 &xdg_shell_view->set_title);
394
395 xdg_shell_view->set_app_id.notify = handle_set_app_id;
396 wl_signal_add(&xdg_surface->toplevel->events.set_app_id,
397 &xdg_shell_view->set_app_id);
302} 398}
303 399
304static void handle_destroy(struct wl_listener *listener, void *data) { 400static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -344,9 +440,6 @@ void handle_xdg_shell_surface(struct wl_listener *listener, void *data) {
344 view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl); 440 view_init(&xdg_shell_view->view, SWAY_VIEW_XDG_SHELL, &view_impl);
345 xdg_shell_view->view.wlr_xdg_surface = xdg_surface; 441 xdg_shell_view->view.wlr_xdg_surface = xdg_surface;
346 442
347 // TODO:
348 // - Look up pid and open on appropriate workspace
349
350 xdg_shell_view->map.notify = handle_map; 443 xdg_shell_view->map.notify = handle_map;
351 wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map); 444 wl_signal_add(&xdg_surface->events.map, &xdg_shell_view->map);
352 445
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 4d76f0a7..ffea03ad 100644
--- a/sway/desktop/xdg_shell_v6.c
+++ b/sway/desktop/xdg_shell_v6.c
@@ -1,4 +1,5 @@
1#define _POSIX_C_SOURCE 199309L 1#define _POSIX_C_SOURCE 199309L
2#include <float.h>
2#include <stdbool.h> 3#include <stdbool.h>
3#include <stdlib.h> 4#include <stdlib.h>
4#include <wayland-server.h> 5#include <wayland-server.h>
@@ -94,6 +95,16 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view(
94 return (struct sway_xdg_shell_v6_view *)view; 95 return (struct sway_xdg_shell_v6_view *)view;
95} 96}
96 97
98static void get_constraints(struct sway_view *view, double *min_width,
99 double *max_width, double *min_height, double *max_height) {
100 struct wlr_xdg_toplevel_v6_state *state =
101 &view->wlr_xdg_surface_v6->toplevel->current;
102 *min_width = state->min_width > 0 ? state->min_width : DBL_MIN;
103 *max_width = state->max_width > 0 ? state->max_width : DBL_MAX;
104 *min_height = state->min_height > 0 ? state->min_height : DBL_MIN;
105 *max_height = state->max_height > 0 ? state->max_height : DBL_MAX;
106}
107
97static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { 108static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
98 if (xdg_shell_v6_view_from_view(view) == NULL) { 109 if (xdg_shell_v6_view_from_view(view) == NULL) {
99 return NULL; 110 return NULL;
@@ -164,6 +175,15 @@ static void for_each_surface(struct sway_view *view,
164 user_data); 175 user_data);
165} 176}
166 177
178static void for_each_popup(struct sway_view *view,
179 wlr_surface_iterator_func_t iterator, void *user_data) {
180 if (xdg_shell_v6_view_from_view(view) == NULL) {
181 return;
182 }
183 wlr_xdg_surface_v6_for_each_popup(view->wlr_xdg_surface_v6, iterator,
184 user_data);
185}
186
167static void _close(struct sway_view *view) { 187static void _close(struct sway_view *view) {
168 if (xdg_shell_v6_view_from_view(view) == NULL) { 188 if (xdg_shell_v6_view_from_view(view) == NULL) {
169 return; 189 return;
@@ -174,6 +194,18 @@ static void _close(struct sway_view *view) {
174 } 194 }
175} 195}
176 196
197static void close_popups_iterator(struct wlr_surface *surface,
198 int sx, int sy, void *data) {
199 struct wlr_xdg_surface_v6 *xdg_surface_v6 =
200 wlr_xdg_surface_v6_from_wlr_surface(surface);
201 wlr_xdg_surface_v6_send_close(xdg_surface_v6);
202}
203
204static void close_popups(struct sway_view *view) {
205 wlr_xdg_surface_v6_for_each_popup(view->wlr_xdg_surface_v6,
206 close_popups_iterator, NULL);
207}
208
177static void destroy(struct sway_view *view) { 209static void destroy(struct sway_view *view) {
178 struct sway_xdg_shell_v6_view *xdg_shell_v6_view = 210 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
179 xdg_shell_v6_view_from_view(view); 211 xdg_shell_v6_view_from_view(view);
@@ -184,6 +216,7 @@ static void destroy(struct sway_view *view) {
184} 216}
185 217
186static const struct sway_view_impl view_impl = { 218static const struct sway_view_impl view_impl = {
219 .get_constraints = get_constraints,
187 .get_string_prop = get_string_prop, 220 .get_string_prop = get_string_prop,
188 .configure = configure, 221 .configure = configure,
189 .set_activated = set_activated, 222 .set_activated = set_activated,
@@ -191,7 +224,9 @@ static const struct sway_view_impl view_impl = {
191 .set_fullscreen = set_fullscreen, 224 .set_fullscreen = set_fullscreen,
192 .wants_floating = wants_floating, 225 .wants_floating = wants_floating,
193 .for_each_surface = for_each_surface, 226 .for_each_surface = for_each_surface,
227 .for_each_popup = for_each_popup,
194 .close = _close, 228 .close = _close,
229 .close_popups = close_popups,
195 .destroy = destroy, 230 .destroy = destroy,
196}; 231};
197 232
@@ -208,10 +243,24 @@ static void handle_commit(struct wl_listener *listener, void *data) {
208 transaction_notify_view_ready(view, xdg_surface_v6->configure_serial); 243 transaction_notify_view_ready(view, xdg_surface_v6->configure_serial);
209 } 244 }
210 245
211 view_update_title(view, false);
212 view_damage_from(view); 246 view_damage_from(view);
213} 247}
214 248
249static void handle_set_title(struct wl_listener *listener, void *data) {
250 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
251 wl_container_of(listener, xdg_shell_v6_view, set_title);
252 struct sway_view *view = &xdg_shell_v6_view->view;
253 view_update_title(view, false);
254 view_execute_criteria(view);
255}
256
257static void handle_set_app_id(struct wl_listener *listener, void *data) {
258 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
259 wl_container_of(listener, xdg_shell_v6_view, set_app_id);
260 struct sway_view *view = &xdg_shell_v6_view->view;
261 view_execute_criteria(view);
262}
263
215static void handle_new_popup(struct wl_listener *listener, void *data) { 264static void handle_new_popup(struct wl_listener *listener, void *data) {
216 struct sway_xdg_shell_v6_view *xdg_shell_v6_view = 265 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
217 wl_container_of(listener, xdg_shell_v6_view, new_popup); 266 wl_container_of(listener, xdg_shell_v6_view, new_popup);
@@ -236,13 +285,41 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
236 return; 285 return;
237 } 286 }
238 287
239 view_set_fullscreen(view, e->fullscreen); 288 container_set_fullscreen(view->swayc, e->fullscreen);
240 289
241 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 290 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
242 arrange_windows(output); 291 arrange_windows(output);
243 transaction_commit_dirty(); 292 transaction_commit_dirty();
244} 293}
245 294
295static void handle_request_move(struct wl_listener *listener, void *data) {
296 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
297 wl_container_of(listener, xdg_shell_v6_view, request_move);
298 struct sway_view *view = &xdg_shell_v6_view->view;
299 if (!container_is_floating(view->swayc)) {
300 return;
301 }
302 struct wlr_xdg_toplevel_v6_move_event *e = data;
303 struct sway_seat *seat = e->seat->seat->data;
304 if (e->serial == seat->last_button_serial) {
305 seat_begin_move(seat, view->swayc, seat->last_button);
306 }
307}
308
309static void handle_request_resize(struct wl_listener *listener, void *data) {
310 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
311 wl_container_of(listener, xdg_shell_v6_view, request_resize);
312 struct sway_view *view = &xdg_shell_v6_view->view;
313 if (!container_is_floating(view->swayc)) {
314 return;
315 }
316 struct wlr_xdg_toplevel_v6_resize_event *e = data;
317 struct sway_seat *seat = e->seat->seat->data;
318 if (e->serial == seat->last_button_serial) {
319 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
320 }
321}
322
246static void handle_unmap(struct wl_listener *listener, void *data) { 323static void handle_unmap(struct wl_listener *listener, void *data) {
247 struct sway_xdg_shell_v6_view *xdg_shell_v6_view = 324 struct sway_xdg_shell_v6_view *xdg_shell_v6_view =
248 wl_container_of(listener, xdg_shell_v6_view, unmap); 325 wl_container_of(listener, xdg_shell_v6_view, unmap);
@@ -257,6 +334,10 @@ static void handle_unmap(struct wl_listener *listener, void *data) {
257 wl_list_remove(&xdg_shell_v6_view->commit.link); 334 wl_list_remove(&xdg_shell_v6_view->commit.link);
258 wl_list_remove(&xdg_shell_v6_view->new_popup.link); 335 wl_list_remove(&xdg_shell_v6_view->new_popup.link);
259 wl_list_remove(&xdg_shell_v6_view->request_fullscreen.link); 336 wl_list_remove(&xdg_shell_v6_view->request_fullscreen.link);
337 wl_list_remove(&xdg_shell_v6_view->request_move.link);
338 wl_list_remove(&xdg_shell_v6_view->request_resize.link);
339 wl_list_remove(&xdg_shell_v6_view->set_title.link);
340 wl_list_remove(&xdg_shell_v6_view->set_app_id.link);
260} 341}
261 342
262static void handle_map(struct wl_listener *listener, void *data) { 343static void handle_map(struct wl_listener *listener, void *data) {
@@ -275,7 +356,7 @@ static void handle_map(struct wl_listener *listener, void *data) {
275 view_map(view, view->wlr_xdg_surface_v6->surface); 356 view_map(view, view->wlr_xdg_surface_v6->surface);
276 357
277 if (xdg_surface->toplevel->client_pending.fullscreen) { 358 if (xdg_surface->toplevel->client_pending.fullscreen) {
278 view_set_fullscreen(view, true); 359 container_set_fullscreen(view->swayc, true);
279 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 360 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
280 arrange_windows(ws); 361 arrange_windows(ws);
281 } else { 362 } else {
@@ -294,6 +375,22 @@ static void handle_map(struct wl_listener *listener, void *data) {
294 xdg_shell_v6_view->request_fullscreen.notify = handle_request_fullscreen; 375 xdg_shell_v6_view->request_fullscreen.notify = handle_request_fullscreen;
295 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen, 376 wl_signal_add(&xdg_surface->toplevel->events.request_fullscreen,
296 &xdg_shell_v6_view->request_fullscreen); 377 &xdg_shell_v6_view->request_fullscreen);
378
379 xdg_shell_v6_view->request_move.notify = handle_request_move;
380 wl_signal_add(&xdg_surface->toplevel->events.request_move,
381 &xdg_shell_v6_view->request_move);
382
383 xdg_shell_v6_view->request_resize.notify = handle_request_resize;
384 wl_signal_add(&xdg_surface->toplevel->events.request_resize,
385 &xdg_shell_v6_view->request_resize);
386
387 xdg_shell_v6_view->set_title.notify = handle_set_title;
388 wl_signal_add(&xdg_surface->toplevel->events.set_title,
389 &xdg_shell_v6_view->set_title);
390
391 xdg_shell_v6_view->set_app_id.notify = handle_set_app_id;
392 wl_signal_add(&xdg_surface->toplevel->events.set_app_id,
393 &xdg_shell_v6_view->set_app_id);
297} 394}
298 395
299static void handle_destroy(struct wl_listener *listener, void *data) { 396static void handle_destroy(struct wl_listener *listener, void *data) {
@@ -335,9 +432,6 @@ void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data) {
335 view_init(&xdg_shell_v6_view->view, SWAY_VIEW_XDG_SHELL_V6, &view_impl); 432 view_init(&xdg_shell_v6_view->view, SWAY_VIEW_XDG_SHELL_V6, &view_impl);
336 xdg_shell_v6_view->view.wlr_xdg_surface_v6 = xdg_surface; 433 xdg_shell_v6_view->view.wlr_xdg_surface_v6 = xdg_surface;
337 434
338 // TODO:
339 // - Look up pid and open on appropriate workspace
340
341 xdg_shell_v6_view->map.notify = handle_map; 435 xdg_shell_v6_view->map.notify = handle_map;
342 wl_signal_add(&xdg_surface->events.map, &xdg_shell_v6_view->map); 436 wl_signal_add(&xdg_surface->events.map, &xdg_shell_v6_view->map);
343 437
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index bce0a37b..398446f8 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -69,11 +69,13 @@ static void unmanaged_handle_map(struct wl_listener *listener, void *data) {
69 surface->ly = xsurface->y; 69 surface->ly = xsurface->y;
70 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true); 70 desktop_damage_surface(xsurface->surface, surface->lx, surface->ly, true);
71 71
72 struct sway_seat *seat = input_manager_current_seat(input_manager); 72 if (wlr_xwayland_or_surface_wants_focus(xsurface)) {
73 struct wlr_xwayland *xwayland = 73 struct sway_seat *seat = input_manager_current_seat(input_manager);
74 seat->input->server->xwayland.wlr_xwayland; 74 struct wlr_xwayland *xwayland =
75 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 75 seat->input->server->xwayland.wlr_xwayland;
76 seat_set_focus_surface(seat, xsurface->surface, false); 76 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
77 seat_set_focus_surface(seat, xsurface->surface, false);
78 }
77} 79}
78 80
79static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) { 81static void unmanaged_handle_unmap(struct wl_listener *listener, void *data) {
@@ -305,6 +307,8 @@ static void handle_destroy(struct wl_listener *listener, void *data) {
305 wl_list_remove(&xwayland_view->destroy.link); 307 wl_list_remove(&xwayland_view->destroy.link);
306 wl_list_remove(&xwayland_view->request_configure.link); 308 wl_list_remove(&xwayland_view->request_configure.link);
307 wl_list_remove(&xwayland_view->request_fullscreen.link); 309 wl_list_remove(&xwayland_view->request_fullscreen.link);
310 wl_list_remove(&xwayland_view->request_move.link);
311 wl_list_remove(&xwayland_view->request_resize.link);
308 wl_list_remove(&xwayland_view->set_title.link); 312 wl_list_remove(&xwayland_view->set_title.link);
309 wl_list_remove(&xwayland_view->set_class.link); 313 wl_list_remove(&xwayland_view->set_class.link);
310 wl_list_remove(&xwayland_view->set_window_type.link); 314 wl_list_remove(&xwayland_view->set_window_type.link);
@@ -355,7 +359,7 @@ static void handle_map(struct wl_listener *listener, void *data) {
355 view_map(view, xsurface->surface); 359 view_map(view, xsurface->surface);
356 360
357 if (xsurface->fullscreen) { 361 if (xsurface->fullscreen) {
358 view_set_fullscreen(view, true); 362 container_set_fullscreen(view->swayc, true);
359 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 363 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
360 arrange_windows(ws); 364 arrange_windows(ws);
361 } else { 365 } else {
@@ -393,13 +397,44 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
393 if (!xsurface->mapped) { 397 if (!xsurface->mapped) {
394 return; 398 return;
395 } 399 }
396 view_set_fullscreen(view, xsurface->fullscreen); 400 container_set_fullscreen(view->swayc, xsurface->fullscreen);
397 401
398 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 402 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
399 arrange_windows(output); 403 arrange_windows(output);
400 transaction_commit_dirty(); 404 transaction_commit_dirty();
401} 405}
402 406
407static void handle_request_move(struct wl_listener *listener, void *data) {
408 struct sway_xwayland_view *xwayland_view =
409 wl_container_of(listener, xwayland_view, request_move);
410 struct sway_view *view = &xwayland_view->view;
411 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
412 if (!xsurface->mapped) {
413 return;
414 }
415 if (!container_is_floating(view->swayc)) {
416 return;
417 }
418 struct sway_seat *seat = input_manager_current_seat(input_manager);
419 seat_begin_move(seat, view->swayc, seat->last_button);
420}
421
422static void handle_request_resize(struct wl_listener *listener, void *data) {
423 struct sway_xwayland_view *xwayland_view =
424 wl_container_of(listener, xwayland_view, request_resize);
425 struct sway_view *view = &xwayland_view->view;
426 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
427 if (!xsurface->mapped) {
428 return;
429 }
430 if (!container_is_floating(view->swayc)) {
431 return;
432 }
433 struct wlr_xwayland_resize_event *e = data;
434 struct sway_seat *seat = input_manager_current_seat(input_manager);
435 seat_begin_resize(seat, view->swayc, seat->last_button, e->edges);
436}
437
403static void handle_set_title(struct wl_listener *listener, void *data) { 438static void handle_set_title(struct wl_listener *listener, void *data) {
404 struct sway_xwayland_view *xwayland_view = 439 struct sway_xwayland_view *xwayland_view =
405 wl_container_of(listener, xwayland_view, set_title); 440 wl_container_of(listener, xwayland_view, set_title);
@@ -481,9 +516,6 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
481 view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl); 516 view_init(&xwayland_view->view, SWAY_VIEW_XWAYLAND, &view_impl);
482 xwayland_view->view.wlr_xwayland_surface = xsurface; 517 xwayland_view->view.wlr_xwayland_surface = xsurface;
483 518
484 // TODO:
485 // - Look up pid and open on appropriate workspace
486
487 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy); 519 wl_signal_add(&xsurface->events.destroy, &xwayland_view->destroy);
488 xwayland_view->destroy.notify = handle_destroy; 520 xwayland_view->destroy.notify = handle_destroy;
489 521
@@ -495,6 +527,14 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
495 &xwayland_view->request_fullscreen); 527 &xwayland_view->request_fullscreen);
496 xwayland_view->request_fullscreen.notify = handle_request_fullscreen; 528 xwayland_view->request_fullscreen.notify = handle_request_fullscreen;
497 529
530 wl_signal_add(&xsurface->events.request_move,
531 &xwayland_view->request_move);
532 xwayland_view->request_move.notify = handle_request_move;
533
534 wl_signal_add(&xsurface->events.request_resize,
535 &xwayland_view->request_resize);
536 xwayland_view->request_resize.notify = handle_request_resize;
537
498 wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); 538 wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title);
499 xwayland_view->set_title.notify = handle_set_title; 539 xwayland_view->set_title.notify = handle_set_title;
500 540
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index c76c20b3..c2fc4e9e 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -5,15 +5,20 @@
5#elif __FreeBSD__ 5#elif __FreeBSD__
6#include <dev/evdev/input-event-codes.h> 6#include <dev/evdev/input-event-codes.h>
7#endif 7#endif
8#include <limits.h>
8#include <wlr/types/wlr_cursor.h> 9#include <wlr/types/wlr_cursor.h>
9#include <wlr/types/wlr_xcursor_manager.h> 10#include <wlr/types/wlr_xcursor_manager.h>
10#include <wlr/types/wlr_idle.h> 11#include <wlr/types/wlr_idle.h>
11#include "list.h" 12#include "list.h"
12#include "log.h" 13#include "log.h"
14#include "config.h"
15#include "sway/desktop.h"
13#include "sway/desktop/transaction.h" 16#include "sway/desktop/transaction.h"
14#include "sway/input/cursor.h" 17#include "sway/input/cursor.h"
18#include "sway/input/keyboard.h"
15#include "sway/layers.h" 19#include "sway/layers.h"
16#include "sway/output.h" 20#include "sway/output.h"
21#include "sway/tree/arrange.h"
17#include "sway/tree/view.h" 22#include "sway/tree/view.h"
18#include "sway/tree/workspace.h" 23#include "sway/tree/workspace.h"
19#include "wlr-layer-shell-unstable-v1-protocol.h" 24#include "wlr-layer-shell-unstable-v1-protocol.h"
@@ -50,6 +55,7 @@ static struct sway_container *container_at_coords(
50 struct sway_seat *seat, double lx, double ly, 55 struct sway_seat *seat, double lx, double ly,
51 struct wlr_surface **surface, double *sx, double *sy) { 56 struct wlr_surface **surface, double *sx, double *sy) {
52 // check for unmanaged views first 57 // check for unmanaged views first
58#ifdef HAVE_XWAYLAND
53 struct wl_list *unmanaged = &root_container.sway_root->xwayland_unmanaged; 59 struct wl_list *unmanaged = &root_container.sway_root->xwayland_unmanaged;
54 struct sway_xwayland_unmanaged *unmanaged_surface; 60 struct sway_xwayland_unmanaged *unmanaged_surface;
55 wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) { 61 wl_list_for_each_reverse(unmanaged_surface, unmanaged, link) {
@@ -65,7 +71,7 @@ static struct sway_container *container_at_coords(
65 return NULL; 71 return NULL;
66 } 72 }
67 } 73 }
68 74#endif
69 // find the output the cursor is on 75 // find the output the cursor is on
70 struct wlr_output_layout *output_layout = 76 struct wlr_output_layout *output_layout =
71 root_container.sway_root->output_layout; 77 root_container.sway_root->output_layout;
@@ -93,14 +99,8 @@ static struct sway_container *container_at_coords(
93 return ws; 99 return ws;
94 } 100 }
95 if (ws->sway_workspace->fullscreen) { 101 if (ws->sway_workspace->fullscreen) {
96 struct wlr_surface *wlr_surface = ws->sway_workspace->fullscreen->surface; 102 return container_at_view(ws->sway_workspace->fullscreen, lx, ly,
97 if (wlr_surface_point_accepts_input(wlr_surface, ox, oy)) { 103 surface, sx, sy);
98 *sx = ox;
99 *sy = oy;
100 *surface = wlr_surface;
101 return ws->sway_workspace->fullscreen->swayc;
102 }
103 return NULL;
104 } 104 }
105 if ((*surface = layer_surface_at(output, 105 if ((*surface = layer_surface_at(output,
106 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], 106 &output->layers[ZWLR_LAYER_SHELL_V1_LAYER_TOP],
@@ -109,9 +109,6 @@ static struct sway_container *container_at_coords(
109 } 109 }
110 110
111 struct sway_container *c; 111 struct sway_container *c;
112 if ((c = floating_container_at(lx, ly, surface, sx, sy))) {
113 return c;
114 }
115 if ((c = container_at(ws, lx, ly, surface, sx, sy))) { 112 if ((c = container_at(ws, lx, ly, surface, sx, sy))) {
116 return c; 113 return c;
117 } 114 }
@@ -127,7 +124,7 @@ static struct sway_container *container_at_coords(
127 return ws; 124 return ws;
128 } 125 }
129 126
130 c = seat_get_focus_inactive(seat, output->swayc); 127 c = seat_get_active_child(seat, output->swayc);
131 if (c) { 128 if (c) {
132 return c; 129 return c;
133 } 130 }
@@ -139,6 +136,171 @@ static struct sway_container *container_at_coords(
139 return output->swayc; 136 return output->swayc;
140} 137}
141 138
139static enum wlr_edges find_resize_edge(struct sway_container *cont,
140 struct sway_cursor *cursor) {
141 if (cont->type != C_VIEW) {
142 return WLR_EDGE_NONE;
143 }
144 struct sway_view *view = cont->sway_view;
145 if (view->border == B_NONE || !view->border_thickness || view->using_csd) {
146 return WLR_EDGE_NONE;
147 }
148
149 enum wlr_edges edge = 0;
150 if (cursor->cursor->x < cont->x + view->border_thickness) {
151 edge |= WLR_EDGE_LEFT;
152 }
153 if (cursor->cursor->y < cont->y + view->border_thickness) {
154 edge |= WLR_EDGE_TOP;
155 }
156 if (cursor->cursor->x >= cont->x + cont->width - view->border_thickness) {
157 edge |= WLR_EDGE_RIGHT;
158 }
159 if (cursor->cursor->y >= cont->y + cont->height - view->border_thickness) {
160 edge |= WLR_EDGE_BOTTOM;
161 }
162 return edge;
163}
164
165static void handle_move_motion(struct sway_seat *seat,
166 struct sway_cursor *cursor) {
167 struct sway_container *con = seat->op_container;
168 desktop_damage_whole_container(con);
169 container_floating_translate(con,
170 cursor->cursor->x - cursor->previous.x,
171 cursor->cursor->y - cursor->previous.y);
172 desktop_damage_whole_container(con);
173}
174
175static void calculate_floating_constraints(struct sway_container *con,
176 int *min_width, int *max_width, int *min_height, int *max_height) {
177 if (config->floating_minimum_width == -1) { // no minimum
178 *min_width = 0;
179 } else if (config->floating_minimum_width == 0) { // automatic
180 *min_width = 75;
181 } else {
182 *min_width = config->floating_minimum_width;
183 }
184
185 if (config->floating_minimum_height == -1) { // no minimum
186 *min_height = 0;
187 } else if (config->floating_minimum_height == 0) { // automatic
188 *min_height = 50;
189 } else {
190 *min_height = config->floating_minimum_height;
191 }
192
193 if (config->floating_maximum_width == -1) { // no maximum
194 *max_width = INT_MAX;
195 } else if (config->floating_maximum_width == 0) { // automatic
196 struct sway_container *ws = container_parent(con, C_WORKSPACE);
197 *max_width = ws->width;
198 } else {
199 *max_width = config->floating_maximum_width;
200 }
201
202 if (config->floating_maximum_height == -1) { // no maximum
203 *max_height = INT_MAX;
204 } else if (config->floating_maximum_height == 0) { // automatic
205 struct sway_container *ws = container_parent(con, C_WORKSPACE);
206 *max_height = ws->height;
207 } else {
208 *max_height = config->floating_maximum_height;
209 }
210}
211
212static void handle_resize_motion(struct sway_seat *seat,
213 struct sway_cursor *cursor) {
214 struct sway_container *con = seat->op_container;
215 enum wlr_edges edge = seat->op_resize_edge;
216
217 // The amount the mouse has moved since the start of the resize operation
218 // Positive is down/right
219 double mouse_move_x = cursor->cursor->x - seat->op_ref_lx;
220 double mouse_move_y = cursor->cursor->y - seat->op_ref_ly;
221
222 if (edge == WLR_EDGE_TOP || edge == WLR_EDGE_BOTTOM) {
223 mouse_move_x = 0;
224 }
225 if (edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT) {
226 mouse_move_y = 0;
227 }
228
229 double grow_width = edge & WLR_EDGE_LEFT ? -mouse_move_x : mouse_move_x;
230 double grow_height = edge & WLR_EDGE_TOP ? -mouse_move_y : mouse_move_y;
231
232 if (seat->op_resize_preserve_ratio) {
233 double x_multiplier = grow_width / seat->op_ref_width;
234 double y_multiplier = grow_height / seat->op_ref_height;
235 double max_multiplier = fmax(x_multiplier, y_multiplier);
236 grow_width = seat->op_ref_width * max_multiplier;
237 grow_height = seat->op_ref_height * max_multiplier;
238 }
239
240 // Determine new width/height, and accommodate for floating min/max values
241 double width = seat->op_ref_width + grow_width;
242 double height = seat->op_ref_height + grow_height;
243 int min_width, max_width, min_height, max_height;
244 calculate_floating_constraints(con, &min_width, &max_width,
245 &min_height, &max_height);
246 width = fmax(min_width, fmin(width, max_width));
247 height = fmax(min_height, fmin(height, max_height));
248
249 // Apply the view's min/max size
250 if (con->type == C_VIEW) {
251 double view_min_width, view_max_width, view_min_height, view_max_height;
252 view_get_constraints(con->sway_view, &view_min_width, &view_max_width,
253 &view_min_height, &view_max_height);
254 width = fmax(view_min_width, fmin(width, view_max_width));
255 height = fmax(view_min_height, fmin(height, view_max_height));
256 }
257
258 // Recalculate these, in case we hit a min/max limit
259 grow_width = width - seat->op_ref_width;
260 grow_height = height - seat->op_ref_height;
261
262 // Determine grow x/y values - these are relative to the container's x/y at
263 // the start of the resize operation.
264 double grow_x = 0, grow_y = 0;
265 if (edge & WLR_EDGE_LEFT) {
266 grow_x = -grow_width;
267 } else if (edge & WLR_EDGE_RIGHT) {
268 grow_x = 0;
269 } else {
270 grow_x = -grow_width / 2;
271 }
272 if (edge & WLR_EDGE_TOP) {
273 grow_y = -grow_height;
274 } else if (edge & WLR_EDGE_BOTTOM) {
275 grow_y = 0;
276 } else {
277 grow_y = -grow_height / 2;
278 }
279
280 // Determine the amounts we need to bump everything relative to the current
281 // size.
282 int relative_grow_width = width - con->width;
283 int relative_grow_height = height - con->height;
284 int relative_grow_x = (seat->op_ref_con_lx + grow_x) - con->x;
285 int relative_grow_y = (seat->op_ref_con_ly + grow_y) - con->y;
286
287 // Actually resize stuff
288 con->x += relative_grow_x;
289 con->y += relative_grow_y;
290 con->width += relative_grow_width;
291 con->height += relative_grow_height;
292
293 if (con->type == C_VIEW) {
294 struct sway_view *view = con->sway_view;
295 view->x += relative_grow_x;
296 view->y += relative_grow_y;
297 view->width += relative_grow_width;
298 view->height += relative_grow_height;
299 }
300
301 arrange_windows(con);
302}
303
142void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec, 304void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
143 bool allow_refocusing) { 305 bool allow_refocusing) {
144 if (time_msec == 0) { 306 if (time_msec == 0) {
@@ -146,6 +308,18 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
146 } 308 }
147 309
148 struct sway_seat *seat = cursor->seat; 310 struct sway_seat *seat = cursor->seat;
311
312 if (seat->operation != OP_NONE) {
313 if (seat->operation == OP_MOVE) {
314 handle_move_motion(seat, cursor);
315 } else {
316 handle_resize_motion(seat, cursor);
317 }
318 cursor->previous.x = cursor->cursor->x;
319 cursor->previous.y = cursor->cursor->y;
320 return;
321 }
322
149 struct wlr_seat *wlr_seat = seat->wlr_seat; 323 struct wlr_seat *wlr_seat = seat->wlr_seat;
150 struct wlr_surface *surface = NULL; 324 struct wlr_surface *surface = NULL;
151 double sx, sy; 325 double sx, sy;
@@ -172,7 +346,7 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
172 output = container_parent(c, C_OUTPUT); 346 output = container_parent(c, C_OUTPUT);
173 } 347 }
174 if (output != focus) { 348 if (output != focus) {
175 seat_set_focus_warp(seat, c, false); 349 seat_set_focus_warp(seat, c, false, true);
176 } 350 }
177 } else if (c->type == C_VIEW) { 351 } else if (c->type == C_VIEW) {
178 // Focus c if the following are true: 352 // Focus c if the following are true:
@@ -182,27 +356,33 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, uint32_t time_msec,
182 if (!wlr_seat_keyboard_has_grab(cursor->seat->wlr_seat) && 356 if (!wlr_seat_keyboard_has_grab(cursor->seat->wlr_seat) &&
183 c != prev_c && 357 c != prev_c &&
184 view_is_visible(c->sway_view)) { 358 view_is_visible(c->sway_view)) {
185 seat_set_focus_warp(seat, c, false); 359 seat_set_focus_warp(seat, c, false, true);
186 } else { 360 } else {
187 struct sway_container *next_focus = 361 struct sway_container *next_focus =
188 seat_get_focus_inactive(seat, &root_container); 362 seat_get_focus_inactive(seat, &root_container);
189 if (next_focus && next_focus->type == C_VIEW && 363 if (next_focus && next_focus->type == C_VIEW &&
190 view_is_visible(next_focus->sway_view)) { 364 view_is_visible(next_focus->sway_view)) {
191 seat_set_focus_warp(seat, next_focus, false); 365 seat_set_focus_warp(seat, next_focus, false, true);
192 } 366 }
193 } 367 }
194 } 368 }
195 } 369 }
196 370
197 // reset cursor if switching between clients 371 // Handle cursor image
198 struct wl_client *client = NULL; 372 if (surface) {
199 if (surface != NULL) { 373 // Reset cursor if switching between clients
200 client = wl_resource_get_client(surface->resource); 374 struct wl_client *client = wl_resource_get_client(surface->resource);
201 } 375 if (client != cursor->image_client) {
202 if (client != cursor->image_client) { 376 cursor_set_image(cursor, "left_ptr", client);
203 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, 377 }
204 "left_ptr", cursor->cursor); 378 } else if (c && container_is_floating(c)) {
205 cursor->image_client = client; 379 // Try a floating container's resize edge
380 enum wlr_edges edge = find_resize_edge(c, cursor);
381 const char *image = edge == WLR_EDGE_NONE ?
382 "left_ptr" : wlr_xcursor_get_resize_name(edge);
383 cursor_set_image(cursor, image, NULL);
384 } else {
385 cursor_set_image(cursor, "left_ptr", NULL);
206 } 386 }
207 387
208 // send pointer enter/leave 388 // send pointer enter/leave
@@ -243,8 +423,142 @@ static void handle_cursor_motion_absolute(
243 transaction_commit_dirty(); 423 transaction_commit_dirty();
244} 424}
245 425
426static void dispatch_cursor_button_floating(struct sway_cursor *cursor,
427 uint32_t time_msec, uint32_t button, enum wlr_button_state state,
428 struct wlr_surface *surface, double sx, double sy,
429 struct sway_container *cont) {
430 struct sway_seat *seat = cursor->seat;
431
432 // Deny moving or resizing a fullscreen container
433 if (container_is_fullscreen_or_child(cont)) {
434 seat_pointer_notify_button(seat, time_msec, button, state);
435 return;
436 }
437 struct sway_container *floater = cont;
438 while (floater->parent->layout != L_FLOATING) {
439 floater = floater->parent;
440 }
441
442 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat);
443 bool mod_pressed = keyboard &&
444 (wlr_keyboard_get_modifiers(keyboard) & config->floating_mod);
445 enum wlr_edges edge = find_resize_edge(floater, cursor);
446 bool over_title = edge == WLR_EDGE_NONE && !surface;
447
448 // Check for beginning move
449 uint32_t btn_move = config->floating_mod_inverse ? BTN_RIGHT : BTN_LEFT;
450 if (button == btn_move && state == WLR_BUTTON_PRESSED &&
451 (mod_pressed || over_title)) {
452 seat_begin_move(seat, floater, button);
453 return;
454 }
455
456 // Check for beginning resize
457 bool resizing_via_border = button == BTN_LEFT && edge != WLR_EDGE_NONE;
458 uint32_t btn_resize = config->floating_mod_inverse ? BTN_LEFT : BTN_RIGHT;
459 bool resizing_via_mod = button == btn_resize && mod_pressed;
460 if ((resizing_via_border || resizing_via_mod) &&
461 state == WLR_BUTTON_PRESSED) {
462 if (edge == WLR_EDGE_NONE) {
463 edge |= cursor->cursor->x > floater->x + floater->width / 2 ?
464 WLR_EDGE_RIGHT : WLR_EDGE_LEFT;
465 edge |= cursor->cursor->y > floater->y + floater->height / 2 ?
466 WLR_EDGE_BOTTOM : WLR_EDGE_TOP;
467 }
468 seat_begin_resize(seat, floater, button, edge);
469 return;
470 }
471
472 // Send event to surface
473 seat_set_focus(seat, cont);
474 seat_pointer_notify_button(seat, time_msec, button, state);
475}
476
477/**
478 * Remove a button (and duplicates) to the sorted list of currently pressed buttons
479 */
480static void state_erase_button(struct sway_cursor *cursor, uint32_t button) {
481 size_t j = 0;
482 for (size_t i = 0; i < cursor->pressed_button_count; ++i) {
483 if (i > j) {
484 cursor->pressed_buttons[j] = cursor->pressed_buttons[i];
485 }
486 if (cursor->pressed_buttons[i] != button) {
487 ++j;
488 }
489 }
490 while (cursor->pressed_button_count > j) {
491 --cursor->pressed_button_count;
492 cursor->pressed_buttons[cursor->pressed_button_count] = 0;
493 }
494}
495
496/**
497 * Add a button to the sorted list of currently pressed buttons, if there
498 * is space.
499 */
500static void state_add_button(struct sway_cursor *cursor, uint32_t button) {
501 if (cursor->pressed_button_count >= SWAY_CURSOR_PRESSED_BUTTONS_CAP) {
502 return;
503 }
504 size_t i = 0;
505 while (i < cursor->pressed_button_count && cursor->pressed_buttons[i] < button) {
506 ++i;
507 }
508 size_t j = cursor->pressed_button_count;
509 while (j > i) {
510 cursor->pressed_buttons[j] = cursor->pressed_buttons[j - 1];
511 --j;
512 }
513 cursor->pressed_buttons[i] = button;
514 cursor->pressed_button_count++;
515}
516
517/**
518 * Return the mouse binding which matches modifier, click location, release,
519 * and pressed button state, otherwise return null.
520 */
521static struct sway_binding* get_active_mouse_binding(const struct sway_cursor *cursor,
522 list_t *bindings, uint32_t modifiers, bool release, bool on_titlebar,
523 bool on_border, bool on_content) {
524 uint32_t click_region = (on_titlebar ? BINDING_TITLEBAR : 0) |
525 (on_border ? BINDING_BORDER : 0) |
526 (on_content ? BINDING_CONTENTS : 0);
527
528 for (int i = 0; i < bindings->length; ++i) {
529 struct sway_binding *binding = bindings->items[i];
530 if (modifiers ^ binding->modifiers ||
531 cursor->pressed_button_count != (size_t)binding->keys->length ||
532 release != (binding->flags & BINDING_RELEASE) ||
533 !(click_region & binding->flags)) {
534 continue;
535 }
536
537 bool match = true;
538 for (size_t j = 0; j < cursor->pressed_button_count; j++) {
539 uint32_t key = *(uint32_t *)binding->keys->items[j];
540 if (key != cursor->pressed_buttons[j]) {
541 match = false;
542 break;
543 }
544 }
545 if (!match) {
546 continue;
547 }
548
549 return binding;
550 }
551 return NULL;
552}
553
246void dispatch_cursor_button(struct sway_cursor *cursor, 554void dispatch_cursor_button(struct sway_cursor *cursor,
247 uint32_t time_msec, uint32_t button, enum wlr_button_state state) { 555 uint32_t time_msec, uint32_t button, enum wlr_button_state state) {
556 if (cursor->seat->operation != OP_NONE &&
557 button == cursor->seat->op_button && state == WLR_BUTTON_RELEASED) {
558 seat_end_mouse_operation(cursor->seat);
559 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
560 return;
561 }
248 if (time_msec == 0) { 562 if (time_msec == 0) {
249 time_msec = get_current_time_msec(); 563 time_msec = get_current_time_msec();
250 } 564 }
@@ -253,12 +567,41 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
253 double sx, sy; 567 double sx, sy;
254 struct sway_container *cont = container_at_coords(cursor->seat, 568 struct sway_container *cont = container_at_coords(cursor->seat,
255 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); 569 cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy);
570
571 // Handle mouse bindings
572 bool on_border = cont && (find_resize_edge(cont, cursor) != WLR_EDGE_NONE);
573 bool on_contents = !on_border && surface;
574 bool on_titlebar = !on_border && !surface;
575 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(cursor->seat->wlr_seat);
576 uint32_t modifiers = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
577
578 struct sway_binding *binding = NULL;
579 if (state == WLR_BUTTON_PRESSED) {
580 state_add_button(cursor, button);
581 binding = get_active_mouse_binding(cursor,
582 config->current_mode->mouse_bindings, modifiers, false,
583 on_titlebar, on_border, on_contents);
584 } else {
585 binding = get_active_mouse_binding(cursor,
586 config->current_mode->mouse_bindings, modifiers, true,
587 on_titlebar, on_border, on_contents);
588 state_erase_button(cursor, button);
589 }
590 if (binding) {
591 seat_execute_command(cursor->seat, binding);
592 // TODO: do we want to pass on the event?
593 }
594
256 if (surface && wlr_surface_is_layer_surface(surface)) { 595 if (surface && wlr_surface_is_layer_surface(surface)) {
257 struct wlr_layer_surface *layer = 596 struct wlr_layer_surface *layer =
258 wlr_layer_surface_from_wlr_surface(surface); 597 wlr_layer_surface_from_wlr_surface(surface);
259 if (layer->current.keyboard_interactive) { 598 if (layer->current.keyboard_interactive) {
260 seat_set_focus_layer(cursor->seat, layer); 599 seat_set_focus_layer(cursor->seat, layer);
261 } 600 }
601 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
602 } else if (cont && container_is_floating_or_child(cont)) {
603 dispatch_cursor_button_floating(cursor, time_msec, button, state,
604 surface, sx, sy, cont);
262 } else if (surface && cont && cont->type != C_VIEW) { 605 } else if (surface && cont && cont->type != C_VIEW) {
263 // Avoid moving keyboard focus from a surface that accepts it to one 606 // Avoid moving keyboard focus from a surface that accepts it to one
264 // that does not unless the change would move us to a new workspace. 607 // that does not unless the change would move us to a new workspace.
@@ -275,12 +618,14 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
275 if (new_ws != old_ws) { 618 if (new_ws != old_ws) {
276 seat_set_focus(cursor->seat, cont); 619 seat_set_focus(cursor->seat, cont);
277 } 620 }
621 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
278 } else if (cont) { 622 } else if (cont) {
279 seat_set_focus(cursor->seat, cont); 623 seat_set_focus(cursor->seat, cont);
624 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
625 } else {
626 seat_pointer_notify_button(cursor->seat, time_msec, button, state);
280 } 627 }
281 628
282 wlr_seat_pointer_notify_button(cursor->seat->wlr_seat,
283 time_msec, button, state);
284 transaction_commit_dirty(); 629 transaction_commit_dirty();
285} 630}
286 631
@@ -467,6 +812,9 @@ static void handle_request_set_cursor(struct wl_listener *listener,
467 void *data) { 812 void *data) {
468 struct sway_cursor *cursor = 813 struct sway_cursor *cursor =
469 wl_container_of(listener, cursor, request_set_cursor); 814 wl_container_of(listener, cursor, request_set_cursor);
815 if (cursor->seat->operation != OP_NONE) {
816 return;
817 }
470 struct wlr_seat_pointer_request_set_cursor_event *event = data; 818 struct wlr_seat_pointer_request_set_cursor_event *event = data;
471 819
472 struct wl_client *focused_client = NULL; 820 struct wl_client *focused_client = NULL;
@@ -485,9 +833,20 @@ static void handle_request_set_cursor(struct wl_listener *listener,
485 833
486 wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x, 834 wlr_cursor_set_surface(cursor->cursor, event->surface, event->hotspot_x,
487 event->hotspot_y); 835 event->hotspot_y);
836 cursor->image = NULL;
488 cursor->image_client = focused_client; 837 cursor->image_client = focused_client;
489} 838}
490 839
840void cursor_set_image(struct sway_cursor *cursor, const char *image,
841 struct wl_client *client) {
842 if (!cursor->image || strcmp(cursor->image, image) != 0) {
843 wlr_xcursor_manager_set_cursor_image(cursor->xcursor_manager, image,
844 cursor->cursor);
845 cursor->image = image;
846 }
847 cursor->image_client = client;
848}
849
491void sway_cursor_destroy(struct sway_cursor *cursor) { 850void sway_cursor_destroy(struct sway_cursor *cursor) {
492 if (!cursor) { 851 if (!cursor) {
493 return; 852 return;
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index 0b7cb766..c820e032 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -8,6 +8,7 @@
8#include <math.h> 8#include <math.h>
9#include <wlr/backend/libinput.h> 9#include <wlr/backend/libinput.h>
10#include <wlr/types/wlr_input_inhibitor.h> 10#include <wlr/types/wlr_input_inhibitor.h>
11#include <wlr/types/wlr_virtual_keyboard_v1.h>
11#include "sway/config.h" 12#include "sway/config.h"
12#include "sway/input/input-manager.h" 13#include "sway/input/input-manager.h"
13#include "sway/input/seat.h" 14#include "sway/input/seat.h"
@@ -303,6 +304,35 @@ static void handle_inhibit_deactivate(struct wl_listener *listener, void *data)
303 } 304 }
304} 305}
305 306
307void handle_virtual_keyboard(struct wl_listener *listener, void *data) {
308 struct sway_input_manager *input_manager =
309 wl_container_of(listener, input_manager, virtual_keyboard_new);
310 struct wlr_virtual_keyboard_v1 *keyboard = data;
311 struct wlr_input_device *device = &keyboard->input_device;
312
313 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
314
315 // TODO: The user might want this on a different seat
316 struct sway_input_device *input_device =
317 calloc(1, sizeof(struct sway_input_device));
318 if (!sway_assert(input_device, "could not allocate input device")) {
319 return;
320 }
321 device->data = input_device;
322
323 input_device->wlr_device = device;
324 input_device->identifier = get_device_identifier(device);
325 wl_list_insert(&input_manager->devices, &input_device->link);
326
327 wlr_log(WLR_DEBUG, "adding virtual keyboard: '%s'",
328 input_device->identifier);
329
330 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
331 input_device->device_destroy.notify = handle_device_destroy;
332
333 seat_add_device(seat, input_device);
334}
335
306struct sway_input_manager *input_manager_create( 336struct sway_input_manager *input_manager_create(
307 struct sway_server *server) { 337 struct sway_server *server) {
308 struct sway_input_manager *input = 338 struct sway_input_manager *input =
@@ -321,6 +351,12 @@ struct sway_input_manager *input_manager_create(
321 input->new_input.notify = handle_new_input; 351 input->new_input.notify = handle_new_input;
322 wl_signal_add(&server->backend->events.new_input, &input->new_input); 352 wl_signal_add(&server->backend->events.new_input, &input->new_input);
323 353
354 input->virtual_keyboard = wlr_virtual_keyboard_manager_v1_create(
355 server->wl_display);
356 wl_signal_add(&input->virtual_keyboard->events.new_virtual_keyboard,
357 &input->virtual_keyboard_new);
358 input->virtual_keyboard_new.notify = handle_virtual_keyboard;
359
324 input->inhibit = wlr_input_inhibit_manager_create(server->wl_display); 360 input->inhibit = wlr_input_inhibit_manager_create(server->wl_display);
325 input->inhibit_activate.notify = handle_inhibit_activate; 361 input->inhibit_activate.notify = handle_inhibit_activate;
326 wl_signal_add(&input->inhibit->events.activate, 362 wl_signal_add(&input->inhibit->events.activate,
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c
index ede38519..160ef10b 100644
--- a/sway/input/keyboard.c
+++ b/sway/input/keyboard.c
@@ -3,11 +3,12 @@
3#include <wlr/backend/multi.h> 3#include <wlr/backend/multi.h>
4#include <wlr/backend/session.h> 4#include <wlr/backend/session.h>
5#include <wlr/types/wlr_idle.h> 5#include <wlr/types/wlr_idle.h>
6#include <wlr/interfaces/wlr_keyboard.h>
7#include "sway/commands.h"
6#include "sway/desktop/transaction.h" 8#include "sway/desktop/transaction.h"
7#include "sway/input/seat.h"
8#include "sway/input/keyboard.h"
9#include "sway/input/input-manager.h" 9#include "sway/input/input-manager.h"
10#include "sway/commands.h" 10#include "sway/input/keyboard.h"
11#include "sway/input/seat.h"
11#include "log.h" 12#include "log.h"
12 13
13/** 14/**
@@ -88,11 +89,13 @@ static void get_active_binding(const struct sway_shortcut_state *state,
88 uint32_t modifiers, bool release, bool locked) { 89 uint32_t modifiers, bool release, bool locked) {
89 for (int i = 0; i < bindings->length; ++i) { 90 for (int i = 0; i < bindings->length; ++i) {
90 struct sway_binding *binding = bindings->items[i]; 91 struct sway_binding *binding = bindings->items[i];
92 bool binding_locked = binding->flags & BINDING_LOCKED;
93 bool binding_release = binding->flags & BINDING_RELEASE;
91 94
92 if (modifiers ^ binding->modifiers || 95 if (modifiers ^ binding->modifiers ||
93 state->npressed != (size_t)binding->keys->length || 96 state->npressed != (size_t)binding->keys->length ||
94 locked > binding->locked || 97 release != binding_release ||
95 release != binding->release) { 98 locked > binding_locked) {
96 continue; 99 continue;
97 } 100 }
98 101
@@ -119,23 +122,6 @@ static void get_active_binding(const struct sway_shortcut_state *state,
119} 122}
120 123
121/** 124/**
122 * Execute the command associated to a binding
123 */
124static void keyboard_execute_command(struct sway_keyboard *keyboard,
125 struct sway_binding *binding) {
126 wlr_log(WLR_DEBUG, "running command for binding: %s",
127 binding->command);
128 config->handler_context.seat = keyboard->seat_device->sway_seat;
129 struct cmd_results *results = execute_command(binding->command, NULL);
130 transaction_commit_dirty();
131 if (results->status != CMD_SUCCESS) {
132 wlr_log(WLR_DEBUG, "could not run command for binding: %s (%s)",
133 binding->command, results->error);
134 }
135 free_cmd_results(results);
136}
137
138/**
139 * Execute a built-in, hardcoded compositor binding. These are triggered from a 125 * Execute a built-in, hardcoded compositor binding. These are triggered from a
140 * single keysym. 126 * single keysym.
141 * 127 *
@@ -211,12 +197,13 @@ static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard,
211static void handle_keyboard_key(struct wl_listener *listener, void *data) { 197static void handle_keyboard_key(struct wl_listener *listener, void *data) {
212 struct sway_keyboard *keyboard = 198 struct sway_keyboard *keyboard =
213 wl_container_of(listener, keyboard, keyboard_key); 199 wl_container_of(listener, keyboard, keyboard_key);
214 struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; 200 struct sway_seat* seat = keyboard->seat_device->sway_seat;
201 struct wlr_seat *wlr_seat = seat->wlr_seat;
215 struct wlr_input_device *wlr_device = 202 struct wlr_input_device *wlr_device =
216 keyboard->seat_device->input_device->wlr_device; 203 keyboard->seat_device->input_device->wlr_device;
217 wlr_idle_notify_activity(keyboard->seat_device->sway_seat->input->server->idle, wlr_seat); 204 wlr_idle_notify_activity(seat->input->server->idle, wlr_seat);
218 struct wlr_event_keyboard_key *event = data; 205 struct wlr_event_keyboard_key *event = data;
219 bool input_inhibited = keyboard->seat_device->sway_seat->exclusive_client != NULL; 206 bool input_inhibited = seat->exclusive_client != NULL;
220 207
221 // Identify new keycode, raw keysym(s), and translated keysym(s) 208 // Identify new keycode, raw keysym(s), and translated keysym(s)
222 xkb_keycode_t keycode = event->keycode + 8; 209 xkb_keycode_t keycode = event->keycode + 8;
@@ -266,7 +253,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
266 // Execute stored release binding once no longer active 253 // Execute stored release binding once no longer active
267 if (keyboard->held_binding && binding_released != keyboard->held_binding && 254 if (keyboard->held_binding && binding_released != keyboard->held_binding &&
268 event->state == WLR_KEY_RELEASED) { 255 event->state == WLR_KEY_RELEASED) {
269 keyboard_execute_command(keyboard, keyboard->held_binding); 256 seat_execute_command(seat, keyboard->held_binding);
270 handled = true; 257 handled = true;
271 } 258 }
272 if (binding_released != keyboard->held_binding) { 259 if (binding_released != keyboard->held_binding) {
@@ -277,6 +264,7 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
277 } 264 }
278 265
279 // Identify and execute active pressed binding 266 // Identify and execute active pressed binding
267 struct sway_binding *next_repeat_binding = NULL;
280 if (event->state == WLR_KEY_PRESSED) { 268 if (event->state == WLR_KEY_PRESSED) {
281 struct sway_binding *binding_pressed = NULL; 269 struct sway_binding *binding_pressed = NULL;
282 get_active_binding(&keyboard->state_keycodes, 270 get_active_binding(&keyboard->state_keycodes,
@@ -290,8 +278,23 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
290 raw_modifiers, false, input_inhibited); 278 raw_modifiers, false, input_inhibited);
291 279
292 if (binding_pressed) { 280 if (binding_pressed) {
293 keyboard_execute_command(keyboard, binding_pressed); 281 seat_execute_command(seat, binding_pressed);
294 handled = true; 282 handled = true;
283 next_repeat_binding = binding_pressed;
284 }
285 }
286
287 // Set up (or clear) keyboard repeat for a pressed binding
288 if (next_repeat_binding && wlr_device->keyboard->repeat_info.delay > 0) {
289 keyboard->repeat_binding = next_repeat_binding;
290 if (wl_event_source_timer_update(keyboard->key_repeat_source,
291 wlr_device->keyboard->repeat_info.delay) < 0) {
292 wlr_log(WLR_DEBUG, "failed to set key repeat timer");
293 }
294 } else if (keyboard->repeat_binding) {
295 keyboard->repeat_binding = NULL;
296 if (wl_event_source_timer_update(keyboard->key_repeat_source, 0) < 0) {
297 wlr_log(WLR_DEBUG, "failed to disarm key repeat timer");
295 } 298 }
296 } 299 }
297 300
@@ -312,6 +315,28 @@ static void handle_keyboard_key(struct wl_listener *listener, void *data) {
312 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, 315 wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec,
313 event->keycode, event->state); 316 event->keycode, event->state);
314 } 317 }
318
319 transaction_commit_dirty();
320}
321
322static int handle_keyboard_repeat(void *data) {
323 struct sway_keyboard *keyboard = (struct sway_keyboard *)data;
324 struct wlr_keyboard *wlr_device =
325 keyboard->seat_device->input_device->wlr_device->keyboard;
326 if (keyboard->repeat_binding) {
327 if (wlr_device->repeat_info.rate > 0) {
328 // We queue the next event first, as the command might cancel it
329 if (wl_event_source_timer_update(keyboard->key_repeat_source,
330 1000 / wlr_device->repeat_info.rate) < 0) {
331 wlr_log(WLR_DEBUG, "failed to update key repeat timer");
332 }
333 }
334
335 seat_execute_command(keyboard->seat_device->sway_seat,
336 keyboard->repeat_binding);
337 transaction_commit_dirty();
338 }
339 return 0;
315} 340}
316 341
317static void handle_keyboard_modifiers(struct wl_listener *listener, 342static void handle_keyboard_modifiers(struct wl_listener *listener,
@@ -339,6 +364,9 @@ struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat,
339 wl_list_init(&keyboard->keyboard_key.link); 364 wl_list_init(&keyboard->keyboard_key.link);
340 wl_list_init(&keyboard->keyboard_modifiers.link); 365 wl_list_init(&keyboard->keyboard_modifiers.link);
341 366
367 keyboard->key_repeat_source = wl_event_loop_add_timer(server.wl_event_loop,
368 handle_keyboard_repeat, keyboard);
369
342 return keyboard; 370 return keyboard;
343} 371}
344 372
@@ -397,6 +425,31 @@ void sway_keyboard_configure(struct sway_keyboard *keyboard) {
397 keyboard->keymap = keymap; 425 keyboard->keymap = keymap;
398 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); 426 wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap);
399 427
428 xkb_mod_mask_t locked_mods = 0;
429 if (input_config && input_config->xkb_numlock > 0) {
430 xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_NUM);
431 if (mod_index != XKB_MOD_INVALID) {
432 locked_mods |= (uint32_t)1 << mod_index;
433 }
434 }
435 if (input_config && input_config->xkb_capslock > 0) {
436 xkb_mod_index_t mod_index = xkb_map_mod_get_index(keymap, XKB_MOD_NAME_CAPS);
437 if (mod_index != XKB_MOD_INVALID) {
438 locked_mods |= (uint32_t)1 << mod_index;
439 }
440 }
441 if (locked_mods) {
442 wlr_keyboard_notify_modifiers(wlr_device->keyboard, 0, 0, locked_mods, 0);
443 uint32_t leds = 0;
444 for (uint32_t i = 0; i < WLR_LED_COUNT; ++i) {
445 if (xkb_state_led_index_is_active(wlr_device->keyboard->xkb_state,
446 wlr_device->keyboard->led_indexes[i])) {
447 leds |= (1 << i);
448 }
449 }
450 wlr_keyboard_led_update(wlr_device->keyboard, leds);
451 }
452
400 if (input_config && input_config->repeat_delay != INT_MIN 453 if (input_config && input_config->repeat_delay != INT_MIN
401 && input_config->repeat_rate != INT_MIN) { 454 && input_config->repeat_rate != INT_MIN) {
402 wlr_keyboard_set_repeat_info(wlr_device->keyboard, 455 wlr_keyboard_set_repeat_info(wlr_device->keyboard,
@@ -427,5 +480,6 @@ void sway_keyboard_destroy(struct sway_keyboard *keyboard) {
427 } 480 }
428 wl_list_remove(&keyboard->keyboard_key.link); 481 wl_list_remove(&keyboard->keyboard_key.link);
429 wl_list_remove(&keyboard->keyboard_modifiers.link); 482 wl_list_remove(&keyboard->keyboard_modifiers.link);
483 wl_event_source_remove(keyboard->key_repeat_source);
430 free(keyboard); 484 free(keyboard);
431} 485}
diff --git a/sway/input/seat.c b/sway/input/seat.c
index e77d88a8..dd4d5c3b 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -1,12 +1,19 @@
1#define _XOPEN_SOURCE 700 1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 199309L 2#define _POSIX_C_SOURCE 199309L
3#include <assert.h> 3#include <assert.h>
4#include <errno.h>
5#ifdef __linux__
6#include <linux/input-event-codes.h>
7#elif __FreeBSD__
8#include <dev/evdev/input-event-codes.h>
9#endif
4#include <strings.h> 10#include <strings.h>
5#include <time.h> 11#include <time.h>
6#include <wlr/types/wlr_cursor.h> 12#include <wlr/types/wlr_cursor.h>
7#include <wlr/types/wlr_output_layout.h> 13#include <wlr/types/wlr_output_layout.h>
8#include <wlr/types/wlr_xcursor_manager.h> 14#include <wlr/types/wlr_xcursor_manager.h>
9#include "log.h" 15#include "log.h"
16#include "config.h"
10#include "sway/debug.h" 17#include "sway/debug.h"
11#include "sway/desktop.h" 18#include "sway/desktop.h"
12#include "sway/input/cursor.h" 19#include "sway/input/cursor.h"
@@ -98,11 +105,13 @@ static void seat_send_focus(struct sway_container *con,
98 105
99 if (con->type == C_VIEW 106 if (con->type == C_VIEW
100 && seat_is_input_allowed(seat, con->sway_view->surface)) { 107 && seat_is_input_allowed(seat, con->sway_view->surface)) {
108#ifdef HAVE_XWAYLAND
101 if (con->sway_view->type == SWAY_VIEW_XWAYLAND) { 109 if (con->sway_view->type == SWAY_VIEW_XWAYLAND) {
102 struct wlr_xwayland *xwayland = 110 struct wlr_xwayland *xwayland =
103 seat->input->server->xwayland.wlr_xwayland; 111 seat->input->server->xwayland.wlr_xwayland;
104 wlr_xwayland_set_seat(xwayland, seat->wlr_seat); 112 wlr_xwayland_set_seat(xwayland, seat->wlr_seat);
105 } 113 }
114#endif
106 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat); 115 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat);
107 if (keyboard) { 116 if (keyboard) {
108 wlr_seat_keyboard_notify_enter(seat->wlr_seat, 117 wlr_seat_keyboard_notify_enter(seat->wlr_seat,
@@ -116,12 +125,14 @@ static void seat_send_focus(struct sway_container *con,
116} 125}
117 126
118static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat, 127static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat,
119 struct sway_container *container, enum sway_container_type type) { 128 struct sway_container *container, enum sway_container_type type,
129 bool only_tiling) {
120 if (container->type == C_VIEW) { 130 if (container->type == C_VIEW) {
121 return container; 131 return container;
122 } 132 }
123 133
124 struct sway_container *floating = container->type == C_WORKSPACE ? 134 struct sway_container *floating =
135 container->type == C_WORKSPACE && !only_tiling ?
125 container->sway_workspace->floating : NULL; 136 container->sway_workspace->floating : NULL;
126 if (container->children->length == 0 && 137 if (container->children->length == 0 &&
127 (!floating || floating->children->length == 0)) { 138 (!floating || floating->children->length == 0)) {
@@ -135,6 +146,10 @@ static struct sway_container *seat_get_focus_by_type(struct sway_seat *seat,
135 } 146 }
136 147
137 if (container_has_child(container, current->container)) { 148 if (container_has_child(container, current->container)) {
149 if (only_tiling &&
150 container_is_floating_or_child(current->container)) {
151 continue;
152 }
138 return current->container; 153 return current->container;
139 } 154 }
140 if (floating && container_has_child(floating, current->container)) { 155 if (floating && container_has_child(floating, current->container)) {
@@ -161,7 +176,7 @@ void seat_focus_inactive_children_for_each(struct sway_seat *seat,
161 176
162struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat, 177struct sway_container *seat_get_focus_inactive_view(struct sway_seat *seat,
163 struct sway_container *container) { 178 struct sway_container *container) {
164 return seat_get_focus_by_type(seat, container, C_VIEW); 179 return seat_get_focus_by_type(seat, container, C_VIEW, false);
165} 180}
166 181
167static void handle_seat_container_destroy(struct wl_listener *listener, 182static void handle_seat_container_destroy(struct wl_listener *listener,
@@ -183,7 +198,7 @@ static void handle_seat_container_destroy(struct wl_listener *listener,
183 if (set_focus) { 198 if (set_focus) {
184 struct sway_container *next_focus = NULL; 199 struct sway_container *next_focus = NULL;
185 while (next_focus == NULL) { 200 while (next_focus == NULL) {
186 next_focus = seat_get_focus_by_type(seat, parent, C_VIEW); 201 next_focus = seat_get_focus_by_type(seat, parent, C_VIEW, false);
187 202
188 if (next_focus == NULL && parent->type == C_WORKSPACE) { 203 if (next_focus == NULL && parent->type == C_WORKSPACE) {
189 next_focus = parent; 204 next_focus = parent;
@@ -348,6 +363,7 @@ struct sway_seat *seat_create(struct sway_input_manager *input,
348 free(seat); 363 free(seat);
349 return NULL; 364 return NULL;
350 } 365 }
366 seat->wlr_seat->data = seat;
351 367
352 seat->cursor = sway_cursor_create(seat); 368 seat->cursor = sway_cursor_create(seat);
353 if (!seat->cursor) { 369 if (!seat->cursor) {
@@ -377,7 +393,6 @@ struct sway_seat *seat_create(struct sway_input_manager *input,
377 WL_SEAT_CAPABILITY_POINTER | 393 WL_SEAT_CAPABILITY_POINTER |
378 WL_SEAT_CAPABILITY_TOUCH); 394 WL_SEAT_CAPABILITY_TOUCH);
379 395
380 seat_configure_xcursor(seat);
381 396
382 wl_list_insert(&input->seats, &seat->link); 397 wl_list_insert(&input->seats, &seat->link);
383 398
@@ -422,6 +437,7 @@ static void seat_apply_input_config(struct sway_seat *seat,
422 437
423static void seat_configure_pointer(struct sway_seat *seat, 438static void seat_configure_pointer(struct sway_seat *seat,
424 struct sway_seat_device *sway_device) { 439 struct sway_seat_device *sway_device) {
440 seat_configure_xcursor(seat);
425 wlr_cursor_attach_input_device(seat->cursor->cursor, 441 wlr_cursor_attach_input_device(seat->cursor->cursor,
426 sway_device->input_device->wlr_device); 442 sway_device->input_device->wlr_device);
427 seat_apply_input_config(seat, sway_device); 443 seat_apply_input_config(seat, sway_device);
@@ -601,7 +617,7 @@ static int handle_urgent_timeout(void *data) {
601} 617}
602 618
603void seat_set_focus_warp(struct sway_seat *seat, 619void seat_set_focus_warp(struct sway_seat *seat,
604 struct sway_container *container, bool warp) { 620 struct sway_container *container, bool warp, bool notify) {
605 if (seat->focused_layer) { 621 if (seat->focused_layer) {
606 return; 622 return;
607 } 623 }
@@ -622,7 +638,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
622 638
623 if (last_workspace && last_workspace == new_workspace 639 if (last_workspace && last_workspace == new_workspace
624 && last_workspace->sway_workspace->fullscreen 640 && last_workspace->sway_workspace->fullscreen
625 && !container->sway_view->is_fullscreen) { 641 && container && !container_is_fullscreen_or_child(container)) {
626 return; 642 return;
627 } 643 }
628 644
@@ -639,7 +655,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
639 struct sway_container *new_output_last_ws = NULL; 655 struct sway_container *new_output_last_ws = NULL;
640 if (last_output && new_output && last_output != new_output) { 656 if (last_output && new_output && last_output != new_output) {
641 new_output_last_ws = 657 new_output_last_ws =
642 seat_get_focus_by_type(seat, new_output, C_WORKSPACE); 658 seat_get_focus_by_type(seat, new_output, C_WORKSPACE, false);
643 } 659 }
644 660
645 if (container && container->parent) { 661 if (container && container->parent) {
@@ -686,8 +702,14 @@ void seat_set_focus_warp(struct sway_seat *seat,
686 config->urgent_timeout > 0) { 702 config->urgent_timeout > 0) {
687 view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop, 703 view->urgent_timer = wl_event_loop_add_timer(server.wl_event_loop,
688 handle_urgent_timeout, view); 704 handle_urgent_timeout, view);
689 wl_event_source_timer_update(view->urgent_timer, 705 if (view->urgent_timer) {
690 config->urgent_timeout); 706 wl_event_source_timer_update(view->urgent_timer,
707 config->urgent_timeout);
708 } else {
709 wlr_log(WLR_ERROR, "Unable to create urgency timer (%s)",
710 strerror(errno));
711 handle_urgent_timeout(view);
712 }
691 } else { 713 } else {
692 view_set_urgent(view, false); 714 view_set_urgent(view, false);
693 } 715 }
@@ -715,9 +737,18 @@ void seat_set_focus_warp(struct sway_seat *seat,
715 } 737 }
716 } 738 }
717 739
740 // Close any popups on the old focus
741 if (last_focus && last_focus != container) {
742 if (last_focus->type == C_VIEW) {
743 view_close_popups(last_focus->sway_view);
744 }
745 }
746
718 if (last_focus) { 747 if (last_focus) {
719 if (last_workspace) { 748 if (last_workspace) {
720 ipc_event_workspace(last_workspace, container, "focus"); 749 if (notify && last_workspace != new_workspace) {
750 ipc_event_workspace(last_workspace, new_workspace, "focus");
751 }
721 if (!workspace_is_visible(last_workspace) 752 if (!workspace_is_visible(last_workspace)
722 && workspace_is_empty(last_workspace)) { 753 && workspace_is_empty(last_workspace)) {
723 if (last_workspace == last_focus) { 754 if (last_workspace == last_focus) {
@@ -744,8 +775,12 @@ void seat_set_focus_warp(struct sway_seat *seat,
744 } 775 }
745 } 776 }
746 777
747 if (last_focus != NULL) { 778 if (container) {
748 cursor_send_pointer_motion(seat->cursor, 0, true); 779 if (container->type == C_VIEW) {
780 ipc_event_window(container, "focus");
781 } else if (container->type == C_WORKSPACE) {
782 ipc_event_workspace(NULL, container, "focus");
783 }
749 } 784 }
750 785
751 seat->has_focus = (container != NULL); 786 seat->has_focus = (container != NULL);
@@ -755,7 +790,7 @@ void seat_set_focus_warp(struct sway_seat *seat,
755 790
756void seat_set_focus(struct sway_seat *seat, 791void seat_set_focus(struct sway_seat *seat,
757 struct sway_container *container) { 792 struct sway_container *container) {
758 seat_set_focus_warp(seat, container, true); 793 seat_set_focus_warp(seat, container, true, true);
759} 794}
760 795
761void seat_set_focus_surface(struct sway_seat *seat, 796void seat_set_focus_surface(struct sway_seat *seat,
@@ -848,7 +883,12 @@ void seat_set_exclusive_client(struct sway_seat *seat,
848 883
849struct sway_container *seat_get_focus_inactive(struct sway_seat *seat, 884struct sway_container *seat_get_focus_inactive(struct sway_seat *seat,
850 struct sway_container *container) { 885 struct sway_container *container) {
851 return seat_get_focus_by_type(seat, container, C_TYPES); 886 return seat_get_focus_by_type(seat, container, C_TYPES, false);
887}
888
889struct sway_container *seat_get_focus_inactive_tiling(struct sway_seat *seat,
890 struct sway_container *container) {
891 return seat_get_focus_by_type(seat, container, C_TYPES, true);
852} 892}
853 893
854struct sway_container *seat_get_active_child(struct sway_seat *seat, 894struct sway_container *seat_get_active_child(struct sway_seat *seat,
@@ -894,3 +934,68 @@ struct seat_config *seat_get_config(struct sway_seat *seat) {
894 934
895 return NULL; 935 return NULL;
896} 936}
937
938void seat_begin_move(struct sway_seat *seat, struct sway_container *con,
939 uint32_t button) {
940 if (!seat->cursor) {
941 wlr_log(WLR_DEBUG, "Ignoring move request due to no cursor device");
942 return;
943 }
944 seat->operation = OP_MOVE;
945 seat->op_container = con;
946 seat->op_button = button;
947 cursor_set_image(seat->cursor, "grab", NULL);
948}
949
950void seat_begin_resize(struct sway_seat *seat, struct sway_container *con,
951 uint32_t button, enum wlr_edges edge) {
952 if (!seat->cursor) {
953 wlr_log(WLR_DEBUG, "Ignoring resize request due to no cursor device");
954 return;
955 }
956 struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat->wlr_seat);
957 seat->operation = OP_RESIZE;
958 seat->op_container = con;
959 seat->op_resize_preserve_ratio = keyboard &&
960 (wlr_keyboard_get_modifiers(keyboard) & WLR_MODIFIER_SHIFT);
961 seat->op_resize_edge = edge == WLR_EDGE_NONE ?
962 RESIZE_EDGE_BOTTOM | RESIZE_EDGE_RIGHT : edge;
963 seat->op_button = button;
964 seat->op_ref_lx = seat->cursor->cursor->x;
965 seat->op_ref_ly = seat->cursor->cursor->y;
966 seat->op_ref_con_lx = con->x;
967 seat->op_ref_con_ly = con->y;
968 seat->op_ref_width = con->width;
969 seat->op_ref_height = con->height;
970
971 const char *image = edge == WLR_EDGE_NONE ?
972 "se-resize" : wlr_xcursor_get_resize_name(edge);
973 cursor_set_image(seat->cursor, image, NULL);
974}
975
976void seat_end_mouse_operation(struct sway_seat *seat) {
977 switch (seat->operation) {
978 case OP_MOVE:
979 {
980 // We "move" the container to its own location so it discovers its
981 // output again.
982 struct sway_container *con = seat->op_container;
983 container_floating_move_to(con, con->x, con->y);
984 }
985 case OP_RESIZE:
986 // Don't need to do anything here.
987 break;
988 case OP_NONE:
989 break;
990 }
991 seat->operation = OP_NONE;
992 seat->op_container = NULL;
993 cursor_set_image(seat->cursor, "left_ptr", NULL);
994}
995
996void seat_pointer_notify_button(struct sway_seat *seat, uint32_t time_msec,
997 uint32_t button, enum wlr_button_state state) {
998 seat->last_button = button;
999 seat->last_button_serial = wlr_seat_pointer_notify_button(seat->wlr_seat,
1000 time_msec, button, state);
1001}
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index c49ea47e..4c2bcc98 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -201,6 +201,15 @@ static void ipc_json_describe_view(struct sway_container *c, json_object *object
201 bool urgent = c->type == C_VIEW ? 201 bool urgent = c->type == C_VIEW ?
202 view_is_urgent(c->sway_view) : container_has_urgent_child(c); 202 view_is_urgent(c->sway_view) : container_has_urgent_child(c);
203 json_object_object_add(object, "urgent", json_object_new_boolean(urgent)); 203 json_object_object_add(object, "urgent", json_object_new_boolean(urgent));
204
205 if (c->type == C_VIEW) {
206 json_object *marks = json_object_new_array();
207 list_t *view_marks = c->sway_view->marks;
208 for (int i = 0; i < view_marks->length; ++i) {
209 json_object_array_add(marks, json_object_new_string(view_marks->items[i]));
210 }
211 json_object_object_add(object, "marks", marks);
212 }
204} 213}
205 214
206static void focus_inactive_children_iterator(struct sway_container *c, void *data) { 215static void focus_inactive_children_iterator(struct sway_container *c, void *data) {
diff --git a/sway/ipc-server.c b/sway/ipc-server.c
index be703915..7d2d8969 100644
--- a/sway/ipc-server.c
+++ b/sway/ipc-server.c
@@ -3,12 +3,18 @@
3// Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0) 3// Any value will hide SOCK_CLOEXEC on FreeBSD (__BSD_VISIBLE=0)
4#define _XOPEN_SOURCE 700 4#define _XOPEN_SOURCE 700
5#endif 5#endif
6#ifdef __linux__
7#include <linux/input-event-codes.h>
8#elif __FreeBSD__
9#include <dev/evdev/input-event-codes.h>
10#endif
6#include <assert.h> 11#include <assert.h>
7#include <errno.h> 12#include <errno.h>
8#include <fcntl.h> 13#include <fcntl.h>
9#include <json-c/json.h> 14#include <json-c/json.h>
10#include <stdbool.h> 15#include <stdbool.h>
11#include <stdint.h> 16#include <stdint.h>
17#include <stdio.h>
12#include <stdlib.h> 18#include <stdlib.h>
13#include <string.h> 19#include <string.h>
14#include <sys/socket.h> 20#include <sys/socket.h>
@@ -28,6 +34,7 @@
28#include "sway/tree/view.h" 34#include "sway/tree/view.h"
29#include "list.h" 35#include "list.h"
30#include "log.h" 36#include "log.h"
37#include "util.h"
31 38
32static int ipc_socket = -1; 39static int ipc_socket = -1;
33static struct wl_event_source *ipc_event_source = NULL; 40static struct wl_event_source *ipc_event_source = NULL;
@@ -291,13 +298,11 @@ void ipc_event_workspace(struct sway_container *old,
291 wlr_log(WLR_DEBUG, "Sending workspace::%s event", change); 298 wlr_log(WLR_DEBUG, "Sending workspace::%s event", change);
292 json_object *obj = json_object_new_object(); 299 json_object *obj = json_object_new_object();
293 json_object_object_add(obj, "change", json_object_new_string(change)); 300 json_object_object_add(obj, "change", json_object_new_string(change));
294 if (strcmp("focus", change) == 0) { 301 if (old) {
295 if (old) { 302 json_object_object_add(obj, "old",
296 json_object_object_add(obj, "old", 303 ipc_json_describe_container_recursive(old));
297 ipc_json_describe_container_recursive(old)); 304 } else {
298 } else { 305 json_object_object_add(obj, "old", NULL);
299 json_object_object_add(obj, "old", NULL);
300 }
301 } 306 }
302 307
303 if (new) { 308 if (new) {
@@ -353,6 +358,104 @@ void ipc_event_mode(const char *mode, bool pango) {
353 json_object_put(obj); 358 json_object_put(obj);
354} 359}
355 360
361void ipc_event_shutdown(const char *reason) {
362 if (!ipc_has_event_listeners(IPC_EVENT_SHUTDOWN)) {
363 return;
364 }
365 wlr_log(WLR_DEBUG, "Sending shutdown::%s event", reason);
366
367 json_object *json = json_object_new_object();
368 json_object_object_add(json, "change", json_object_new_string(reason));
369
370 const char *json_string = json_object_to_json_string(json);
371 ipc_send_event(json_string, IPC_EVENT_SHUTDOWN);
372 json_object_put(json);
373}
374
375void ipc_event_binding(struct sway_binding *binding) {
376 if (!ipc_has_event_listeners(IPC_EVENT_BINDING)) {
377 return;
378 }
379 wlr_log(WLR_DEBUG, "Sending binding event");
380
381 json_object *json_binding = json_object_new_object();
382 json_object_object_add(json_binding, "command", json_object_new_string(binding->command));
383
384 const char *names[10];
385 int len = get_modifier_names(names, binding->modifiers);
386 json_object *modifiers = json_object_new_array();
387 for (int i = 0; i < len; ++i) {
388 json_object_array_add(modifiers, json_object_new_string(names[i]));
389 }
390 json_object_object_add(json_binding, "event_state_mask", modifiers);
391
392 json_object *input_codes = json_object_new_array();
393 int input_code = 0;
394 json_object *symbols = json_object_new_array();
395 json_object *symbol = NULL;
396
397 if (binding->type == BINDING_KEYCODE) { // bindcode: populate input_codes
398 uint32_t keycode;
399 for (int i = 0; i < binding->keys->length; ++i) {
400 keycode = *(uint32_t *)binding->keys->items[i];
401 json_object_array_add(input_codes, json_object_new_int(keycode));
402 if (i == 0) {
403 input_code = keycode;
404 }
405 }
406 } else { // bindsym/mouse: populate symbols
407 uint32_t keysym;
408 char buffer[64];
409 for (int i = 0; i < binding->keys->length; ++i) {
410 keysym = *(uint32_t *)binding->keys->items[i];
411 if (keysym >= BTN_LEFT && keysym <= BTN_LEFT + 8) {
412 snprintf(buffer, 64, "button%u", keysym - BTN_LEFT + 1);
413 } else if (xkb_keysym_get_name(keysym, buffer, 64) < 0) {
414 continue;
415 }
416
417 json_object *str = json_object_new_string(buffer);
418 if (i == 0) {
419 // str is owned by both symbol and symbols. Make sure
420 // to bump the ref count.
421 json_object_array_add(symbols, json_object_get(str));
422 symbol = str;
423 } else {
424 json_object_array_add(symbols, str);
425 }
426 }
427 }
428
429 json_object_object_add(json_binding, "input_codes", input_codes);
430 json_object_object_add(json_binding, "input_code", json_object_new_int(input_code));
431 json_object_object_add(json_binding, "symbols", symbols);
432 json_object_object_add(json_binding, "symbol", symbol);
433 json_object_object_add(json_binding, "input_type", binding->type == BINDING_MOUSE ?
434 json_object_new_string("mouse") : json_object_new_string("keyboard"));
435
436 json_object *json = json_object_new_object();
437 json_object_object_add(json, "change", json_object_new_string("run"));
438 json_object_object_add(json, "binding", json_binding);
439 const char *json_string = json_object_to_json_string(json);
440 ipc_send_event(json_string, IPC_EVENT_BINDING);
441 json_object_put(json);
442}
443
444static void ipc_event_tick(const char *payload) {
445 if (!ipc_has_event_listeners(IPC_EVENT_TICK)) {
446 return;
447 }
448 wlr_log(WLR_DEBUG, "Sending tick event");
449
450 json_object *json = json_object_new_object();
451 json_object_object_add(json, "first", json_object_new_boolean(false));
452 json_object_object_add(json, "payload", json_object_new_string(payload));
453
454 const char *json_string = json_object_to_json_string(json);
455 ipc_send_event(json_string, IPC_EVENT_TICK);
456 json_object_put(json);
457}
458
356int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { 459int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) {
357 struct ipc_client *client = data; 460 struct ipc_client *client = data;
358 461
@@ -494,6 +597,13 @@ void ipc_client_handle_command(struct ipc_client *client) {
494 goto exit_cleanup; 597 goto exit_cleanup;
495 } 598 }
496 599
600 case IPC_SEND_TICK:
601 {
602 ipc_event_tick(buf);
603 ipc_send_reply(client, "{\"success\": true}", 17);
604 goto exit_cleanup;
605 }
606
497 case IPC_GET_OUTPUTS: 607 case IPC_GET_OUTPUTS:
498 { 608 {
499 json_object *outputs = json_object_new_array(); 609 json_object *outputs = json_object_new_array();
@@ -540,6 +650,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
540 goto exit_cleanup; 650 goto exit_cleanup;
541 } 651 }
542 652
653 bool is_tick = false;
543 // parse requested event types 654 // parse requested event types
544 for (size_t i = 0; i < json_object_array_length(request); i++) { 655 for (size_t i = 0; i < json_object_array_length(request); i++) {
545 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i)); 656 const char *event_type = json_object_get_string(json_object_array_get_idx(request, i));
@@ -549,12 +660,15 @@ void ipc_client_handle_command(struct ipc_client *client) {
549 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE); 660 client->subscribed_events |= event_mask(IPC_EVENT_BARCONFIG_UPDATE);
550 } else if (strcmp(event_type, "mode") == 0) { 661 } else if (strcmp(event_type, "mode") == 0) {
551 client->subscribed_events |= event_mask(IPC_EVENT_MODE); 662 client->subscribed_events |= event_mask(IPC_EVENT_MODE);
663 } else if (strcmp(event_type, "shutdown") == 0) {
664 client->subscribed_events |= event_mask(IPC_EVENT_SHUTDOWN);
552 } else if (strcmp(event_type, "window") == 0) { 665 } else if (strcmp(event_type, "window") == 0) {
553 client->subscribed_events |= event_mask(IPC_EVENT_WINDOW); 666 client->subscribed_events |= event_mask(IPC_EVENT_WINDOW);
554 } else if (strcmp(event_type, "modifier") == 0) {
555 client->subscribed_events |= event_mask(IPC_EVENT_MODIFIER);
556 } else if (strcmp(event_type, "binding") == 0) { 667 } else if (strcmp(event_type, "binding") == 0) {
557 client->subscribed_events |= event_mask(IPC_EVENT_BINDING); 668 client->subscribed_events |= event_mask(IPC_EVENT_BINDING);
669 } else if (strcmp(event_type, "tick") == 0) {
670 client->subscribed_events |= event_mask(IPC_EVENT_TICK);
671 is_tick = true;
558 } else { 672 } else {
559 client_valid = 673 client_valid =
560 ipc_send_reply(client, "{\"success\": false}", 18); 674 ipc_send_reply(client, "{\"success\": false}", 18);
@@ -566,6 +680,10 @@ void ipc_client_handle_command(struct ipc_client *client) {
566 680
567 json_object_put(request); 681 json_object_put(request);
568 client_valid = ipc_send_reply(client, "{\"success\": true}", 17); 682 client_valid = ipc_send_reply(client, "{\"success\": true}", 17);
683 if (is_tick) {
684 client->current_command = IPC_EVENT_TICK;
685 ipc_send_reply(client, "{\"first\": true, \"payload\": \"\"}", 30);
686 }
569 goto exit_cleanup; 687 goto exit_cleanup;
570 } 688 }
571 689
diff --git a/sway/main.c b/sway/main.c
index a20f1dac..477ffa5a 100644
--- a/sway/main.c
+++ b/sway/main.c
@@ -36,6 +36,7 @@ struct sway_server server;
36void sway_terminate(int exit_code) { 36void sway_terminate(int exit_code) {
37 terminate_request = true; 37 terminate_request = true;
38 exit_value = exit_code; 38 exit_value = exit_code;
39 ipc_event_shutdown("exit");
39 wl_display_terminate(server.wl_display); 40 wl_display_terminate(server.wl_display);
40} 41}
41 42
diff --git a/sway/meson.build b/sway/meson.build
index 09bc40b8..a9503c3b 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -7,6 +7,7 @@ sway_sources = files(
7 'debug-tree.c', 7 'debug-tree.c',
8 'ipc-json.c', 8 'ipc-json.c',
9 'ipc-server.c', 9 'ipc-server.c',
10 'scratchpad.c',
10 'security.c', 11 'security.c',
11 12
12 'desktop/desktop.c', 13 'desktop/desktop.c',
@@ -17,7 +18,6 @@ sway_sources = files(
17 'desktop/transaction.c', 18 'desktop/transaction.c',
18 'desktop/xdg_shell_v6.c', 19 'desktop/xdg_shell_v6.c',
19 'desktop/xdg_shell.c', 20 'desktop/xdg_shell.c',
20 'desktop/xwayland.c',
21 21
22 'input/input-manager.c', 22 'input/input-manager.c',
23 'input/seat.c', 23 'input/seat.c',
@@ -42,6 +42,7 @@ sway_sources = files(
42 'commands/exec_always.c', 42 'commands/exec_always.c',
43 'commands/floating.c', 43 'commands/floating.c',
44 'commands/floating_minmax_size.c', 44 'commands/floating_minmax_size.c',
45 'commands/floating_modifier.c',
45 'commands/focus.c', 46 'commands/focus.c',
46 'commands/focus_follows_mouse.c', 47 'commands/focus_follows_mouse.c',
47 'commands/focus_wrapping.c', 48 'commands/focus_wrapping.c',
@@ -66,6 +67,7 @@ sway_sources = files(
66 'commands/reload.c', 67 'commands/reload.c',
67 'commands/rename.c', 68 'commands/rename.c',
68 'commands/resize.c', 69 'commands/resize.c',
70 'commands/scratchpad.c',
69 'commands/seat.c', 71 'commands/seat.c',
70 'commands/seat/attach.c', 72 'commands/seat/attach.c',
71 'commands/seat/cursor.c', 73 'commands/seat/cursor.c',
@@ -126,8 +128,10 @@ sway_sources = files(
126 'commands/input/scroll_method.c', 128 'commands/input/scroll_method.c',
127 'commands/input/tap.c', 129 'commands/input/tap.c',
128 'commands/input/tap_button_map.c', 130 'commands/input/tap_button_map.c',
131 'commands/input/xkb_capslock.c',
129 'commands/input/xkb_layout.c', 132 'commands/input/xkb_layout.c',
130 'commands/input/xkb_model.c', 133 'commands/input/xkb_model.c',
134 'commands/input/xkb_numlock.c',
131 'commands/input/xkb_options.c', 135 'commands/input/xkb_options.c',
132 'commands/input/xkb_rules.c', 136 'commands/input/xkb_rules.c',
133 'commands/input/xkb_variant.c', 137 'commands/input/xkb_variant.c',
@@ -162,10 +166,14 @@ sway_deps = [
162 server_protos, 166 server_protos,
163 wayland_server, 167 wayland_server,
164 wlroots, 168 wlroots,
165 xcb,
166 xkbcommon, 169 xkbcommon,
167] 170]
168 171
172if get_option('enable-xwayland')
173 sway_sources += 'desktop/xwayland.c'
174 sway_deps += xcb
175endif
176
169executable( 177executable(
170 'sway', 178 'sway',
171 sway_sources, 179 sway_sources,
diff --git a/sway/scratchpad.c b/sway/scratchpad.c
new file mode 100644
index 00000000..b7d6fd99
--- /dev/null
+++ b/sway/scratchpad.c
@@ -0,0 +1,181 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <stdio.h>
4#include <stdbool.h>
5#include "sway/scratchpad.h"
6#include "sway/input/seat.h"
7#include "sway/tree/arrange.h"
8#include "sway/tree/container.h"
9#include "sway/tree/view.h"
10#include "sway/tree/workspace.h"
11#include "list.h"
12#include "log.h"
13
14void scratchpad_add_container(struct sway_container *con) {
15 if (!sway_assert(!con->scratchpad, "Container is already in scratchpad")) {
16 return;
17 }
18 con->scratchpad = true;
19 list_add(root_container.sway_root->scratchpad, con);
20
21 struct sway_container *parent = con->parent;
22 container_set_floating(con, true);
23 container_remove_child(con);
24 arrange_windows(parent);
25
26 struct sway_seat *seat = input_manager_current_seat(input_manager);
27 seat_set_focus(seat, seat_get_focus_inactive(seat, parent));
28}
29
30void scratchpad_remove_container(struct sway_container *con) {
31 if (!sway_assert(con->scratchpad, "Container is not in scratchpad")) {
32 return;
33 }
34 con->scratchpad = false;
35 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
36 if (root_container.sway_root->scratchpad->items[i] == con) {
37 list_del(root_container.sway_root->scratchpad, i);
38 break;
39 }
40 }
41}
42
43/**
44 * Show a single scratchpad container.
45 * The container might be visible on another workspace already.
46 */
47static void scratchpad_show(struct sway_container *con) {
48 struct sway_seat *seat = input_manager_current_seat(input_manager);
49 struct sway_container *ws = seat_get_focus(seat);
50 if (ws->type != C_WORKSPACE) {
51 ws = container_parent(ws, C_WORKSPACE);
52 }
53
54 // If the current con or any of its parents are in fullscreen mode, we
55 // first need to disable it before showing the scratchpad con.
56 if (ws->sway_workspace->fullscreen) {
57 container_set_fullscreen(ws->sway_workspace->fullscreen, false);
58 }
59
60 // Show the container
61 if (con->parent) {
62 container_remove_child(con);
63 }
64 container_add_child(ws->sway_workspace->floating, con);
65
66 // Make sure the container's center point overlaps this workspace
67 double center_lx = con->x + con->width / 2;
68 double center_ly = con->y + con->height / 2;
69
70 struct wlr_box workspace_box;
71 container_get_box(ws, &workspace_box);
72 if (!wlr_box_contains_point(&workspace_box, center_lx, center_ly)) {
73 // Maybe resize it
74 if (con->width > ws->width || con->height > ws->height) {
75 container_init_floating(con);
76 }
77
78 // Center it
79 double new_lx = ws->x + (ws->width - con->width) / 2;
80 double new_ly = ws->y + (ws->height - con->height) / 2;
81 container_floating_move_to(con, new_lx, new_ly);
82 }
83
84 arrange_windows(ws);
85 seat_set_focus(seat, seat_get_focus_inactive(seat, con));
86
87 container_set_dirty(con->parent);
88}
89
90/**
91 * Hide a single scratchpad container.
92 * The container might not be the focused container (eg. when using criteria).
93 */
94static void scratchpad_hide(struct sway_container *con) {
95 struct sway_seat *seat = input_manager_current_seat(input_manager);
96 struct sway_container *focus = seat_get_focus(seat);
97 struct sway_container *ws = container_parent(con, C_WORKSPACE);
98
99 container_remove_child(con);
100 arrange_windows(ws);
101 if (con == focus) {
102 seat_set_focus(seat, seat_get_focus_inactive(seat, ws));
103 }
104 list_move_to_end(root_container.sway_root->scratchpad, con);
105}
106
107void scratchpad_toggle_auto(void) {
108 struct sway_seat *seat = input_manager_current_seat(input_manager);
109 struct sway_container *focus = seat_get_focus(seat);
110 struct sway_container *ws = focus->type == C_WORKSPACE ?
111 focus : container_parent(focus, C_WORKSPACE);
112
113 // If the focus is in a floating split container,
114 // operate on the split container instead of the child.
115 if (container_is_floating_or_child(focus)) {
116 while (focus->parent->layout != L_FLOATING) {
117 focus = focus->parent;
118 }
119 }
120
121
122 // Check if the currently focused window is a scratchpad window and should
123 // be hidden again.
124 if (focus->scratchpad) {
125 wlr_log(WLR_DEBUG, "Focus is a scratchpad window - hiding %s",
126 focus->name);
127 scratchpad_hide(focus);
128 return;
129 }
130
131 // Check if there is an unfocused scratchpad window on the current workspace
132 // and focus it.
133 for (int i = 0; i < ws->sway_workspace->floating->children->length; ++i) {
134 struct sway_container *floater =
135 ws->sway_workspace->floating->children->items[i];
136 if (floater->scratchpad && focus != floater) {
137 wlr_log(WLR_DEBUG,
138 "Focusing other scratchpad window (%s) in this workspace",
139 floater->name);
140 scratchpad_show(floater);
141 return;
142 }
143 }
144
145 // Check if there is a visible scratchpad window on another workspace.
146 // In this case we move it to the current workspace.
147 for (int i = 0; i < root_container.sway_root->scratchpad->length; ++i) {
148 struct sway_container *con =
149 root_container.sway_root->scratchpad->items[i];
150 if (con->parent) {
151 wlr_log(WLR_DEBUG,
152 "Moving a visible scratchpad window (%s) to this workspace",
153 con->name);
154 scratchpad_show(con);
155 return;
156 }
157 }
158
159 // Take the container at the bottom of the scratchpad list
160 if (!sway_assert(root_container.sway_root->scratchpad->length,
161 "Scratchpad is empty")) {
162 return;
163 }
164 struct sway_container *con = root_container.sway_root->scratchpad->items[0];
165 wlr_log(WLR_DEBUG, "Showing %s from list", con->name);
166 scratchpad_show(con);
167}
168
169void scratchpad_toggle_container(struct sway_container *con) {
170 if (!sway_assert(con->scratchpad, "Container isn't in the scratchpad")) {
171 return;
172 }
173
174 // Check if it matches a currently visible scratchpad window and hide it.
175 if (con->parent) {
176 scratchpad_hide(con);
177 return;
178 }
179
180 scratchpad_show(con);
181}
diff --git a/sway/server.c b/sway/server.c
index 91ae7c97..e8755360 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -26,7 +26,10 @@
26#include "sway/input/input-manager.h" 26#include "sway/input/input-manager.h"
27#include "sway/server.h" 27#include "sway/server.h"
28#include "sway/tree/layout.h" 28#include "sway/tree/layout.h"
29#include "config.h"
30#ifdef HAVE_XWAYLAND
29#include "sway/xwayland.h" 31#include "sway/xwayland.h"
32#endif
30 33
31bool server_privileged_prepare(struct sway_server *server) { 34bool server_privileged_prepare(struct sway_server *server) {
32 wlr_log(WLR_DEBUG, "Preparing Wayland server initialization"); 35 wlr_log(WLR_DEBUG, "Preparing Wayland server initialization");
@@ -83,6 +86,7 @@ bool server_init(struct sway_server *server) {
83 server->xdg_shell_surface.notify = handle_xdg_shell_surface; 86 server->xdg_shell_surface.notify = handle_xdg_shell_surface;
84 87
85 // TODO make xwayland optional 88 // TODO make xwayland optional
89#ifdef HAVE_XWAYLAND
86 server->xwayland.wlr_xwayland = 90 server->xwayland.wlr_xwayland =
87 wlr_xwayland_create(server->wl_display, server->compositor, true); 91 wlr_xwayland_create(server->wl_display, server->compositor, true);
88 wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface, 92 wl_signal_add(&server->xwayland.wlr_xwayland->events.new_surface,
@@ -103,6 +107,7 @@ bool server_init(struct sway_server *server) {
103 image->width * 4, image->width, image->height, image->hotspot_x, 107 image->width * 4, image->width, image->height, image->hotspot_x,
104 image->hotspot_y); 108 image->hotspot_y);
105 } 109 }
110#endif
106 111
107 // TODO: Integration with sway borders 112 // TODO: Integration with sway borders
108 struct wlr_server_decoration_manager *deco_manager = 113 struct wlr_server_decoration_manager *deco_manager =
diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd
index b6391431..707c36af 100644
--- a/sway/sway-input.5.scd
+++ b/sway/sway-input.5.scd
@@ -33,6 +33,14 @@ For more information on these xkb configuration options, see
33*input* <identifier> xkb\_variant <variant> 33*input* <identifier> xkb\_variant <variant>
34 Sets the variant of the keyboard like _dvorak_ or _colemak_. 34 Sets the variant of the keyboard like _dvorak_ or _colemak_.
35 35
36The following commands may only be used in the configuration file.
37
38*input* <identifier> xkb\_capslock enabled|disabled
39 Initially enables or disables CapsLock, the default is disabled.
40
41*input* <identifier> xkb\_numlock enabled|disabled
42 Initially enables or disables NumLock, the default is disabled.
43
36## MAPPING CONFIGURATION 44## MAPPING CONFIGURATION
37 45
38*input* <identifier> map\_to\_output <identifier> 46*input* <identifier> map\_to\_output <identifier>
@@ -100,7 +108,7 @@ For more information on these xkb configuration options, see
100*input* <identifier> tap enabled|disabled 108*input* <identifier> tap enabled|disabled
101 Enables or disables tap for specified input device. 109 Enables or disables tap for specified input device.
102 110
103*input* <identifier> tap_button_map lrm|lmr 111*input* <identifier> tap\_button\_map lrm|lmr
104 Specifies which button mapping to use for tapping. _lrm_ treats 1 finger as 112 Specifies which button mapping to use for tapping. _lrm_ treats 1 finger as
105 left click, 2 fingers as right click, and 3 fingers as middle click. _lmr_ 113 left click, 2 fingers as right click, and 3 fingers as middle click. _lmr_
106 treats 1 finger as left click, 2 fingers as middle click, and 3 fingers as 114 treats 1 finger as left click, 2 fingers as middle click, and 3 fingers as
diff --git a/sway/sway.1.scd b/sway/sway.1.scd
index 5b770cce..0c2ee974 100644
--- a/sway/sway.1.scd
+++ b/sway/sway.1.scd
@@ -92,4 +92,4 @@ source contributors. For more information about sway development, see
92 92
93# SEE ALSO 93# SEE ALSO
94 94
95*sway*(5) *swaymsg*(1) *swaygrab*(1) *sway-input*(5) *sway-bar*(5) 95*sway*(5) *swaymsg*(1) *sway-input*(5) *sway-bar*(5)
diff --git a/sway/tree/arrange.c b/sway/tree/arrange.c
index 533cf71c..5452b13c 100644
--- a/sway/tree/arrange.c
+++ b/sway/tree/arrange.c
@@ -220,8 +220,22 @@ static void arrange_workspace(struct sway_container *workspace) {
220 container_set_dirty(workspace); 220 container_set_dirty(workspace);
221 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name, 221 wlr_log(WLR_DEBUG, "Arranging workspace '%s' at %f, %f", workspace->name,
222 workspace->x, workspace->y); 222 workspace->x, workspace->y);
223 arrange_floating(workspace->sway_workspace->floating); 223 if (workspace->sway_workspace->fullscreen) {
224 arrange_children_of(workspace); 224 struct sway_container *fs = workspace->sway_workspace->fullscreen;
225 fs->x = workspace->parent->x;
226 fs->y = workspace->parent->y;
227 fs->width = workspace->parent->width;
228 fs->height = workspace->parent->height;
229 if (fs->type == C_VIEW) {
230 view_autoconfigure(fs->sway_view);
231 } else {
232 arrange_children_of(fs);
233 }
234 container_set_dirty(fs);
235 } else {
236 arrange_floating(workspace->sway_workspace->floating);
237 arrange_children_of(workspace);
238 }
225} 239}
226 240
227static void arrange_output(struct sway_container *output) { 241static void arrange_output(struct sway_container *output) {
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 4dbfbb29..46c54e2d 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -17,6 +17,7 @@
17#include "sway/input/seat.h" 17#include "sway/input/seat.h"
18#include "sway/ipc-server.h" 18#include "sway/ipc-server.h"
19#include "sway/output.h" 19#include "sway/output.h"
20#include "sway/scratchpad.h"
20#include "sway/server.h" 21#include "sway/server.h"
21#include "sway/tree/arrange.h" 22#include "sway/tree/arrange.h"
22#include "sway/tree/layout.h" 23#include "sway/tree/layout.h"
@@ -61,13 +62,17 @@ void container_create_notify(struct sway_container *container) {
61 // TODO send ipc event type based on the container type 62 // TODO send ipc event type based on the container type
62 wl_signal_emit(&root_container.sway_root->events.new_container, container); 63 wl_signal_emit(&root_container.sway_root->events.new_container, container);
63 64
64 if (container->type == C_VIEW || container->type == C_CONTAINER) { 65 if (container->type == C_VIEW) {
65 ipc_event_window(container, "new"); 66 ipc_event_window(container, "new");
67 } else if (container->type == C_WORKSPACE) {
68 ipc_event_workspace(NULL, container, "init");
66 } 69 }
67} 70}
68 71
69static void container_update_textures_recursive(struct sway_container *con) { 72void container_update_textures_recursive(struct sway_container *con) {
70 container_update_title_textures(con); 73 if (con->type == C_CONTAINER || con->type == C_VIEW) {
74 container_update_title_textures(con);
75 }
71 76
72 if (con->type == C_VIEW) { 77 if (con->type == C_VIEW) {
73 view_update_marks_textures(con->sway_view); 78 view_update_marks_textures(con->sway_view);
@@ -76,6 +81,10 @@ static void container_update_textures_recursive(struct sway_container *con) {
76 struct sway_container *child = con->children->items[i]; 81 struct sway_container *child = con->children->items[i];
77 container_update_textures_recursive(child); 82 container_update_textures_recursive(child);
78 } 83 }
84
85 if (con->type == C_WORKSPACE) {
86 container_update_textures_recursive(con->sway_workspace->floating);
87 }
79 } 88 }
80} 89}
81 90
@@ -139,8 +148,6 @@ struct sway_container *container_create(enum sway_container_type type) {
139static void container_workspace_free(struct sway_workspace *ws) { 148static void container_workspace_free(struct sway_workspace *ws) {
140 list_foreach(ws->output_priority, free); 149 list_foreach(ws->output_priority, free);
141 list_free(ws->output_priority); 150 list_free(ws->output_priority);
142 ws->floating->destroying = true;
143 container_free(ws->floating);
144 free(ws); 151 free(ws);
145} 152}
146 153
@@ -193,6 +200,9 @@ void container_free(struct sway_container *cont) {
193 free(cont); 200 free(cont);
194} 201}
195 202
203static struct sway_container *container_destroy_noreaping(
204 struct sway_container *con);
205
196static struct sway_container *container_workspace_destroy( 206static struct sway_container *container_workspace_destroy(
197 struct sway_container *workspace) { 207 struct sway_container *workspace) {
198 if (!sway_assert(workspace, "cannot destroy null workspace")) { 208 if (!sway_assert(workspace, "cannot destroy null workspace")) {
@@ -237,6 +247,8 @@ static struct sway_container *container_workspace_destroy(
237 } 247 }
238 } 248 }
239 249
250 container_destroy_noreaping(workspace->sway_workspace->floating);
251
240 return output; 252 return output;
241} 253}
242 254
@@ -271,7 +283,7 @@ static struct sway_container *container_output_destroy(
271 container_remove_child(workspace); 283 container_remove_child(workspace);
272 if (!workspace_is_empty(workspace)) { 284 if (!workspace_is_empty(workspace)) {
273 container_add_child(new_output, workspace); 285 container_add_child(new_output, workspace);
274 ipc_event_workspace(workspace, NULL, "move"); 286 ipc_event_workspace(NULL, workspace, "move");
275 } else { 287 } else {
276 container_destroy(workspace); 288 container_destroy(workspace);
277 } 289 }
@@ -309,7 +321,13 @@ static struct sway_container *container_destroy_noreaping(
309 } 321 }
310 322
311 wl_signal_emit(&con->events.destroy, con); 323 wl_signal_emit(&con->events.destroy, con);
312 ipc_event_window(con, "close"); 324
325 // emit IPC event
326 if (con->type == C_VIEW) {
327 ipc_event_window(con, "close");
328 } else if (con->type == C_WORKSPACE) {
329 ipc_event_workspace(NULL, con, "empty");
330 }
313 331
314 // The below functions move their children to somewhere else. 332 // The below functions move their children to somewhere else.
315 if (con->type == C_OUTPUT) { 333 if (con->type == C_OUTPUT) {
@@ -323,9 +341,15 @@ static struct sway_container *container_destroy_noreaping(
323 } 341 }
324 } 342 }
325 343
344 container_end_mouse_operation(con);
345
326 con->destroying = true; 346 con->destroying = true;
327 container_set_dirty(con); 347 container_set_dirty(con);
328 348
349 if (con->scratchpad) {
350 scratchpad_remove_container(con);
351 }
352
329 if (!con->parent) { 353 if (!con->parent) {
330 return NULL; 354 return NULL;
331 } 355 }
@@ -398,6 +422,10 @@ struct sway_container *container_flatten(struct sway_container *container) {
398 * This function just wraps container_destroy_noreaping(), then does reaping. 422 * This function just wraps container_destroy_noreaping(), then does reaping.
399 */ 423 */
400struct sway_container *container_destroy(struct sway_container *con) { 424struct sway_container *container_destroy(struct sway_container *con) {
425 if (con->is_fullscreen) {
426 struct sway_container *ws = container_parent(con, C_WORKSPACE);
427 ws->sway_workspace->fullscreen = NULL;
428 }
401 struct sway_container *parent = container_destroy_noreaping(con); 429 struct sway_container *parent = container_destroy_noreaping(con);
402 430
403 if (!parent) { 431 if (!parent) {
@@ -507,7 +535,7 @@ struct sway_container *container_parent(struct sway_container *container,
507 return container; 535 return container;
508} 536}
509 537
510static struct sway_container *container_at_view(struct sway_container *swayc, 538struct sway_container *container_at_view(struct sway_container *swayc,
511 double lx, double ly, 539 double lx, double ly,
512 struct wlr_surface **surface, double *sx, double *sy) { 540 struct wlr_surface **surface, double *sx, double *sy) {
513 if (!sway_assert(swayc->type == C_VIEW, "Expected a view")) { 541 if (!sway_assert(swayc->type == C_VIEW, "Expected a view")) {
@@ -520,10 +548,12 @@ static struct sway_container *container_at_view(struct sway_container *swayc,
520 double _sx, _sy; 548 double _sx, _sy;
521 struct wlr_surface *_surface = NULL; 549 struct wlr_surface *_surface = NULL;
522 switch (sview->type) { 550 switch (sview->type) {
551#ifdef HAVE_XWAYLAND
523 case SWAY_VIEW_XWAYLAND: 552 case SWAY_VIEW_XWAYLAND:
524 _surface = wlr_surface_surface_at(sview->surface, 553 _surface = wlr_surface_surface_at(sview->surface,
525 view_sx, view_sy, &_sx, &_sy); 554 view_sx, view_sy, &_sx, &_sy);
526 break; 555 break;
556#endif
527 case SWAY_VIEW_XDG_SHELL_V6: 557 case SWAY_VIEW_XDG_SHELL_V6:
528 _surface = wlr_xdg_surface_v6_surface_at( 558 _surface = wlr_xdg_surface_v6_surface_at(
529 sview->wlr_xdg_surface_v6, 559 sview->wlr_xdg_surface_v6,
@@ -539,10 +569,15 @@ static struct sway_container *container_at_view(struct sway_container *swayc,
539 *sx = _sx; 569 *sx = _sx;
540 *sy = _sy; 570 *sy = _sy;
541 *surface = _surface; 571 *surface = _surface;
572 return swayc;
542 } 573 }
543 return swayc; 574 return NULL;
544} 575}
545 576
577static struct sway_container *tiling_container_at(
578 struct sway_container *con, double lx, double ly,
579 struct wlr_surface **surface, double *sx, double *sy);
580
546/** 581/**
547 * container_at for a container with layout L_TABBED. 582 * container_at for a container with layout L_TABBED.
548 */ 583 */
@@ -569,7 +604,7 @@ static struct sway_container *container_at_tabbed(struct sway_container *parent,
569 // Surfaces 604 // Surfaces
570 struct sway_container *current = seat_get_active_child(seat, parent); 605 struct sway_container *current = seat_get_active_child(seat, parent);
571 606
572 return container_at(current, lx, ly, surface, sx, sy); 607 return tiling_container_at(current, lx, ly, surface, sx, sy);
573} 608}
574 609
575/** 610/**
@@ -594,7 +629,7 @@ static struct sway_container *container_at_stacked(
594 // Surfaces 629 // Surfaces
595 struct sway_container *current = seat_get_active_child(seat, parent); 630 struct sway_container *current = seat_get_active_child(seat, parent);
596 631
597 return container_at(current, lx, ly, surface, sx, sy); 632 return tiling_container_at(current, lx, ly, surface, sx, sy);
598} 633}
599 634
600/** 635/**
@@ -612,45 +647,13 @@ static struct sway_container *container_at_linear(struct sway_container *parent,
612 .height = child->height, 647 .height = child->height,
613 }; 648 };
614 if (wlr_box_contains_point(&box, lx, ly)) { 649 if (wlr_box_contains_point(&box, lx, ly)) {
615 return container_at(child, lx, ly, surface, sx, sy); 650 return tiling_container_at(child, lx, ly, surface, sx, sy);
616 } 651 }
617 } 652 }
618 return NULL; 653 return NULL;
619} 654}
620 655
621struct sway_container *container_at(struct sway_container *parent, 656static struct sway_container *floating_container_at(double lx, double ly,
622 double lx, double ly,
623 struct wlr_surface **surface, double *sx, double *sy) {
624 if (!sway_assert(parent->type >= C_WORKSPACE,
625 "Expected workspace or deeper")) {
626 return NULL;
627 }
628 if (parent->type == C_VIEW) {
629 return container_at_view(parent, lx, ly, surface, sx, sy);
630 }
631 if (!parent->children->length) {
632 return NULL;
633 }
634
635 switch (parent->layout) {
636 case L_HORIZ:
637 case L_VERT:
638 return container_at_linear(parent, lx, ly, surface, sx, sy);
639 case L_TABBED:
640 return container_at_tabbed(parent, lx, ly, surface, sx, sy);
641 case L_STACKED:
642 return container_at_stacked(parent, lx, ly, surface, sx, sy);
643 case L_FLOATING:
644 sway_assert(false, "Didn't expect to see floating here");
645 return NULL;
646 case L_NONE:
647 return NULL;
648 }
649
650 return NULL;
651}
652
653struct sway_container *floating_container_at(double lx, double ly,
654 struct wlr_surface **surface, double *sx, double *sy) { 657 struct wlr_surface **surface, double *sx, double *sy) {
655 for (int i = 0; i < root_container.children->length; ++i) { 658 for (int i = 0; i < root_container.children->length; ++i) {
656 struct sway_container *output = root_container.children->items[i]; 659 struct sway_container *output = root_container.children->items[i];
@@ -672,7 +675,8 @@ struct sway_container *floating_container_at(double lx, double ly,
672 .height = floater->height, 675 .height = floater->height,
673 }; 676 };
674 if (wlr_box_contains_point(&box, lx, ly)) { 677 if (wlr_box_contains_point(&box, lx, ly)) {
675 return container_at(floater, lx, ly, surface, sx, sy); 678 return tiling_container_at(floater, lx, ly,
679 surface, sx, sy);
676 } 680 }
677 } 681 }
678 } 682 }
@@ -680,6 +684,90 @@ struct sway_container *floating_container_at(double lx, double ly,
680 return NULL; 684 return NULL;
681} 685}
682 686
687static struct sway_container *tiling_container_at(
688 struct sway_container *con, double lx, double ly,
689 struct wlr_surface **surface, double *sx, double *sy) {
690 if (con->type == C_VIEW) {
691 return container_at_view(con, lx, ly, surface, sx, sy);
692 }
693 if (!con->children->length) {
694 return NULL;
695 }
696
697 switch (con->layout) {
698 case L_HORIZ:
699 case L_VERT:
700 return container_at_linear(con, lx, ly, surface, sx, sy);
701 case L_TABBED:
702 return container_at_tabbed(con, lx, ly, surface, sx, sy);
703 case L_STACKED:
704 return container_at_stacked(con, lx, ly, surface, sx, sy);
705 case L_FLOATING:
706 sway_assert(false, "Didn't expect to see floating here");
707 return NULL;
708 case L_NONE:
709 return NULL;
710 }
711 return NULL;
712}
713
714static bool surface_is_popup(struct wlr_surface *surface) {
715 if (wlr_surface_is_xdg_surface(surface)) {
716 struct wlr_xdg_surface *xdg_surface =
717 wlr_xdg_surface_from_wlr_surface(surface);
718 while (xdg_surface) {
719 if (xdg_surface->role == WLR_XDG_SURFACE_ROLE_POPUP) {
720 return true;
721 }
722 xdg_surface = xdg_surface->toplevel->parent;
723 }
724 return false;
725 }
726
727 if (wlr_surface_is_xdg_surface_v6(surface)) {
728 struct wlr_xdg_surface_v6 *xdg_surface_v6 =
729 wlr_xdg_surface_v6_from_wlr_surface(surface);
730 while (xdg_surface_v6) {
731 if (xdg_surface_v6->role == WLR_XDG_SURFACE_V6_ROLE_POPUP) {
732 return true;
733 }
734 xdg_surface_v6 = xdg_surface_v6->toplevel->parent;
735 }
736 return false;
737 }
738
739 return false;
740}
741
742struct sway_container *container_at(struct sway_container *workspace,
743 double lx, double ly,
744 struct wlr_surface **surface, double *sx, double *sy) {
745 if (!sway_assert(workspace->type == C_WORKSPACE, "Expected a workspace")) {
746 return NULL;
747 }
748 struct sway_container *c;
749 // Focused view's popups
750 struct sway_seat *seat = input_manager_current_seat(input_manager);
751 struct sway_container *focus =
752 seat_get_focus_inactive(seat, &root_container);
753 if (focus && focus->type == C_VIEW) {
754 container_at_view(focus, lx, ly, surface, sx, sy);
755 if (*surface && surface_is_popup(*surface)) {
756 return focus;
757 }
758 *surface = NULL;
759 }
760 // Floating
761 if ((c = floating_container_at(lx, ly, surface, sx, sy))) {
762 return c;
763 }
764 // Tiling
765 if ((c = tiling_container_at(workspace, lx, ly, surface, sx, sy))) {
766 return c;
767 }
768 return NULL;
769}
770
683void container_for_each_descendant_dfs(struct sway_container *container, 771void container_for_each_descendant_dfs(struct sway_container *container,
684 void (*f)(struct sway_container *container, void *data), 772 void (*f)(struct sway_container *container, void *data),
685 void *data) { 773 void *data) {
@@ -934,36 +1022,104 @@ size_t container_titlebar_height() {
934 return config->font_height + TITLEBAR_V_PADDING * 2; 1022 return config->font_height + TITLEBAR_V_PADDING * 2;
935} 1023}
936 1024
1025void container_init_floating(struct sway_container *con) {
1026 if (!sway_assert(con->type == C_VIEW || con->type == C_CONTAINER,
1027 "Expected a view or container")) {
1028 return;
1029 }
1030 struct sway_container *ws = container_parent(con, C_WORKSPACE);
1031 int min_width, min_height;
1032 int max_width, max_height;
1033
1034 if (config->floating_minimum_width == -1) { // no minimum
1035 min_width = 0;
1036 } else if (config->floating_minimum_width == 0) { // automatic
1037 min_width = 75;
1038 } else {
1039 min_width = config->floating_minimum_width;
1040 }
1041
1042 if (config->floating_minimum_height == -1) { // no minimum
1043 min_height = 0;
1044 } else if (config->floating_minimum_height == 0) { // automatic
1045 min_height = 50;
1046 } else {
1047 min_height = config->floating_minimum_height;
1048 }
1049
1050 if (config->floating_maximum_width == -1) { // no maximum
1051 max_width = INT_MAX;
1052 } else if (config->floating_maximum_width == 0) { // automatic
1053 max_width = ws->width * 0.6666;
1054 } else {
1055 max_width = config->floating_maximum_width;
1056 }
1057
1058 if (config->floating_maximum_height == -1) { // no maximum
1059 max_height = INT_MAX;
1060 } else if (config->floating_maximum_height == 0) { // automatic
1061 max_height = ws->height * 0.6666;
1062 } else {
1063 max_height = config->floating_maximum_height;
1064 }
1065
1066 if (con->type == C_CONTAINER) {
1067 con->width = max_width;
1068 con->height = max_height;
1069 con->x = ws->x + (ws->width - con->width) / 2;
1070 con->y = ws->y + (ws->height - con->height) / 2;
1071 } else {
1072 struct sway_view *view = con->sway_view;
1073 view->width = fmax(min_width, fmin(view->natural_width, max_width));
1074 view->height = fmax(min_height, fmin(view->natural_height, max_height));
1075 view->x = ws->x + (ws->width - view->width) / 2;
1076 view->y = ws->y + (ws->height - view->height) / 2;
1077
1078 // If the view's border is B_NONE then these properties are ignored.
1079 view->border_top = view->border_bottom = true;
1080 view->border_left = view->border_right = true;
1081
1082 container_set_geometry_from_floating_view(view->swayc);
1083 }
1084}
1085
937void container_set_floating(struct sway_container *container, bool enable) { 1086void container_set_floating(struct sway_container *container, bool enable) {
938 if (container_is_floating(container) == enable) { 1087 if (container_is_floating(container) == enable) {
939 return; 1088 return;
940 } 1089 }
941 1090
942 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
943 struct sway_seat *seat = input_manager_current_seat(input_manager); 1091 struct sway_seat *seat = input_manager_current_seat(input_manager);
1092 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
944 1093
945 if (enable) { 1094 if (enable) {
946 container_remove_child(container); 1095 container_remove_child(container);
947 container_add_child(workspace->sway_workspace->floating, container); 1096 container_add_child(workspace->sway_workspace->floating, container);
1097 container_init_floating(container);
948 if (container->type == C_VIEW) { 1098 if (container->type == C_VIEW) {
949 view_init_floating(container->sway_view);
950 view_set_tiled(container->sway_view, false); 1099 view_set_tiled(container->sway_view, false);
951 } 1100 }
952 seat_set_focus(seat, seat_get_focus_inactive(seat, container));
953 container_reap_empty_recursive(workspace);
954 } else { 1101 } else {
955 // Returning to tiled 1102 // Returning to tiled
1103 if (container->scratchpad) {
1104 scratchpad_remove_container(container);
1105 }
956 container_remove_child(container); 1106 container_remove_child(container);
957 container_add_child(workspace, container); 1107 struct sway_container *reference =
1108 seat_get_focus_inactive_tiling(seat, workspace);
1109 if (reference->type == C_VIEW) {
1110 reference = reference->parent;
1111 }
1112 container_add_child(reference, container);
958 container->width = container->parent->width; 1113 container->width = container->parent->width;
959 container->height = container->parent->height; 1114 container->height = container->parent->height;
960 if (container->type == C_VIEW) { 1115 if (container->type == C_VIEW) {
961 view_set_tiled(container->sway_view, true); 1116 view_set_tiled(container->sway_view, true);
962 } 1117 }
963 container->is_sticky = false; 1118 container->is_sticky = false;
964 container_reap_empty_recursive(workspace->sway_workspace->floating);
965 } 1119 }
966 1120
1121 container_end_mouse_operation(container);
1122
967 ipc_event_window(container, "floating"); 1123 ipc_event_window(container, "floating");
968} 1124}
969 1125
@@ -1009,7 +1165,7 @@ void container_get_box(struct sway_container *container, struct wlr_box *box) {
1009/** 1165/**
1010 * Translate the container's position as well as all children. 1166 * Translate the container's position as well as all children.
1011 */ 1167 */
1012static void container_floating_translate(struct sway_container *con, 1168void container_floating_translate(struct sway_container *con,
1013 double x_amount, double y_amount) { 1169 double x_amount, double y_amount) {
1014 con->x += x_amount; 1170 con->x += x_amount;
1015 con->y += y_amount; 1171 con->y += y_amount;
@@ -1105,3 +1261,110 @@ static bool find_urgent_iterator(struct sway_container *con,
1105bool container_has_urgent_child(struct sway_container *container) { 1261bool container_has_urgent_child(struct sway_container *container) {
1106 return container_find(container, find_urgent_iterator, NULL); 1262 return container_find(container, find_urgent_iterator, NULL);
1107} 1263}
1264
1265void container_end_mouse_operation(struct sway_container *container) {
1266 struct sway_seat *seat;
1267 wl_list_for_each(seat, &input_manager->seats, link) {
1268 if (seat->op_container == container) {
1269 seat_end_mouse_operation(seat);
1270 }
1271 }
1272}
1273
1274static void set_fullscreen_iterator(struct sway_container *con, void *data) {
1275 if (con->type != C_VIEW) {
1276 return;
1277 }
1278 if (con->sway_view->impl->set_fullscreen) {
1279 bool *enable = data;
1280 con->sway_view->impl->set_fullscreen(con->sway_view, *enable);
1281 }
1282}
1283
1284void container_set_fullscreen(struct sway_container *container, bool enable) {
1285 if (container->is_fullscreen == enable) {
1286 return;
1287 }
1288
1289 struct sway_container *workspace = container_parent(container, C_WORKSPACE);
1290 if (enable && workspace->sway_workspace->fullscreen) {
1291 container_set_fullscreen(workspace->sway_workspace->fullscreen, false);
1292 }
1293
1294 container_for_each_descendant_dfs(container,
1295 set_fullscreen_iterator, &enable);
1296
1297 container->is_fullscreen = enable;
1298
1299 if (enable) {
1300 workspace->sway_workspace->fullscreen = container;
1301 container->saved_x = container->x;
1302 container->saved_y = container->y;
1303 container->saved_width = container->width;
1304 container->saved_height = container->height;
1305
1306 struct sway_seat *seat;
1307 struct sway_container *focus, *focus_ws;
1308 wl_list_for_each(seat, &input_manager->seats, link) {
1309 focus = seat_get_focus(seat);
1310 if (focus) {
1311 focus_ws = focus;
1312 if (focus_ws->type != C_WORKSPACE) {
1313 focus_ws = container_parent(focus_ws, C_WORKSPACE);
1314 }
1315 if (focus_ws == workspace) {
1316 seat_set_focus(seat, container);
1317 }
1318 }
1319 }
1320 } else {
1321 workspace->sway_workspace->fullscreen = NULL;
1322 if (container_is_floating(container)) {
1323 container->x = container->saved_x;
1324 container->y = container->saved_y;
1325 container->width = container->saved_width;
1326 container->height = container->saved_height;
1327 } else {
1328 container->width = container->saved_width;
1329 container->height = container->saved_height;
1330 }
1331 }
1332
1333 container_end_mouse_operation(container);
1334
1335 ipc_event_window(container, "fullscreen_mode");
1336}
1337
1338bool container_is_floating_or_child(struct sway_container *container) {
1339 do {
1340 if (container->parent && container->parent->layout == L_FLOATING) {
1341 return true;
1342 }
1343 container = container->parent;
1344 } while (container && container->type != C_WORKSPACE);
1345
1346 return false;
1347}
1348
1349bool container_is_fullscreen_or_child(struct sway_container *container) {
1350 do {
1351 if (container->is_fullscreen) {
1352 return true;
1353 }
1354 container = container->parent;
1355 } while (container && container->type != C_WORKSPACE);
1356
1357 return false;
1358}
1359
1360struct sway_container *container_wrap_children(struct sway_container *parent) {
1361 struct sway_container *middle = container_create(C_CONTAINER);
1362 middle->layout = parent->layout;
1363 while (parent->children->length) {
1364 struct sway_container *child = parent->children->items[0];
1365 container_remove_child(child);
1366 container_add_child(middle, child);
1367 }
1368 container_add_child(parent, middle);
1369 return middle;
1370}
diff --git a/sway/tree/layout.c b/sway/tree/layout.c
index 1f898f8a..1f82e534 100644
--- a/sway/tree/layout.c
+++ b/sway/tree/layout.c
@@ -6,6 +6,7 @@
6#include <string.h> 6#include <string.h>
7#include <wlr/types/wlr_output.h> 7#include <wlr/types/wlr_output.h>
8#include <wlr/types/wlr_output_layout.h> 8#include <wlr/types/wlr_output_layout.h>
9#include "config.h"
9#include "sway/debug.h" 10#include "sway/debug.h"
10#include "sway/tree/arrange.h" 11#include "sway/tree/arrange.h"
11#include "sway/tree/container.h" 12#include "sway/tree/container.h"
@@ -39,9 +40,12 @@ void layout_init(void) {
39 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root)); 40 root_container.sway_root = calloc(1, sizeof(*root_container.sway_root));
40 root_container.sway_root->output_layout = wlr_output_layout_create(); 41 root_container.sway_root->output_layout = wlr_output_layout_create();
41 wl_list_init(&root_container.sway_root->outputs); 42 wl_list_init(&root_container.sway_root->outputs);
43#ifdef HAVE_XWAYLAND
42 wl_list_init(&root_container.sway_root->xwayland_unmanaged); 44 wl_list_init(&root_container.sway_root->xwayland_unmanaged);
45#endif
43 wl_list_init(&root_container.sway_root->drag_icons); 46 wl_list_init(&root_container.sway_root->drag_icons);
44 wl_signal_init(&root_container.sway_root->events.new_container); 47 wl_signal_init(&root_container.sway_root->events.new_container);
48 root_container.sway_root->scratchpad = create_list();
45 49
46 root_container.sway_root->output_layout_change.notify = 50 root_container.sway_root->output_layout_change.notify =
47 output_layout_handle_change; 51 output_layout_handle_change;
@@ -62,10 +66,9 @@ static int index_child(const struct sway_container *child) {
62 66
63static void container_handle_fullscreen_reparent(struct sway_container *con, 67static void container_handle_fullscreen_reparent(struct sway_container *con,
64 struct sway_container *old_parent) { 68 struct sway_container *old_parent) {
65 if (con->type != C_VIEW || !con->sway_view->is_fullscreen) { 69 if (!con->is_fullscreen) {
66 return; 70 return;
67 } 71 }
68 struct sway_view *view = con->sway_view;
69 struct sway_container *old_workspace = old_parent; 72 struct sway_container *old_workspace = old_parent;
70 if (old_workspace && old_workspace->type != C_WORKSPACE) { 73 if (old_workspace && old_workspace->type != C_WORKSPACE) {
71 old_workspace = container_parent(old_workspace, C_WORKSPACE); 74 old_workspace = container_parent(old_workspace, C_WORKSPACE);
@@ -81,19 +84,27 @@ static void container_handle_fullscreen_reparent(struct sway_container *con,
81 84
82 // Mark the new workspace as fullscreen 85 // Mark the new workspace as fullscreen
83 if (new_workspace->sway_workspace->fullscreen) { 86 if (new_workspace->sway_workspace->fullscreen) {
84 view_set_fullscreen(new_workspace->sway_workspace->fullscreen, false); 87 container_set_fullscreen(
88 new_workspace->sway_workspace->fullscreen, false);
85 } 89 }
86 new_workspace->sway_workspace->fullscreen = view; 90 new_workspace->sway_workspace->fullscreen = con;
87 // Resize view to new output dimensions 91
92 // Resize container to new output dimensions
88 struct sway_container *output = new_workspace->parent; 93 struct sway_container *output = new_workspace->parent;
89 view->x = output->x;
90 view->y = output->y;
91 view->width = output->width;
92 view->height = output->height;
93 con->x = output->x; 94 con->x = output->x;
94 con->y = output->y; 95 con->y = output->y;
95 con->width = output->width; 96 con->width = output->width;
96 con->height = output->height; 97 con->height = output->height;
98
99 if (con->type == C_VIEW) {
100 struct sway_view *view = con->sway_view;
101 view->x = output->x;
102 view->y = output->y;
103 view->width = output->width;
104 view->height = output->height;
105 } else {
106 arrange_windows(new_workspace);
107 }
97} 108}
98 109
99void container_insert_child(struct sway_container *parent, 110void container_insert_child(struct sway_container *parent,
@@ -135,10 +146,14 @@ void container_add_child(struct sway_container *parent,
135 list_add(parent->children, child); 146 list_add(parent->children, child);
136 child->parent = parent; 147 child->parent = parent;
137 container_handle_fullscreen_reparent(child, old_parent); 148 container_handle_fullscreen_reparent(child, old_parent);
149 if (old_parent) {
150 container_set_dirty(old_parent);
151 }
152 container_set_dirty(child);
138} 153}
139 154
140struct sway_container *container_remove_child(struct sway_container *child) { 155struct sway_container *container_remove_child(struct sway_container *child) {
141 if (child->type == C_VIEW && child->sway_view->is_fullscreen) { 156 if (child->is_fullscreen) {
142 struct sway_container *workspace = container_parent(child, C_WORKSPACE); 157 struct sway_container *workspace = container_parent(child, C_WORKSPACE);
143 workspace->sway_workspace->fullscreen = NULL; 158 workspace->sway_workspace->fullscreen = NULL;
144 } 159 }
@@ -153,6 +168,9 @@ struct sway_container *container_remove_child(struct sway_container *child) {
153 child->parent = NULL; 168 child->parent = NULL;
154 container_notify_subtree_changed(parent); 169 container_notify_subtree_changed(parent);
155 170
171 container_set_dirty(parent);
172 container_set_dirty(child);
173
156 return parent; 174 return parent;
157} 175}
158 176
@@ -199,7 +217,9 @@ void container_move_to(struct sway_container *container,
199 container_sort_workspaces(new_parent); 217 container_sort_workspaces(new_parent);
200 seat_set_focus(seat, new_parent); 218 seat_set_focus(seat, new_parent);
201 workspace_output_raise_priority(container, old_parent, new_parent); 219 workspace_output_raise_priority(container, old_parent, new_parent);
202 ipc_event_workspace(container, NULL, "move"); 220 ipc_event_workspace(NULL, container, "move");
221 } else if (container->type == C_VIEW) {
222 ipc_event_window(container, "move");
203 } 223 }
204 container_notify_subtree_changed(old_parent); 224 container_notify_subtree_changed(old_parent);
205 container_notify_subtree_changed(new_parent); 225 container_notify_subtree_changed(new_parent);
@@ -218,10 +238,10 @@ void container_move_to(struct sway_container *container,
218 if (focus_ws->type != C_WORKSPACE) { 238 if (focus_ws->type != C_WORKSPACE) {
219 focus_ws = container_parent(focus_ws, C_WORKSPACE); 239 focus_ws = container_parent(focus_ws, C_WORKSPACE);
220 } 240 }
221 seat_set_focus(seat, 241 if (focus_ws == new_workspace) {
222 new_workspace->sway_workspace->fullscreen->swayc); 242 struct sway_container *new_focus = seat_get_focus_inactive(seat,
223 if (focus_ws != new_workspace) { 243 new_workspace->sway_workspace->fullscreen);
224 seat_set_focus(seat, focus); 244 seat_set_focus(seat, new_focus);
225 } 245 }
226 } 246 }
227 } 247 }
@@ -364,10 +384,18 @@ void container_move(struct sway_container *container,
364 struct sway_container *sibling = NULL; 384 struct sway_container *sibling = NULL;
365 struct sway_container *current = container; 385 struct sway_container *current = container;
366 struct sway_container *parent = current->parent; 386 struct sway_container *parent = current->parent;
387 struct sway_container *top = &root_container;
367 388
368 // If moving a fullscreen view, only consider outputs 389 // If moving a fullscreen view, only consider outputs
369 if (container->type == C_VIEW && container->sway_view->is_fullscreen) { 390 if (container->is_fullscreen) {
370 current = container_parent(container, C_OUTPUT); 391 current = container_parent(container, C_OUTPUT);
392 } else if (container_is_fullscreen_or_child(container) ||
393 container_is_floating_or_child(container)) {
394 // If we've fullscreened a split container, only allow the child to move
395 // around within the fullscreen parent.
396 // Same with floating a split container.
397 struct sway_container *ws = container_parent(container, C_WORKSPACE);
398 top = ws->sway_workspace->fullscreen;
371 } 399 }
372 400
373 struct sway_container *new_parent = container_flatten(parent); 401 struct sway_container *new_parent = container_flatten(parent);
@@ -377,7 +405,7 @@ void container_move(struct sway_container *container,
377 } 405 }
378 406
379 while (!sibling) { 407 while (!sibling) {
380 if (current->type == C_ROOT) { 408 if (current == top) {
381 return; 409 return;
382 } 410 }
383 411
@@ -441,8 +469,12 @@ void container_move(struct sway_container *container,
441 if ((index == parent->children->length - 1 && offs > 0) 469 if ((index == parent->children->length - 1 && offs > 0)
442 || (index == 0 && offs < 0)) { 470 || (index == 0 && offs < 0)) {
443 if (current->parent == container->parent) { 471 if (current->parent == container->parent) {
444 if (parent->layout == L_TABBED 472 if (parent->parent->layout == L_FLOATING) {
445 || parent->layout == L_STACKED) { 473 return;
474 }
475 if (!parent->is_fullscreen &&
476 (parent->layout == L_TABBED ||
477 parent->layout == L_STACKED)) {
446 move_out_of_tabs_stacks(container, current, 478 move_out_of_tabs_stacks(container, current,
447 move_dir, offs); 479 move_dir, offs);
448 return; 480 return;
@@ -463,10 +495,14 @@ void container_move(struct sway_container *container,
463 sibling = parent->children->items[index + offs]; 495 sibling = parent->children->items[index + offs];
464 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id); 496 wlr_log(WLR_DEBUG, "Selecting sibling id:%zd", sibling->id);
465 } 497 }
466 } else if (parent->layout == L_TABBED 498 } else if (!parent->is_fullscreen &&
467 || parent->layout == L_STACKED) { 499 parent->parent->layout != L_FLOATING &&
500 (parent->layout == L_TABBED ||
501 parent->layout == L_STACKED)) {
468 move_out_of_tabs_stacks(container, current, move_dir, offs); 502 move_out_of_tabs_stacks(container, current, move_dir, offs);
469 return; 503 return;
504 } else if (parent->parent->layout == L_FLOATING) {
505 return;
470 } else { 506 } else {
471 wlr_log(WLR_DEBUG, "Moving up to find a parallel container"); 507 wlr_log(WLR_DEBUG, "Moving up to find a parallel container");
472 current = current->parent; 508 current = current->parent;
@@ -544,6 +580,10 @@ void container_move(struct sway_container *container,
544 container_notify_subtree_changed(old_parent); 580 container_notify_subtree_changed(old_parent);
545 container_notify_subtree_changed(container->parent); 581 container_notify_subtree_changed(container->parent);
546 582
583 if (container->type == C_VIEW) {
584 ipc_event_window(container, "move");
585 }
586
547 if (old_parent) { 587 if (old_parent) {
548 seat_set_focus(config->handler_context.seat, old_parent); 588 seat_set_focus(config->handler_context.seat, old_parent);
549 seat_set_focus(config->handler_context.seat, container); 589 seat_set_focus(config->handler_context.seat, container);
@@ -558,10 +598,11 @@ void container_move(struct sway_container *container,
558 next_ws = container_parent(next_ws, C_WORKSPACE); 598 next_ws = container_parent(next_ws, C_WORKSPACE);
559 } 599 }
560 if (last_ws && next_ws && last_ws != next_ws) { 600 if (last_ws && next_ws && last_ws != next_ws) {
561 ipc_event_workspace(last_ws, container, "focus"); 601 ipc_event_workspace(last_ws, next_ws, "focus");
562 workspace_detect_urgent(last_ws); 602 workspace_detect_urgent(last_ws);
563 workspace_detect_urgent(next_ws); 603 workspace_detect_urgent(next_ws);
564 } 604 }
605 container_end_mouse_operation(container);
565} 606}
566 607
567enum sway_container_layout container_get_default_layout( 608enum sway_container_layout container_get_default_layout(
@@ -691,22 +732,18 @@ struct sway_container *container_get_in_direction(
691 enum movement_direction dir) { 732 enum movement_direction dir) {
692 struct sway_container *parent = container->parent; 733 struct sway_container *parent = container->parent;
693 734
694 if (container_is_floating(container)) { 735 if (dir == MOVE_CHILD) {
695 return NULL; 736 return seat_get_focus_inactive(seat, container);
696 } 737 }
697 738 if (container->is_fullscreen) {
698 if (container->type == C_VIEW && container->sway_view->is_fullscreen) { 739 if (dir == MOVE_PARENT) {
699 if (dir == MOVE_PARENT || dir == MOVE_CHILD) {
700 return NULL; 740 return NULL;
701 } 741 }
702 container = container_parent(container, C_OUTPUT); 742 container = container_parent(container, C_OUTPUT);
703 parent = container->parent; 743 parent = container->parent;
704 } else { 744 } else {
705 if (dir == MOVE_CHILD) {
706 return seat_get_focus_inactive(seat, container);
707 }
708 if (dir == MOVE_PARENT) { 745 if (dir == MOVE_PARENT) {
709 if (parent->type == C_OUTPUT) { 746 if (parent->type == C_OUTPUT || container_is_floating(container)) {
710 return NULL; 747 return NULL;
711 } else { 748 } else {
712 return parent; 749 return parent;
@@ -755,7 +792,8 @@ struct sway_container *container_get_in_direction(
755 } 792 }
756 sway_assert(next_workspace, "Next container has no workspace"); 793 sway_assert(next_workspace, "Next container has no workspace");
757 if (next_workspace->sway_workspace->fullscreen) { 794 if (next_workspace->sway_workspace->fullscreen) {
758 return next_workspace->sway_workspace->fullscreen->swayc; 795 return seat_get_focus_inactive(seat,
796 next_workspace->sway_workspace->fullscreen);
759 } 797 }
760 if (next->children && next->children->length) { 798 if (next->children && next->children->length) {
761 // TODO consider floating children as well 799 // TODO consider floating children as well
@@ -963,13 +1001,13 @@ static void swap_focus(struct sway_container *con1,
963 if (focus == con1 && (con2->parent->layout == L_TABBED 1001 if (focus == con1 && (con2->parent->layout == L_TABBED
964 || con2->parent->layout == L_STACKED)) { 1002 || con2->parent->layout == L_STACKED)) {
965 if (workspace_is_visible(ws2)) { 1003 if (workspace_is_visible(ws2)) {
966 seat_set_focus_warp(seat, con2, false); 1004 seat_set_focus_warp(seat, con2, false, true);
967 } 1005 }
968 seat_set_focus(seat, ws1 != ws2 ? con2 : con1); 1006 seat_set_focus(seat, ws1 != ws2 ? con2 : con1);
969 } else if (focus == con2 && (con1->parent->layout == L_TABBED 1007 } else if (focus == con2 && (con1->parent->layout == L_TABBED
970 || con1->parent->layout == L_STACKED)) { 1008 || con1->parent->layout == L_STACKED)) {
971 if (workspace_is_visible(ws1)) { 1009 if (workspace_is_visible(ws1)) {
972 seat_set_focus_warp(seat, con1, false); 1010 seat_set_focus_warp(seat, con1, false, true);
973 } 1011 }
974 seat_set_focus(seat, ws1 != ws2 ? con1 : con2); 1012 seat_set_focus(seat, ws1 != ws2 ? con1 : con2);
975 } else if (ws1 != ws2) { 1013 } else if (ws1 != ws2) {
@@ -1002,13 +1040,13 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) {
1002 1040
1003 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id); 1041 wlr_log(WLR_DEBUG, "Swapping containers %zu and %zu", con1->id, con2->id);
1004 1042
1005 int fs1 = con1->type == C_VIEW && con1->sway_view->is_fullscreen; 1043 int fs1 = con1->is_fullscreen;
1006 int fs2 = con2->type == C_VIEW && con2->sway_view->is_fullscreen; 1044 int fs2 = con2->is_fullscreen;
1007 if (fs1) { 1045 if (fs1) {
1008 view_set_fullscreen(con1->sway_view, false); 1046 container_set_fullscreen(con1, false);
1009 } 1047 }
1010 if (fs2) { 1048 if (fs2) {
1011 view_set_fullscreen(con2->sway_view, false); 1049 container_set_fullscreen(con2, false);
1012 } 1050 }
1013 1051
1014 struct sway_seat *seat = input_manager_get_default_seat(input_manager); 1052 struct sway_seat *seat = input_manager_get_default_seat(input_manager);
@@ -1041,10 +1079,10 @@ void container_swap(struct sway_container *con1, struct sway_container *con2) {
1041 prev_workspace_name = stored_prev_name; 1079 prev_workspace_name = stored_prev_name;
1042 } 1080 }
1043 1081
1044 if (fs1 && con2->type == C_VIEW) { 1082 if (fs1) {
1045 view_set_fullscreen(con2->sway_view, true); 1083 container_set_fullscreen(con2, true);
1046 } 1084 }
1047 if (fs2 && con1->type == C_VIEW) { 1085 if (fs2) {
1048 view_set_fullscreen(con1->sway_view, true); 1086 container_set_fullscreen(con1, true);
1049 } 1087 }
1050} 1088}
diff --git a/sway/tree/output.c b/sway/tree/output.c
index da535c18..31e3bf9b 100644
--- a/sway/tree/output.c
+++ b/sway/tree/output.c
@@ -22,7 +22,7 @@ static void restore_workspaces(struct sway_container *output) {
22 if (highest == output) { 22 if (highest == output) {
23 container_remove_child(ws); 23 container_remove_child(ws);
24 container_add_child(output, ws); 24 container_add_child(output, ws);
25 ipc_event_workspace(ws, NULL, "move"); 25 ipc_event_workspace(NULL, ws, "move");
26 j--; 26 j--;
27 } 27 }
28 } 28 }
diff --git a/sway/tree/view.c b/sway/tree/view.c
index 7881e6d7..97318daa 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -2,7 +2,12 @@
2#include <stdlib.h> 2#include <stdlib.h>
3#include <wayland-server.h> 3#include <wayland-server.h>
4#include <wlr/render/wlr_renderer.h> 4#include <wlr/render/wlr_renderer.h>
5#include <wlr/types/wlr_buffer.h>
5#include <wlr/types/wlr_output_layout.h> 6#include <wlr/types/wlr_output_layout.h>
7#include "config.h"
8#ifdef HAVE_XWAYLAND
9#include <wlr/xwayland.h>
10#endif
6#include "list.h" 11#include "list.h"
7#include "log.h" 12#include "log.h"
8#include "sway/criteria.h" 13#include "sway/criteria.h"
@@ -107,14 +112,14 @@ const char *view_get_instance(struct sway_view *view) {
107 } 112 }
108 return NULL; 113 return NULL;
109} 114}
110 115#ifdef HAVE_XWAYLAND
111uint32_t view_get_x11_window_id(struct sway_view *view) { 116uint32_t view_get_x11_window_id(struct sway_view *view) {
112 if (view->impl->get_int_prop) { 117 if (view->impl->get_int_prop) {
113 return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); 118 return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID);
114 } 119 }
115 return 0; 120 return 0;
116} 121}
117 122#endif
118const char *view_get_window_role(struct sway_view *view) { 123const char *view_get_window_role(struct sway_view *view) {
119 if (view->impl->get_string_prop) { 124 if (view->impl->get_string_prop) {
120 return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE); 125 return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE);
@@ -135,12 +140,27 @@ const char *view_get_shell(struct sway_view *view) {
135 return "xdg_shell_v6"; 140 return "xdg_shell_v6";
136 case SWAY_VIEW_XDG_SHELL: 141 case SWAY_VIEW_XDG_SHELL:
137 return "xdg_shell"; 142 return "xdg_shell";
143#ifdef HAVE_XWAYLAND
138 case SWAY_VIEW_XWAYLAND: 144 case SWAY_VIEW_XWAYLAND:
139 return "xwayland"; 145 return "xwayland";
146#endif
140 } 147 }
141 return "unknown"; 148 return "unknown";
142} 149}
143 150
151void view_get_constraints(struct sway_view *view, double *min_width,
152 double *max_width, double *min_height, double *max_height) {
153 if (view->impl->get_constraints) {
154 view->impl->get_constraints(view,
155 min_width, max_width, min_height, max_height);
156 } else {
157 *min_width = DBL_MIN;
158 *max_width = DBL_MAX;
159 *min_height = DBL_MIN;
160 *max_height = DBL_MAX;
161 }
162}
163
144uint32_t view_configure(struct sway_view *view, double lx, double ly, int width, 164uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
145 int height) { 165 int height) {
146 if (view->impl->configure) { 166 if (view->impl->configure) {
@@ -149,55 +169,6 @@ uint32_t view_configure(struct sway_view *view, double lx, double ly, int width,
149 return 0; 169 return 0;
150} 170}
151 171
152void view_init_floating(struct sway_view *view) {
153 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
154 int min_width, min_height;
155 int max_width, max_height;
156
157 if (config->floating_minimum_width == -1) { // no minimum
158 min_width = 0;
159 } else if (config->floating_minimum_width == 0) { // automatic
160 min_width = 75;
161 } else {
162 min_width = config->floating_minimum_width;
163 }
164
165 if (config->floating_minimum_height == -1) { // no minimum
166 min_height = 0;
167 } else if (config->floating_minimum_height == 0) { // automatic
168 min_height = 50;
169 } else {
170 min_height = config->floating_minimum_height;
171 }
172
173 if (config->floating_maximum_width == -1) { // no maximum
174 max_width = INT_MAX;
175 } else if (config->floating_maximum_width == 0) { // automatic
176 max_width = ws->width * 0.6666;
177 } else {
178 max_width = config->floating_maximum_width;
179 }
180
181 if (config->floating_maximum_height == -1) { // no maximum
182 max_height = INT_MAX;
183 } else if (config->floating_maximum_height == 0) { // automatic
184 max_height = ws->height * 0.6666;
185 } else {
186 max_height = config->floating_maximum_height;
187 }
188
189 view->width = fmax(min_width, fmin(view->natural_width, max_width));
190 view->height = fmax(min_height, fmin(view->natural_height, max_height));
191 view->x = ws->x + (ws->width - view->width) / 2;
192 view->y = ws->y + (ws->height - view->height) / 2;
193
194 // If the view's border is B_NONE then these properties are ignored.
195 view->border_top = view->border_bottom = true;
196 view->border_left = view->border_right = true;
197
198 container_set_geometry_from_floating_view(view->swayc);
199}
200
201void view_autoconfigure(struct sway_view *view) { 172void view_autoconfigure(struct sway_view *view) {
202 if (!sway_assert(view->swayc, 173 if (!sway_assert(view->swayc,
203 "Called view_autoconfigure() on a view without a swayc")) { 174 "Called view_autoconfigure() on a view without a swayc")) {
@@ -206,7 +177,7 @@ void view_autoconfigure(struct sway_view *view) {
206 177
207 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 178 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
208 179
209 if (view->is_fullscreen) { 180 if (view->swayc->is_fullscreen) {
210 view->x = output->x; 181 view->x = output->x;
211 view->y = output->y; 182 view->y = output->y;
212 view->width = output->width; 183 view->width = output->width;
@@ -214,10 +185,6 @@ void view_autoconfigure(struct sway_view *view) {
214 return; 185 return;
215 } 186 }
216 187
217 if (container_is_floating(view->swayc)) {
218 return;
219 }
220
221 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 188 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
222 189
223 int other_views = 0; 190 int other_views = 0;
@@ -330,72 +297,18 @@ void view_set_tiled(struct sway_view *view, bool tiled) {
330 } 297 }
331} 298}
332 299
333void view_set_fullscreen(struct sway_view *view, bool fullscreen) {
334 if (view->is_fullscreen == fullscreen) {
335 return;
336 }
337
338 struct sway_container *workspace =
339 container_parent(view->swayc, C_WORKSPACE);
340
341 if (view->impl->set_fullscreen) {
342 view->impl->set_fullscreen(view, fullscreen);
343 }
344
345 view->is_fullscreen = fullscreen;
346
347 if (fullscreen) {
348 if (workspace->sway_workspace->fullscreen) {
349 view_set_fullscreen(workspace->sway_workspace->fullscreen, false);
350 }
351 workspace->sway_workspace->fullscreen = view;
352 view->saved_x = view->x;
353 view->saved_y = view->y;
354 view->saved_width = view->width;
355 view->saved_height = view->height;
356 view->swayc->saved_x = view->swayc->x;
357 view->swayc->saved_y = view->swayc->y;
358 view->swayc->saved_width = view->swayc->width;
359 view->swayc->saved_height = view->swayc->height;
360
361 struct sway_seat *seat;
362 struct sway_container *focus, *focus_ws;
363 wl_list_for_each(seat, &input_manager->seats, link) {
364 focus = seat_get_focus(seat);
365 if (focus) {
366 focus_ws = focus;
367 if (focus && focus_ws->type != C_WORKSPACE) {
368 focus_ws = container_parent(focus_ws, C_WORKSPACE);
369 }
370 seat_set_focus(seat, view->swayc);
371 if (focus_ws != workspace) {
372 seat_set_focus(seat, focus);
373 }
374 }
375 }
376 } else {
377 workspace->sway_workspace->fullscreen = NULL;
378 if (container_is_floating(view->swayc)) {
379 view->x = view->saved_x;
380 view->y = view->saved_y;
381 view->width = view->saved_width;
382 view->height = view->saved_height;
383 container_set_geometry_from_floating_view(view->swayc);
384 } else {
385 view->swayc->width = view->swayc->saved_width;
386 view->swayc->height = view->swayc->saved_height;
387 }
388 }
389
390 ipc_event_window(view->swayc, "fullscreen_mode");
391}
392
393void view_close(struct sway_view *view) { 300void view_close(struct sway_view *view) {
394 if (view->impl->close) { 301 if (view->impl->close) {
395 view->impl->close(view); 302 view->impl->close(view);
396 } 303 }
397} 304}
398 305
306void view_close_popups(struct sway_view *view) {
307 if (view->impl->close_popups) {
308 view->impl->close_popups(view);
309 }
310}
311
399void view_damage_from(struct sway_view *view) { 312void view_damage_from(struct sway_view *view) {
400 for (int i = 0; i < root_container.children->length; ++i) { 313 for (int i = 0; i < root_container.children->length; ++i) {
401 struct sway_container *cont = root_container.children->items[i]; 314 struct sway_container *cont = root_container.children->items[i];
@@ -426,6 +339,16 @@ void view_for_each_surface(struct sway_view *view,
426 } 339 }
427} 340}
428 341
342void view_for_each_popup(struct sway_view *view,
343 wlr_surface_iterator_func_t iterator, void *user_data) {
344 if (!view->surface) {
345 return;
346 }
347 if (view->impl->for_each_popup) {
348 view->impl->for_each_popup(view, iterator, user_data);
349 }
350}
351
429static void view_subsurface_create(struct sway_view *view, 352static void view_subsurface_create(struct sway_view *view,
430 struct wlr_subsurface *subsurface); 353 struct wlr_subsurface *subsurface);
431 354
@@ -521,12 +444,82 @@ void view_execute_criteria(struct sway_view *view) {
521 seat_set_focus(seat, prior_focus); 444 seat_set_focus(seat, prior_focus);
522} 445}
523 446
447static struct sway_container *select_workspace(struct sway_view *view) {
448 struct sway_seat *seat = input_manager_current_seat(input_manager);
449
450 // Check if there's any `assign` criteria for the view
451 list_t *criterias = criteria_for_view(view,
452 CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT);
453 struct sway_container *ws = NULL;
454 for (int i = 0; i < criterias->length; ++i) {
455 struct criteria *criteria = criterias->items[i];
456 if (criteria->type == CT_ASSIGN_WORKSPACE) {
457 ws = workspace_by_name(criteria->target);
458 if (!ws) {
459 ws = workspace_create(NULL, criteria->target);
460 }
461 break;
462 } else {
463 // CT_ASSIGN_OUTPUT
464 struct sway_container *output = output_by_name(criteria->target);
465 if (output) {
466 ws = seat_get_active_child(seat, output);
467 break;
468 }
469 }
470 }
471 list_free(criterias);
472 if (ws) {
473 return ws;
474 }
475
476 // Check if there's a PID mapping
477 pid_t pid;
478#ifdef HAVE_XWAYLAND
479 if (view->type == SWAY_VIEW_XWAYLAND) {
480 struct wlr_xwayland_surface *surf =
481 wlr_xwayland_surface_from_wlr_surface(view->surface);
482 pid = surf->pid;
483 } else {
484 struct wl_client *client =
485 wl_resource_get_client(view->surface->resource);
486 wl_client_get_credentials(client, &pid, NULL, NULL);
487 }
488#else
489 struct wl_client *client =
490 wl_resource_get_client(view->surface->resource);
491 wl_client_get_credentials(client, &pid, NULL, NULL);
492#endif
493 ws = workspace_for_pid(pid);
494 if (ws) {
495 return ws;
496 }
497
498 // Use the focused workspace
499 ws = seat_get_focus_inactive(seat, &root_container);
500 if (ws->type != C_WORKSPACE) {
501 ws = container_parent(ws, C_WORKSPACE);
502 }
503 return ws;
504}
505
524static bool should_focus(struct sway_view *view) { 506static bool should_focus(struct sway_view *view) {
507 struct sway_seat *seat = input_manager_current_seat(input_manager);
508 struct sway_container *prev_focus =
509 seat_get_focus_inactive(seat, &root_container);
510 struct sway_container *prev_ws = prev_focus->type == C_WORKSPACE ?
511 prev_focus : container_parent(prev_focus, C_WORKSPACE);
512 struct sway_container *map_ws = container_parent(view->swayc, C_WORKSPACE);
513
514 // Views can only take focus if they are mapped into the active workspace
515 if (prev_ws != map_ws) {
516 return false;
517 }
518
525 // If the view is the only one in the focused workspace, it'll get focus 519 // If the view is the only one in the focused workspace, it'll get focus
526 // regardless of any no_focus criteria. 520 // regardless of any no_focus criteria.
527 struct sway_container *parent = view->swayc->parent; 521 struct sway_container *parent = view->swayc->parent;
528 struct sway_seat *seat = input_manager_current_seat(input_manager); 522 if (parent->type == C_WORKSPACE && prev_focus == parent) {
529 if (parent->type == C_WORKSPACE && seat_get_focus(seat) == parent) {
530 size_t num_children = parent->children->length + 523 size_t num_children = parent->children->length +
531 parent->sway_workspace->floating->children->length; 524 parent->sway_workspace->floating->children->length;
532 if (num_children == 1) { 525 if (num_children == 1) {
@@ -545,42 +538,19 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
545 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) { 538 if (!sway_assert(view->surface == NULL, "cannot map mapped view")) {
546 return; 539 return;
547 } 540 }
541 view->surface = wlr_surface;
548 542
549 struct sway_seat *seat = input_manager_current_seat(input_manager); 543 struct sway_seat *seat = input_manager_current_seat(input_manager);
550 struct sway_container *focus = 544 struct sway_container *ws = select_workspace(view);
551 seat_get_focus_inactive(seat, &root_container); 545 struct sway_container *target_sibling = seat_get_focus_inactive(seat, ws);
552 struct sway_container *cont = NULL;
553 546
554 // Check if there's any `assign` criteria for the view
555 list_t *criterias = criteria_for_view(view,
556 CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT);
557 struct sway_container *workspace = NULL;
558 if (criterias->length) {
559 struct criteria *criteria = criterias->items[0];
560 if (criteria->type == CT_ASSIGN_WORKSPACE) {
561 workspace = workspace_by_name(criteria->target);
562 if (!workspace) {
563 workspace = workspace_create(NULL, criteria->target);
564 }
565 focus = seat_get_focus_inactive(seat, workspace);
566 } else {
567 // CT_ASSIGN_OUTPUT
568 struct sway_container *output = output_by_name(criteria->target);
569 if (output) {
570 focus = seat_get_focus_inactive(seat, output);
571 }
572 }
573 }
574 // If we're about to launch the view into the floating container, then 547 // If we're about to launch the view into the floating container, then
575 // launch it as a tiled view in the root of the workspace instead. 548 // launch it as a tiled view in the root of the workspace instead.
576 if (container_is_floating(focus)) { 549 if (container_is_floating(target_sibling)) {
577 focus = focus->parent->parent; 550 target_sibling = target_sibling->parent->parent;
578 } 551 }
579 list_free(criterias);
580 cont = container_view_create(focus, view);
581 552
582 view->surface = wlr_surface; 553 view->swayc = container_view_create(target_sibling, view);
583 view->swayc = cont;
584 554
585 view_init_subsurfaces(view, wlr_surface); 555 view_init_subsurfaces(view, wlr_surface);
586 wl_signal_add(&wlr_surface->events.new_subsurface, 556 wl_signal_add(&wlr_surface->events.new_subsurface,
@@ -601,10 +571,7 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
601 } 571 }
602 572
603 if (should_focus(view)) { 573 if (should_focus(view)) {
604 input_manager_set_focus(input_manager, cont); 574 input_manager_set_focus(input_manager, view->swayc);
605 if (workspace) {
606 workspace_switch(workspace);
607 }
608 } 575 }
609 576
610 view_update_title(view, false); 577 view_update_title(view, false);
@@ -628,10 +595,8 @@ void view_unmap(struct sway_view *view) {
628 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); 595 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
629 596
630 struct sway_container *parent; 597 struct sway_container *parent;
631 if (view->is_fullscreen) { 598 if (container_is_fullscreen_or_child(view->swayc)) {
632 ws->sway_workspace->fullscreen = NULL;
633 parent = container_destroy(view->swayc); 599 parent = container_destroy(view->swayc);
634
635 arrange_windows(ws->parent); 600 arrange_windows(ws->parent);
636 } else { 601 } else {
637 parent = container_destroy(view->swayc); 602 parent = container_destroy(view->swayc);
@@ -784,11 +749,13 @@ struct sway_view *view_from_wlr_surface(struct wlr_surface *wlr_surface) {
784 wlr_xdg_surface_v6_from_wlr_surface(wlr_surface); 749 wlr_xdg_surface_v6_from_wlr_surface(wlr_surface);
785 return view_from_wlr_xdg_surface_v6(xdg_surface_v6); 750 return view_from_wlr_xdg_surface_v6(xdg_surface_v6);
786 } 751 }
752#ifdef HAVE_XWAYLAND
787 if (wlr_surface_is_xwayland_surface(wlr_surface)) { 753 if (wlr_surface_is_xwayland_surface(wlr_surface)) {
788 struct wlr_xwayland_surface *xsurface = 754 struct wlr_xwayland_surface *xsurface =
789 wlr_xwayland_surface_from_wlr_surface(wlr_surface); 755 wlr_xwayland_surface_from_wlr_surface(wlr_surface);
790 return view_from_wlr_xwayland_surface(xsurface); 756 return view_from_wlr_xwayland_surface(xsurface);
791 } 757 }
758#endif
792 if (wlr_surface_is_subsurface(wlr_surface)) { 759 if (wlr_surface_is_subsurface(wlr_surface)) {
793 struct wlr_subsurface *subsurface = 760 struct wlr_subsurface *subsurface =
794 wlr_subsurface_from_wlr_surface(wlr_surface); 761 wlr_subsurface_from_wlr_surface(wlr_surface);
@@ -915,6 +882,8 @@ void view_update_title(struct sway_view *view, bool force) {
915 882
916 // Update title after the global font height is updated 883 // Update title after the global font height is updated
917 container_update_title_textures(view->swayc); 884 container_update_title_textures(view->swayc);
885
886 ipc_event_window(view->swayc, "title");
918} 887}
919 888
920static bool find_by_mark_iterator(struct sway_container *con, 889static bool find_by_mark_iterator(struct sway_container *con,
@@ -937,6 +906,7 @@ bool view_find_and_unmark(char *mark) {
937 free(view_mark); 906 free(view_mark);
938 list_del(view->marks, i); 907 list_del(view->marks, i);
939 view_update_marks_textures(view); 908 view_update_marks_textures(view);
909 ipc_event_window(container, "mark");
940 return true; 910 return true;
941 } 911 }
942 } 912 }
@@ -944,11 +914,10 @@ bool view_find_and_unmark(char *mark) {
944} 914}
945 915
946void view_clear_marks(struct sway_view *view) { 916void view_clear_marks(struct sway_view *view) {
947 for (int i = 0; i < view->marks->length; ++i) { 917 while (view->marks->length) {
948 free(view->marks->items[i]); 918 list_del(view->marks, 0);
919 ipc_event_window(view->swayc, "mark");
949 } 920 }
950 list_free(view->marks);
951 view->marks = create_list();
952} 921}
953 922
954bool view_has_mark(struct sway_view *view, char *mark) { 923bool view_has_mark(struct sway_view *view, char *mark) {
@@ -961,6 +930,11 @@ bool view_has_mark(struct sway_view *view, char *mark) {
961 return false; 930 return false;
962} 931}
963 932
933void view_add_mark(struct sway_view *view, char *mark) {
934 list_add(view->marks, strdup(mark));
935 ipc_event_window(view->swayc, "mark");
936}
937
964static void update_marks_texture(struct sway_view *view, 938static void update_marks_texture(struct sway_view *view,
965 struct wlr_texture **texture, struct border_colors *class) { 939 struct wlr_texture **texture, struct border_colors *class) {
966 struct sway_container *output = container_parent(view->swayc, C_OUTPUT); 940 struct sway_container *output = container_parent(view->swayc, C_OUTPUT);
@@ -1055,6 +1029,9 @@ bool view_is_visible(struct sway_view *view) {
1055 } 1029 }
1056 struct sway_container *workspace = 1030 struct sway_container *workspace =
1057 container_parent(view->swayc, C_WORKSPACE); 1031 container_parent(view->swayc, C_WORKSPACE);
1032 if (!workspace) {
1033 return false;
1034 }
1058 // Determine if view is nested inside a floating container which is sticky. 1035 // Determine if view is nested inside a floating container which is sticky.
1059 // A simple floating view will have this ancestry: 1036 // A simple floating view will have this ancestry:
1060 // C_VIEW -> floating -> workspace 1037 // C_VIEW -> floating -> workspace
@@ -1079,7 +1056,8 @@ bool view_is_visible(struct sway_view *view) {
1079 container = container->parent; 1056 container = container->parent;
1080 } 1057 }
1081 // Check view isn't hidden by another fullscreen view 1058 // Check view isn't hidden by another fullscreen view
1082 if (workspace->sway_workspace->fullscreen && !view->is_fullscreen) { 1059 if (workspace->sway_workspace->fullscreen &&
1060 !container_is_fullscreen_or_child(view->swayc)) {
1083 return false; 1061 return false;
1084 } 1062 }
1085 // Check the workspace is visible 1063 // Check the workspace is visible
@@ -1117,3 +1095,22 @@ void view_set_urgent(struct sway_view *view, bool enable) {
1117bool view_is_urgent(struct sway_view *view) { 1095bool view_is_urgent(struct sway_view *view) {
1118 return view->urgent.tv_sec || view->urgent.tv_nsec; 1096 return view->urgent.tv_sec || view->urgent.tv_nsec;
1119} 1097}
1098
1099void view_remove_saved_buffer(struct sway_view *view) {
1100 if (!sway_assert(view->saved_buffer, "Expected a saved buffer")) {
1101 return;
1102 }
1103 wlr_buffer_unref(view->saved_buffer);
1104 view->saved_buffer = NULL;
1105}
1106
1107void view_save_buffer(struct sway_view *view) {
1108 if (!sway_assert(!view->saved_buffer, "Didn't expect saved buffer")) {
1109 view_remove_saved_buffer(view);
1110 }
1111 if (view->surface && wlr_surface_has_buffer(view->surface)) {
1112 view->saved_buffer = wlr_buffer_ref(view->surface->buffer);
1113 view->saved_buffer_width = view->surface->current.width;
1114 view->saved_buffer_height = view->surface->current.height;
1115 }
1116}
diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c
index 622f01ec..588e2aae 100644
--- a/sway/tree/workspace.c
+++ b/sway/tree/workspace.c
@@ -9,6 +9,7 @@
9#include "sway/input/input-manager.h" 9#include "sway/input/input-manager.h"
10#include "sway/input/seat.h" 10#include "sway/input/seat.h"
11#include "sway/ipc-server.h" 11#include "sway/ipc-server.h"
12#include "sway/output.h"
12#include "sway/tree/arrange.h" 13#include "sway/tree/arrange.h"
13#include "sway/tree/container.h" 14#include "sway/tree/container.h"
14#include "sway/tree/view.h" 15#include "sway/tree/view.h"
@@ -107,96 +108,100 @@ static bool workspace_valid_on_output(const char *output_name,
107 return true; 108 return true;
108} 109}
109 110
110char *workspace_next_name(const char *output_name) { 111static void workspace_name_from_binding(const struct sway_binding * binding,
111 wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s", 112 const char* output_name, int *min_order, char **earliest_name) {
112 output_name); 113 char *cmdlist = strdup(binding->command);
113 // Scan all workspace bindings to find the next available workspace name, 114 char *dup = cmdlist;
114 // if none are found/available then default to a number 115 char *name = NULL;
115 struct sway_mode *mode = config->current_mode;
116 116
117 // TODO: iterate over keycode bindings too 117 // workspace n
118 int order = INT_MAX; 118 char *cmd = argsep(&cmdlist, " ");
119 char *target = NULL; 119 if (cmdlist) {
120 for (int i = 0; i < mode->keysym_bindings->length; ++i) { 120 name = argsep(&cmdlist, ",;");
121 struct sway_binding *binding = mode->keysym_bindings->items[i]; 121 }
122 char *cmdlist = strdup(binding->command);
123 char *dup = cmdlist;
124 char *name = NULL;
125
126 // workspace n
127 char *cmd = argsep(&cmdlist, " ");
128 if (cmdlist) {
129 name = argsep(&cmdlist, ",;");
130 }
131 122
132 if (strcmp("workspace", cmd) == 0 && name) { 123 if (strcmp("workspace", cmd) == 0 && name) {
133 char *_target = strdup(name); 124 char *_target = strdup(name);
134 _target = do_var_replacement(_target); 125 _target = do_var_replacement(_target);
135 strip_quotes(_target); 126 strip_quotes(_target);
136 while (isspace(*_target)) { 127 wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'",
137 memmove(_target, _target+1, strlen(_target+1)); 128 _target);
138 }
139 wlr_log(WLR_DEBUG, "Got valid workspace command for target: '%s'",
140 _target);
141 129
142 // Make sure that the command references an actual workspace 130 // Make sure that the command references an actual workspace
143 // not a command about workspaces 131 // not a command about workspaces
144 if (strcmp(_target, "next") == 0 || 132 if (strcmp(_target, "next") == 0 ||
145 strcmp(_target, "prev") == 0 || 133 strcmp(_target, "prev") == 0 ||
146 strcmp(_target, "next_on_output") == 0 || 134 strcmp(_target, "next_on_output") == 0 ||
147 strcmp(_target, "prev_on_output") == 0 || 135 strcmp(_target, "prev_on_output") == 0 ||
148 strcmp(_target, "number") == 0 || 136 strcmp(_target, "number") == 0 ||
149 strcmp(_target, "back_and_forth") == 0 || 137 strcmp(_target, "back_and_forth") == 0 ||
150 strcmp(_target, "current") == 0) 138 strcmp(_target, "current") == 0) {
151 { 139 free(_target);
152 free(_target); 140 free(dup);
153 free(dup); 141 return;
154 continue; 142 }
155 }
156
157 // If the command is workspace number <name>, isolate the name
158 if (strncmp(_target, "number ", strlen("number ")) == 0) {
159 size_t length = strlen(_target) - strlen("number ") + 1;
160 char *temp = malloc(length);
161 strncpy(temp, _target + strlen("number "), length - 1);
162 temp[length - 1] = '\0';
163 free(_target);
164 _target = temp;
165 wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target);
166
167 // Make sure the workspace number doesn't already exist
168 if (workspace_by_number(_target)) {
169 free(_target);
170 free(dup);
171 continue;
172 }
173 }
174 143
175 // Make sure that the workspace doesn't already exist 144 // If the command is workspace number <name>, isolate the name
176 if (workspace_by_name(_target)) { 145 if (strncmp(_target, "number ", strlen("number ")) == 0) {
146 size_t length = strlen(_target) - strlen("number ") + 1;
147 char *temp = malloc(length);
148 strncpy(temp, _target + strlen("number "), length - 1);
149 temp[length - 1] = '\0';
150 free(_target);
151 _target = temp;
152 wlr_log(WLR_DEBUG, "Isolated name from workspace number: '%s'", _target);
153
154 // Make sure the workspace number doesn't already exist
155 if (workspace_by_number(_target)) {
177 free(_target); 156 free(_target);
178 free(dup); 157 free(dup);
179 continue; 158 return;
180 } 159 }
160 }
181 161
182 // make sure that the workspace can appear on the given 162 // Make sure that the workspace doesn't already exist
183 // output 163 if (workspace_by_name(_target)) {
184 if (!workspace_valid_on_output(output_name, _target)) { 164 free(_target);
185 free(_target); 165 free(dup);
186 free(dup); 166 return;
187 continue; 167 }
188 }
189 168
190 if (binding->order < order) { 169 // make sure that the workspace can appear on the given
191 order = binding->order; 170 // output
192 free(target); 171 if (!workspace_valid_on_output(output_name, _target)) {
193 target = _target; 172 free(_target);
194 wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target); 173 free(dup);
195 } else { 174 return;
196 free(_target);
197 }
198 } 175 }
199 free(dup); 176
177 if (binding->order < *min_order) {
178 *min_order = binding->order;
179 free(*earliest_name);
180 *earliest_name = _target;
181 wlr_log(WLR_DEBUG, "Workspace: Found free name %s", _target);
182 } else {
183 free(_target);
184 }
185 }
186 free(dup);
187}
188
189char *workspace_next_name(const char *output_name) {
190 wlr_log(WLR_DEBUG, "Workspace: Generating new workspace name for output %s",
191 output_name);
192 // Scan all workspace bindings to find the next available workspace name,
193 // if none are found/available then default to a number
194 struct sway_mode *mode = config->current_mode;
195
196 int order = INT_MAX;
197 char *target = NULL;
198 for (int i = 0; i < mode->keysym_bindings->length; ++i) {
199 workspace_name_from_binding(mode->keysym_bindings->items[i],
200 output_name, &order, &target);
201 }
202 for (int i = 0; i < mode->keycode_bindings->length; ++i) {
203 workspace_name_from_binding(mode->keycode_bindings->items[i],
204 output_name, &order, &target);
200 } 205 }
201 if (target != NULL) { 206 if (target != NULL) {
202 return target; 207 return target;
@@ -529,3 +534,116 @@ void workspace_detect_urgent(struct sway_container *workspace) {
529 container_damage_whole(workspace); 534 container_damage_whole(workspace);
530 } 535 }
531} 536}
537
538struct pid_workspace {
539 pid_t pid;
540 char *workspace;
541 struct timespec time_added;
542
543 struct sway_container *output;
544 struct wl_listener output_destroy;
545
546 struct wl_list link;
547};
548
549static struct wl_list pid_workspaces;
550
551struct sway_container *workspace_for_pid(pid_t pid) {
552 if (!pid_workspaces.prev && !pid_workspaces.next) {
553 wl_list_init(&pid_workspaces);
554 return NULL;
555 }
556
557 struct sway_container *ws = NULL;
558 struct pid_workspace *pw = NULL;
559
560 wlr_log(WLR_DEBUG, "Looking up workspace for pid %d", pid);
561
562 do {
563 struct pid_workspace *_pw = NULL;
564 wl_list_for_each(_pw, &pid_workspaces, link) {
565 if (pid == _pw->pid) {
566 pw = _pw;
567 wlr_log(WLR_DEBUG,
568 "found pid_workspace for pid %d, workspace %s",
569 pid, pw->workspace);
570 goto found;
571 }
572 }
573 pid = get_parent_pid(pid);
574 } while (pid > 1);
575found:
576
577 if (pw && pw->workspace) {
578 ws = workspace_by_name(pw->workspace);
579
580 if (!ws) {
581 wlr_log(WLR_DEBUG,
582 "Creating workspace %s for pid %d because it disappeared",
583 pw->workspace, pid);
584 ws = workspace_create(pw->output, pw->workspace);
585 }
586
587 wl_list_remove(&pw->output_destroy.link);
588 wl_list_remove(&pw->link);
589 free(pw->workspace);
590 free(pw);
591 }
592
593 return ws;
594}
595
596static void pw_handle_output_destroy(struct wl_listener *listener, void *data) {
597 struct pid_workspace *pw = wl_container_of(listener, pw, output_destroy);
598 pw->output = NULL;
599 wl_list_remove(&pw->output_destroy.link);
600 wl_list_init(&pw->output_destroy.link);
601}
602
603void workspace_record_pid(pid_t pid) {
604 wlr_log(WLR_DEBUG, "Recording workspace for process %d", pid);
605 if (!pid_workspaces.prev && !pid_workspaces.next) {
606 wl_list_init(&pid_workspaces);
607 }
608
609 struct sway_seat *seat = input_manager_current_seat(input_manager);
610 struct sway_container *ws =
611 seat_get_focus_inactive(seat, &root_container);
612 if (ws && ws->type != C_WORKSPACE) {
613 ws = container_parent(ws, C_WORKSPACE);
614 }
615 if (!ws) {
616 wlr_log(WLR_DEBUG, "Bailing out, no workspace");
617 return;
618 }
619 struct sway_container *output = ws->parent;
620 if (!output) {
621 wlr_log(WLR_DEBUG, "Bailing out, no output");
622 return;
623 }
624
625 struct timespec now;
626 clock_gettime(CLOCK_MONOTONIC, &now);
627
628 // Remove expired entries
629 static const int timeout = 60;
630 struct pid_workspace *old, *_old;
631 wl_list_for_each_safe(old, _old, &pid_workspaces, link) {
632 if (now.tv_sec - old->time_added.tv_sec >= timeout) {
633 wl_list_remove(&old->output_destroy.link);
634 wl_list_remove(&old->link);
635 free(old->workspace);
636 free(old);
637 }
638 }
639
640 struct pid_workspace *pw = calloc(1, sizeof(struct pid_workspace));
641 pw->workspace = strdup(ws->name);
642 pw->output = output;
643 pw->pid = pid;
644 memcpy(&pw->time_added, &now, sizeof(struct timespec));
645 pw->output_destroy.notify = pw_handle_output_destroy;
646 wl_signal_add(&output->sway_output->wlr_output->events.destroy,
647 &pw->output_destroy);
648 wl_list_insert(&pid_workspaces, &pw->link);
649}
diff --git a/swayidle/swayidle.1.scd b/swayidle/swayidle.1.scd
index 5cd4a7fd..7c1b138a 100644
--- a/swayidle/swayidle.1.scd
+++ b/swayidle/swayidle.1.scd
@@ -58,4 +58,4 @@ https://github.com/swaywm/sway.
58 58
59# SEE ALSO 59# SEE ALSO
60 60
61*sway*(5) *swaymsg*(1) *swaygrab*(1) *sway-input*(5) *sway-bar*(5) 61*sway*(5) *swaymsg*(1) *sway-input*(5) *sway-bar*(5)
diff --git a/swaymsg/main.c b/swaymsg/main.c
index c4141ca5..3767daf3 100644
--- a/swaymsg/main.c
+++ b/swaymsg/main.c
@@ -250,12 +250,16 @@ static void pretty_print(int type, json_object *resp) {
250 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES && 250 if (type != IPC_COMMAND && type != IPC_GET_WORKSPACES &&
251 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS && 251 type != IPC_GET_INPUTS && type != IPC_GET_OUTPUTS &&
252 type != IPC_GET_VERSION && type != IPC_GET_SEATS && 252 type != IPC_GET_VERSION && type != IPC_GET_SEATS &&
253 type != IPC_GET_CONFIG) { 253 type != IPC_GET_CONFIG && type != IPC_SEND_TICK) {
254 printf("%s\n", json_object_to_json_string_ext(resp, 254 printf("%s\n", json_object_to_json_string_ext(resp,
255 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED)); 255 JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED));
256 return; 256 return;
257 } 257 }
258 258
259 if (type == IPC_SEND_TICK) {
260 return;
261 }
262
259 if (type == IPC_GET_VERSION) { 263 if (type == IPC_GET_VERSION) {
260 pretty_print_version(resp); 264 pretty_print_version(resp);
261 return; 265 return;
@@ -384,6 +388,8 @@ int main(int argc, char **argv) {
384 type = IPC_GET_BINDING_MODES; 388 type = IPC_GET_BINDING_MODES;
385 } else if (strcasecmp(cmdtype, "get_config") == 0) { 389 } else if (strcasecmp(cmdtype, "get_config") == 0) {
386 type = IPC_GET_CONFIG; 390 type = IPC_GET_CONFIG;
391 } else if (strcasecmp(cmdtype, "send_tick") == 0) {
392 type = IPC_SEND_TICK;
387 } else { 393 } else {
388 sway_abort("Unknown message type %s", cmdtype); 394 sway_abort("Unknown message type %s", cmdtype);
389 } 395 }
diff --git a/swaymsg/swaymsg.1.scd b/swaymsg/swaymsg.1.scd
index a6e279da..8cf1b222 100644
--- a/swaymsg/swaymsg.1.scd
+++ b/swaymsg/swaymsg.1.scd
@@ -64,3 +64,6 @@ _swaymsg_ [options...] [message]
64 64
65*get\_config* 65*get\_config*
66 Gets a JSON-encoded copy of the current configuration. 66 Gets a JSON-encoded copy of the current configuration.
67
68*send\_tick*
69 Sends a tick event to all subscribed clients.
diff --git a/swaynag/config.c b/swaynag/config.c
new file mode 100644
index 00000000..d6c5739d
--- /dev/null
+++ b/swaynag/config.c
@@ -0,0 +1,401 @@
1#define _XOPEN_SOURCE 700
2#define _POSIX_C_SOURCE 200112L
3#include <getopt.h>
4#include <stdlib.h>
5#include <wordexp.h>
6#include "log.h"
7#include "list.h"
8#include "readline.h"
9#include "swaynag/swaynag.h"
10#include "swaynag/types.h"
11#include "util.h"
12#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13
14static char *read_from_stdin() {
15 char *buffer = NULL;
16 while (!feof(stdin)) {
17 char *line = read_line(stdin);
18 if (!line) {
19 continue;
20 }
21
22 size_t curlen = buffer ? strlen(buffer) : 0;
23 buffer = realloc(buffer, curlen + strlen(line) + 2);
24 snprintf(buffer + curlen, strlen(line) + 2, "%s\n", line);
25
26 free(line);
27 }
28
29 while (buffer && buffer[strlen(buffer) - 1] == '\n') {
30 buffer[strlen(buffer) - 1] = '\0';
31 }
32
33 return buffer;
34}
35
36int swaynag_parse_options(int argc, char **argv, struct swaynag *swaynag,
37 list_t *types, struct swaynag_type *type, char **config, bool *debug) {
38 enum type_options {
39 TO_COLOR_BACKGROUND = 256,
40 TO_COLOR_BORDER,
41 TO_COLOR_BORDER_BOTTOM,
42 TO_COLOR_BUTTON,
43 TO_COLOR_TEXT,
44 TO_THICK_BAR_BORDER,
45 TO_PADDING_MESSAGE,
46 TO_THICK_DET_BORDER,
47 TO_THICK_BTN_BORDER,
48 TO_GAP_BTN,
49 TO_GAP_BTN_DISMISS,
50 TO_MARGIN_BTN_RIGHT,
51 TO_PADDING_BTN,
52 };
53
54 static struct option opts[] = {
55 {"button", required_argument, NULL, 'b'},
56 {"config", required_argument, NULL, 'c'},
57 {"debug", no_argument, NULL, 'd'},
58 {"edge", required_argument, NULL, 'e'},
59 {"font", required_argument, NULL, 'f'},
60 {"help", no_argument, NULL, 'h'},
61 {"detailed-message", no_argument, NULL, 'l'},
62 {"detailed-button", required_argument, NULL, 'L'},
63 {"message", required_argument, NULL, 'm'},
64 {"output", required_argument, NULL, 'o'},
65 {"dismiss-button", required_argument, NULL, 's'},
66 {"type", required_argument, NULL, 't'},
67 {"version", no_argument, NULL, 'v'},
68
69 {"background", required_argument, NULL, TO_COLOR_BACKGROUND},
70 {"border", required_argument, NULL, TO_COLOR_BORDER},
71 {"border-bottom", required_argument, NULL, TO_COLOR_BORDER_BOTTOM},
72 {"button-background", required_argument, NULL, TO_COLOR_BUTTON},
73 {"text", required_argument, NULL, TO_COLOR_TEXT},
74 {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER},
75 {"message-padding", required_argument, NULL, TO_PADDING_MESSAGE},
76 {"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER},
77 {"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER},
78 {"button-gap", required_argument, NULL, TO_GAP_BTN},
79 {"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS},
80 {"button-margin-right", required_argument, NULL, TO_MARGIN_BTN_RIGHT},
81 {"button-padding", required_argument, NULL, TO_PADDING_BTN},
82
83 {0, 0, 0, 0}
84 };
85
86 const char *usage =
87 "Usage: swaynag [options...]\n"
88 "\n"
89 " -b, --button <text> <action> Create a button with text that "
90 "executes action when pressed. Multiple buttons can be defined.\n"
91 " -c, --config <path> Path to config file.\n"
92 " -d, --debug Enable debugging.\n"
93 " -e, --edge top|bottom Set the edge to use.\n"
94 " -f, --font <font> Set the font to use.\n"
95 " -h, --help Show help message and quit.\n"
96 " -l, --detailed-message Read a detailed message from stdin.\n"
97 " -L, --detailed-button <text> Set the text of the detail button.\n"
98 " -m, --message <msg> Set the message text.\n"
99 " -o, --output <output> Set the output to use.\n"
100 " -s, --dismiss-button <text> Set the dismiss button text.\n"
101 " -t, --type <type> Set the message type.\n"
102 " -v, --version Show the version number and quit.\n"
103 "\n"
104 "The following appearance options can also be given:\n"
105 " --background RRGGBB[AA] Background color.\n"
106 " --border RRGGBB[AA] Border color.\n"
107 " --border-bottom RRGGBB[AA] Bottom border color.\n"
108 " --button-background RRGGBB[AA] Button background color.\n"
109 " --text RRGGBB[AA] Text color.\n"
110 " --border-bottom-size size Thickness of the bar border.\n"
111 " --message-padding padding Padding for the message.\n"
112 " --details-border-size size Thickness for the details border.\n"
113 " --button-border-size size Thickness for the button border.\n"
114 " --button-gap gap Size of the gap between buttons\n"
115 " --button-dismiss-gap gap Size of the gap for dismiss button.\n"
116 " --button-margin-right margin Margin from dismiss button to edge.\n"
117 " --button-padding padding Padding for the button text.\n";
118
119 optind = 1;
120 while (1) {
121 int c = getopt_long(argc, argv, "b:c:de:f:hlL:m:o:s:t:v", opts, NULL);
122 if (c == -1) {
123 break;
124 }
125 switch (c) {
126 case 'b': // Button
127 if (swaynag) {
128 if (optind >= argc) {
129 fprintf(stderr, "Missing action for button %s\n", optarg);
130 return EXIT_FAILURE;
131 }
132 struct swaynag_button *button;
133 button = calloc(sizeof(struct swaynag_button), 1);
134 button->text = strdup(optarg);
135 button->type = SWAYNAG_ACTION_COMMAND;
136 button->action = strdup(argv[optind]);
137 list_add(swaynag->buttons, button);
138 }
139 optind++;
140 break;
141 case 'c': // Config
142 if (config) {
143 *config = strdup(optarg);
144 }
145 break;
146 case 'd': // Debug
147 if (debug) {
148 *debug = true;
149 }
150 break;
151 case 'e': // Edge
152 if (type) {
153 if (strcmp(optarg, "top") == 0) {
154 type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
155 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
156 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
157 } else if (strcmp(optarg, "bottom") == 0) {
158 type->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
159 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
160 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
161 } else {
162 fprintf(stderr, "Invalid edge: %s\n", optarg);
163 return EXIT_FAILURE;
164 }
165 }
166 break;
167 case 'f': // Font
168 if (type) {
169 free(type->font);
170 type->font = strdup(optarg);
171 }
172 break;
173 case 'l': // Detailed Message
174 if (swaynag) {
175 free(swaynag->details.message);
176 swaynag->details.message = read_from_stdin();
177 swaynag->details.button_up.text = strdup("▲");
178 swaynag->details.button_down.text = strdup("▼");
179 }
180 break;
181 case 'L': // Detailed Button Text
182 if (swaynag) {
183 free(swaynag->details.button_details.text);
184 swaynag->details.button_details.text = strdup(optarg);
185 }
186 break;
187 case 'm': // Message
188 if (swaynag) {
189 free(swaynag->message);
190 swaynag->message = strdup(optarg);
191 }
192 break;
193 case 'o': // Output
194 if (type) {
195 free(type->output);
196 type->output = strdup(optarg);
197 }
198 break;
199 case 's': // Dismiss Button Text
200 if (swaynag) {
201 struct swaynag_button *button_close;
202 button_close = swaynag->buttons->items[0];
203 free(button_close->text);
204 button_close->text = strdup(optarg);
205 }
206 break;
207 case 't': // Type
208 if (swaynag) {
209 swaynag->type = swaynag_type_get(types, optarg);
210 if (!swaynag->type) {
211 fprintf(stderr, "Unknown type %s\n", optarg);
212 return EXIT_FAILURE;
213 }
214 }
215 break;
216 case 'v': // Version
217 fprintf(stdout, "swaynag version " SWAY_VERSION "\n");
218 return -1;
219 case TO_COLOR_BACKGROUND: // Background color
220 if (type) {
221 type->background = parse_color(optarg);
222 }
223 break;
224 case TO_COLOR_BORDER: // Border color
225 if (type) {
226 type->border = parse_color(optarg);
227 }
228 break;
229 case TO_COLOR_BORDER_BOTTOM: // Bottom border color
230 if (type) {
231 type->border_bottom = parse_color(optarg);
232 }
233 break;
234 case TO_COLOR_BUTTON: // Button background color
235 if (type) {
236 type->button_background = parse_color(optarg);
237 }
238 break;
239 case TO_COLOR_TEXT: // Text color
240 if (type) {
241 type->text = parse_color(optarg);
242 }
243 break;
244 case TO_THICK_BAR_BORDER: // Bottom border thickness
245 if (type) {
246 type->bar_border_thickness = strtol(optarg, NULL, 0);
247 }
248 break;
249 case TO_PADDING_MESSAGE: // Message padding
250 if (type) {
251 type->message_padding = strtol(optarg, NULL, 0);
252 }
253 break;
254 case TO_THICK_DET_BORDER: // Details border thickness
255 if (type) {
256 type->details_border_thickness = strtol(optarg, NULL, 0);
257 }
258 break;
259 case TO_THICK_BTN_BORDER: // Button border thickness
260 if (type) {
261 type->button_border_thickness = strtol(optarg, NULL, 0);
262 }
263 break;
264 case TO_GAP_BTN: // Gap between buttons
265 if (type) {
266 type->button_gap = strtol(optarg, NULL, 0);
267 }
268 break;
269 case TO_GAP_BTN_DISMISS: // Gap between dismiss button
270 if (type) {
271 type->button_gap_close = strtol(optarg, NULL, 0);
272 }
273 break;
274 case TO_MARGIN_BTN_RIGHT: // Margin on the right side of button area
275 if (type) {
276 type->button_margin_right = strtol(optarg, NULL, 0);
277 }
278 break;
279 case TO_PADDING_BTN: // Padding for the button text
280 if (type) {
281 type->button_padding = strtol(optarg, NULL, 0);
282 }
283 break;
284 default: // Help or unknown flag
285 fprintf(c == 'h' ? stdout : stderr, "%s", usage);
286 return -1;
287 }
288 }
289
290 return 0;
291}
292
293static bool file_exists(const char *path) {
294 return path && access(path, R_OK) != -1;
295}
296
297char *swaynag_get_config_path(void) {
298 static const char *config_paths[] = {
299 "$HOME/.swaynag/config",
300 "$XDG_CONFIG_HOME/swaynag/config",
301 SYSCONFDIR "/swaynag/config",
302 };
303
304 if (!getenv("XDG_CONFIG_HOME")) {
305 char *home = getenv("HOME");
306 char *config_home = malloc(strlen(home) + strlen("/.config") + 1);
307 if (!config_home) {
308 wlr_log(WLR_ERROR, "Unable to allocate $HOME/.config");
309 } else {
310 strcpy(config_home, home);
311 strcat(config_home, "/.config");
312 setenv("XDG_CONFIG_HOME", config_home, 1);
313 wlr_log(WLR_DEBUG, "Set XDG_CONFIG_HOME to %s", config_home);
314 free(config_home);
315 }
316 }
317
318 wordexp_t p;
319 char *path;
320 for (size_t i = 0; i < sizeof(config_paths) / sizeof(char *); ++i) {
321 if (wordexp(config_paths[i], &p, 0) == 0) {
322 path = strdup(p.we_wordv[0]);
323 wordfree(&p);
324 if (file_exists(path)) {
325 return path;
326 }
327 free(path);
328 }
329 }
330
331 return NULL;
332}
333
334int swaynag_load_config(char *path, struct swaynag *swaynag, list_t *types) {
335 FILE *config = fopen(path, "r");
336 if (!config) {
337 fprintf(stderr, "Failed to read config. Running without it.\n");
338 return 0;
339 }
340
341 struct swaynag_type *type;
342 type = calloc(1, sizeof(struct swaynag_type));
343 type->name = strdup("<config>");
344 list_add(types, type);
345
346 char *line;
347 int line_number = 0;
348 while (!feof(config)) {
349 line = read_line(config);
350 if (!line) {
351 continue;
352 }
353
354 line_number++;
355 if (line[0] == '#') {
356 free(line);
357 continue;
358 }
359 if (strlen(line) == 0) {
360 free(line);
361 continue;
362 }
363
364 if (line[0] == '[') {
365 char *close = strchr(line, ']');
366 if (!close) {
367 free(line);
368 fclose(config);
369 fprintf(stderr, "Closing bracket not found on line %d\n",
370 line_number);
371 return 1;
372 }
373 char *name = calloc(1, close - line);
374 strncat(name, line + 1, close - line - 1);
375 type = swaynag_type_get(types, name);
376 if (!type) {
377 type = calloc(1, sizeof(struct swaynag_type));
378 type->name = strdup(name);
379 list_add(types, type);
380 }
381 free(name);
382 } else {
383 char flag[strlen(line) + 3];
384 sprintf(flag, "--%s", line);
385 char *argv[] = {"swaynag", flag};
386 int result;
387 result = swaynag_parse_options(2, argv, swaynag, types, type,
388 NULL, NULL);
389 if (result != 0) {
390 free(line);
391 fclose(config);
392 return result;
393 }
394 }
395
396 free(line);
397 }
398 fclose(config);
399 return 0;
400}
401
diff --git a/swaynag/main.c b/swaynag/main.c
new file mode 100644
index 00000000..854368e5
--- /dev/null
+++ b/swaynag/main.c
@@ -0,0 +1,129 @@
1#define _XOPEN_SOURCE 500
2#include <signal.h>
3#include "log.h"
4#include "list.h"
5#include "swaynag/config.h"
6#include "swaynag/swaynag.h"
7#include "swaynag/types.h"
8
9static struct swaynag swaynag;
10
11void sig_handler(int signal) {
12 swaynag_destroy(&swaynag);
13 exit(EXIT_FAILURE);
14}
15
16void sway_terminate(int code) {
17 swaynag_destroy(&swaynag);
18 exit(code);
19}
20
21int main(int argc, char **argv) {
22 int exit_code = EXIT_SUCCESS;
23
24 list_t *types = create_list();
25 swaynag_types_add_default(types);
26
27 memset(&swaynag, 0, sizeof(swaynag));
28 swaynag.buttons = create_list();
29
30 struct swaynag_button *button_close =
31 calloc(sizeof(struct swaynag_button), 1);
32 button_close->text = strdup("X");
33 button_close->type = SWAYNAG_ACTION_DISMISS;
34 list_add(swaynag.buttons, button_close);
35
36 swaynag.details.button_details.text = strdup("Toggle Details");
37 swaynag.details.button_details.type = SWAYNAG_ACTION_EXPAND;
38
39
40 char *config_path = NULL;
41 bool debug = false;
42 int launch_status = swaynag_parse_options(argc, argv, NULL, NULL, NULL,
43 &config_path, &debug);
44 if (launch_status != 0) {
45 exit_code = launch_status;
46 goto cleanup;
47 }
48 wlr_log_init(debug ? WLR_DEBUG : WLR_ERROR, NULL);
49
50 if (!config_path) {
51 config_path = swaynag_get_config_path();
52 }
53 if (config_path) {
54 wlr_log(WLR_DEBUG, "Loading config file: %s", config_path);
55 int config_status = swaynag_load_config(config_path, &swaynag, types);
56 free(config_path);
57 if (config_status != 0) {
58 exit_code = config_status;
59 goto cleanup;
60 }
61 }
62
63 if (argc > 1) {
64 struct swaynag_type *type_args;
65 type_args = calloc(1, sizeof(struct swaynag_type));
66 type_args->name = strdup("<args>");
67 list_add(types, type_args);
68
69 int result = swaynag_parse_options(argc, argv, &swaynag, types,
70 type_args, NULL, NULL);
71 if (result != 0) {
72 exit_code = result;
73 goto cleanup;
74 }
75 }
76
77 if (!swaynag.message) {
78 wlr_log(WLR_ERROR, "No message passed. Please provide --message/-m");
79 exit_code = EXIT_FAILURE;
80 goto cleanup;
81 }
82
83 if (!swaynag.type) {
84 swaynag.type = swaynag_type_get(types, "error");
85 }
86
87 // Construct a new type using the config defaults as base, then merging
88 // config type defaults on top, then merging arguments on top of that, and
89 // finally merging defaults on top.
90 struct swaynag_type *type = calloc(1, sizeof(struct swaynag_type));
91 type->name = strdup(swaynag.type->name);
92 swaynag_type_merge(type, swaynag_type_get(types, "<args>"));
93 swaynag_type_merge(type, swaynag.type);
94 swaynag_type_merge(type, swaynag_type_get(types, "<config>"));
95 swaynag_type_merge(type, swaynag_type_get(types, "<defaults>"));
96 swaynag.type = type;
97
98 swaynag_types_free(types);
99
100 if (swaynag.details.message) {
101 list_add(swaynag.buttons, &swaynag.details.button_details);
102 } else {
103 free(swaynag.details.button_details.text);
104 }
105
106 wlr_log(WLR_DEBUG, "Output: %s", swaynag.type->output);
107 wlr_log(WLR_DEBUG, "Anchors: %d", swaynag.type->anchors);
108 wlr_log(WLR_DEBUG, "Type: %s", swaynag.type->name);
109 wlr_log(WLR_DEBUG, "Message: %s", swaynag.message);
110 wlr_log(WLR_DEBUG, "Font: %s", swaynag.type->font);
111 wlr_log(WLR_DEBUG, "Buttons");
112 for (int i = 0; i < swaynag.buttons->length; i++) {
113 struct swaynag_button *button = swaynag.buttons->items[i];
114 wlr_log(WLR_DEBUG, "\t[%s] `%s`", button->text, button->action);
115 }
116
117 signal(SIGTERM, sig_handler);
118
119 swaynag_setup(&swaynag);
120 swaynag_run(&swaynag);
121 return exit_code;
122
123cleanup:
124 swaynag_types_free(types);
125 free(swaynag.details.button_details.text);
126 swaynag_destroy(&swaynag);
127 return exit_code;
128}
129
diff --git a/swaynag/meson.build b/swaynag/meson.build
new file mode 100644
index 00000000..2ba3ed95
--- /dev/null
+++ b/swaynag/meson.build
@@ -0,0 +1,23 @@
1executable(
2 'swaynag', [
3 'config.c',
4 'main.c',
5 'render.c',
6 'swaynag.c',
7 'types.c',
8 ],
9 include_directories: [sway_inc],
10 dependencies: [
11 cairo,
12 client_protos,
13 gdk_pixbuf,
14 math,
15 pango,
16 pangocairo,
17 wayland_client,
18 wayland_cursor,
19 wlroots,
20 ],
21 link_with: [lib_sway_common, lib_sway_client],
22 install: true
23)
diff --git a/swaynag/render.c b/swaynag/render.c
new file mode 100644
index 00000000..766409e4
--- /dev/null
+++ b/swaynag/render.c
@@ -0,0 +1,308 @@
1#include <stdint.h>
2#include "cairo.h"
3#include "log.h"
4#include "pango.h"
5#include "pool-buffer.h"
6#include "swaynag/swaynag.h"
7#include "swaynag/types.h"
8#include "wlr-layer-shell-unstable-v1-client-protocol.h"
9
10static uint32_t render_message(cairo_t *cairo, struct swaynag *swaynag) {
11 uint32_t height = swaynag->height * swaynag->scale;
12 height -= swaynag->type->bar_border_thickness * swaynag->scale;
13
14 int text_width, text_height;
15 get_text_size(cairo, swaynag->type->font, &text_width, &text_height,
16 swaynag->scale, true, "%s", swaynag->message);
17
18 int padding = swaynag->type->message_padding * swaynag->scale;
19
20 uint32_t ideal_height = text_height + padding * 2;
21 uint32_t ideal_surface_height = ideal_height / swaynag->scale;
22 if (swaynag->height < ideal_surface_height) {
23 return ideal_surface_height;
24 }
25
26 cairo_set_source_u32(cairo, swaynag->type->text);
27 cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2);
28 pango_printf(cairo, swaynag->type->font, swaynag->scale, false,
29 "%s", swaynag->message);
30
31 return ideal_surface_height;
32}
33
34static void render_details_scroll_button(cairo_t *cairo,
35 struct swaynag *swaynag, struct swaynag_button *button) {
36 int text_width, text_height;
37 get_text_size(cairo, swaynag->type->font, &text_width, &text_height,
38 swaynag->scale, true, "%s", button->text);
39
40 int border = swaynag->type->button_border_thickness * swaynag->scale;
41 int padding = swaynag->type->button_padding * swaynag->scale;
42
43 cairo_set_source_u32(cairo, swaynag->type->border);
44 cairo_rectangle(cairo, button->x, button->y,
45 button->width, button->height);
46 cairo_fill(cairo);
47
48 cairo_set_source_u32(cairo, swaynag->type->button_background);
49 cairo_rectangle(cairo, button->x + border, button->y + border,
50 button->width - (border * 2), button->height - (border * 2));
51 cairo_fill(cairo);
52
53 cairo_set_source_u32(cairo, swaynag->type->text);
54 cairo_move_to(cairo, button->x + border + padding,
55 button->y + border + (button->height - text_height) / 2);
56 pango_printf(cairo, swaynag->type->font, swaynag->scale, true,
57 "%s", button->text);
58}
59
60static int get_detailed_scroll_button_width(cairo_t *cairo,
61 struct swaynag *swaynag) {
62 int up_width, down_width, temp_height;
63 get_text_size(cairo, swaynag->type->font, &up_width, &temp_height,
64 swaynag->scale, true,
65 "%s", swaynag->details.button_up.text);
66 get_text_size(cairo, swaynag->type->font, &down_width, &temp_height,
67 swaynag->scale, true,
68 "%s", swaynag->details.button_down.text);
69
70 int text_width = up_width > down_width ? up_width : down_width;
71 int border = swaynag->type->button_border_thickness * swaynag->scale;
72 int padding = swaynag->type->button_padding * swaynag->scale;
73
74 return text_width + border * 2 + padding * 2;
75}
76
77static uint32_t render_detailed(cairo_t *cairo, struct swaynag *swaynag,
78 uint32_t y) {
79 uint32_t width = swaynag->width * swaynag->scale;
80 uint32_t height = swaynag->height * swaynag->scale;
81 height -= swaynag->type->bar_border_thickness * swaynag->scale;
82
83 int border = swaynag->type->details_border_thickness * swaynag->scale;
84 int padding = swaynag->type->message_padding * swaynag->scale;
85 int decor = padding + border;
86
87 swaynag->details.x = decor;
88 swaynag->details.y = y * swaynag->scale + decor;
89 swaynag->details.width = width - decor * 2;
90
91 PangoLayout *layout = get_pango_layout(cairo, swaynag->type->font,
92 swaynag->details.message, swaynag->scale, false);
93 pango_layout_set_width(layout,
94 (swaynag->details.width - padding * 2) * PANGO_SCALE);
95 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
96 pango_layout_set_single_paragraph_mode(layout, false);
97 pango_cairo_update_layout(cairo, layout);
98 swaynag->details.total_lines = pango_layout_get_line_count(layout);
99
100 PangoLayoutLine *line;
101 line = pango_layout_get_line_readonly(layout, swaynag->details.offset);
102 gint offset = line->start_index;
103 const char *text = pango_layout_get_text(layout);
104 pango_layout_set_text(layout, text + offset, strlen(text) - offset);
105
106 int text_width, text_height;
107 pango_cairo_update_layout(cairo, layout);
108 pango_layout_get_pixel_size(layout, &text_width, &text_height);
109
110 bool show_buttons = swaynag->details.offset > 0;
111 int button_width = get_detailed_scroll_button_width(cairo, swaynag);
112 if (show_buttons) {
113 swaynag->details.width -= button_width;
114 pango_layout_set_width(layout,
115 (swaynag->details.width - padding * 2) * PANGO_SCALE);
116 }
117
118 uint32_t ideal_height;
119 do {
120 ideal_height = swaynag->details.y + text_height + decor + padding * 2;
121 if (ideal_height > SWAYNAG_MAX_HEIGHT) {
122 ideal_height = SWAYNAG_MAX_HEIGHT;
123
124 if (!show_buttons) {
125 show_buttons = true;
126 swaynag->details.width -= button_width;
127 pango_layout_set_width(layout,
128 (swaynag->details.width - padding * 2) * PANGO_SCALE);
129 }
130 }
131
132 swaynag->details.height = ideal_height - swaynag->details.y - decor;
133 pango_layout_set_height(layout,
134 (swaynag->details.height - padding * 2) * PANGO_SCALE);
135 pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
136 pango_cairo_update_layout(cairo, layout);
137 pango_layout_get_pixel_size(layout, &text_width, &text_height);
138 } while (text_height != (swaynag->details.height - padding * 2));
139
140 swaynag->details.visible_lines = pango_layout_get_line_count(layout);
141
142 if (show_buttons) {
143 swaynag->details.button_up.x =
144 swaynag->details.x + swaynag->details.width;
145 swaynag->details.button_up.y = swaynag->details.y;
146 swaynag->details.button_up.width = button_width;
147 swaynag->details.button_up.height = swaynag->details.height / 2;
148 render_details_scroll_button(cairo, swaynag,
149 &swaynag->details.button_up);
150
151 swaynag->details.button_down.x =
152 swaynag->details.x + swaynag->details.width;
153 swaynag->details.button_down.y =
154 swaynag->details.button_up.y + swaynag->details.button_up.height;
155 swaynag->details.button_down.width = button_width;
156 swaynag->details.button_down.height = swaynag->details.height / 2;
157 render_details_scroll_button(cairo, swaynag,
158 &swaynag->details.button_down);
159 }
160
161 cairo_set_source_u32(cairo, swaynag->type->border);
162 cairo_rectangle(cairo, swaynag->details.x, swaynag->details.y,
163 swaynag->details.width, swaynag->details.height);
164 cairo_fill(cairo);
165
166 cairo_move_to(cairo, swaynag->details.x + padding,
167 swaynag->details.y + padding);
168 cairo_set_source_u32(cairo, swaynag->type->text);
169 pango_cairo_show_layout(cairo, layout);
170 g_object_unref(layout);
171
172 return ideal_height / swaynag->scale;
173}
174
175static uint32_t render_button(cairo_t *cairo, struct swaynag *swaynag,
176 int button_index, int *x) {
177 uint32_t height = swaynag->height * swaynag->scale;
178 height -= swaynag->type->bar_border_thickness * swaynag->scale;
179 struct swaynag_button *button = swaynag->buttons->items[button_index];
180
181 int text_width, text_height;
182 get_text_size(cairo, swaynag->type->font, &text_width, &text_height,
183 swaynag->scale, true, "%s", button->text);
184
185 int border = swaynag->type->button_border_thickness * swaynag->scale;
186 int padding = swaynag->type->button_padding * swaynag->scale;
187
188 uint32_t ideal_height = text_height + padding * 2 + border * 2;
189 uint32_t ideal_surface_height = ideal_height / swaynag->scale;
190 if (swaynag->height < ideal_surface_height) {
191 return ideal_surface_height;
192 }
193
194 button->x = *x - border - text_width - padding * 2;
195 button->y = (int)(ideal_height - text_height) / 2 - padding;
196 button->width = text_width + padding * 2;
197 button->height = text_height + padding * 2;
198
199 cairo_set_source_u32(cairo, swaynag->type->border);
200 cairo_rectangle(cairo, button->x - border, button->y - border,
201 button->width + border * 2, button->height + border * 2);
202 cairo_fill(cairo);
203
204 cairo_set_source_u32(cairo, swaynag->type->button_background);
205 cairo_rectangle(cairo, button->x, button->y,
206 button->width, button->height);
207 cairo_fill(cairo);
208
209 cairo_set_source_u32(cairo, swaynag->type->text);
210 cairo_move_to(cairo, button->x + padding, button->y + padding);
211 pango_printf(cairo, swaynag->type->font, swaynag->scale, true,
212 "%s", button->text);
213
214 *x = button->x - border;
215
216 return ideal_surface_height;
217}
218
219static uint32_t render_to_cairo(cairo_t *cairo, struct swaynag *swaynag) {
220 uint32_t max_height = 0;
221
222 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
223 cairo_set_source_u32(cairo, swaynag->type->background);
224 cairo_paint(cairo);
225
226 uint32_t h = render_message(cairo, swaynag);
227 max_height = h > max_height ? h : max_height;
228
229 int x = swaynag->width - swaynag->type->button_margin_right;
230 x *= swaynag->scale;
231 for (int i = 0; i < swaynag->buttons->length; i++) {
232 h = render_button(cairo, swaynag, i, &x);
233 max_height = h > max_height ? h : max_height;
234 x -= swaynag->type->button_gap * swaynag->scale;
235 if (i == 0) {
236 x -= swaynag->type->button_gap_close * swaynag->scale;
237 }
238 }
239
240 if (swaynag->details.visible) {
241 h = render_detailed(cairo, swaynag, max_height);
242 max_height = h > max_height ? h : max_height;
243 }
244
245 int border = swaynag->type->bar_border_thickness * swaynag->scale;
246 if (max_height > swaynag->height) {
247 max_height += border;
248 }
249 cairo_set_source_u32(cairo, swaynag->type->border_bottom);
250 cairo_rectangle(cairo, 0,
251 swaynag->height * swaynag->scale - border,
252 swaynag->width * swaynag->scale,
253 border);
254 cairo_fill(cairo);
255
256 return max_height;
257}
258
259void render_frame(struct swaynag *swaynag) {
260 if (!swaynag->run_display) {
261 return;
262 }
263
264 cairo_surface_t *recorder = cairo_recording_surface_create(
265 CAIRO_CONTENT_COLOR_ALPHA, NULL);
266 cairo_t *cairo = cairo_create(recorder);
267 cairo_save(cairo);
268 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
269 cairo_paint(cairo);
270 cairo_restore(cairo);
271 uint32_t height = render_to_cairo(cairo, swaynag);
272 if (height != swaynag->height) {
273 zwlr_layer_surface_v1_set_size(swaynag->layer_surface, 0, height);
274 zwlr_layer_surface_v1_set_exclusive_zone(swaynag->layer_surface,
275 height);
276 wl_surface_commit(swaynag->surface);
277 wl_display_roundtrip(swaynag->display);
278 } else {
279 swaynag->current_buffer = get_next_buffer(swaynag->shm,
280 swaynag->buffers,
281 swaynag->width * swaynag->scale,
282 swaynag->height * swaynag->scale);
283 if (!swaynag->current_buffer) {
284 wlr_log(WLR_DEBUG, "Failed to get buffer. Skipping frame.");
285 goto cleanup;
286 }
287
288 cairo_t *shm = swaynag->current_buffer->cairo;
289 cairo_save(shm);
290 cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR);
291 cairo_paint(shm);
292 cairo_restore(shm);
293 cairo_set_source_surface(shm, recorder, 0.0, 0.0);
294 cairo_paint(shm);
295
296 wl_surface_set_buffer_scale(swaynag->surface, swaynag->scale);
297 wl_surface_attach(swaynag->surface,
298 swaynag->current_buffer->buffer, 0, 0);
299 wl_surface_damage(swaynag->surface, 0, 0,
300 swaynag->width, swaynag->height);
301 wl_surface_commit(swaynag->surface);
302 wl_display_roundtrip(swaynag->display);
303 }
304
305cleanup:
306 cairo_surface_destroy(recorder);
307 cairo_destroy(cairo);
308}
diff --git a/swaynag/swaynag.1.scd b/swaynag/swaynag.1.scd
new file mode 100644
index 00000000..1c395aee
--- /dev/null
+++ b/swaynag/swaynag.1.scd
@@ -0,0 +1,106 @@
1swaynag(1)
2
3# NAME
4
5swaynag - Show a warning or error message with buttons
6
7# SYNOPSIS
8
9_swaynag_ [options...]
10
11# OPTIONS
12
13*-b, --button* <text> <action>
14 Create a button with the text _text_ that executes _action_ when pressed.
15 Multiple buttons can be defined by providing the flag multiple times.
16
17*-c, --config* <path>
18 The config file to use. By default, the following paths are checked:
19 _$HOME/.swaynag/config_, _$XDG\_CONFIG\_HOME/swaynag/config_, and
20 _SYSCONFDIR/swaynag/config_. All flags aside from this one and _debug_ are
21 valid options in the configuration file using the format
22 _long-option=value_. All leading dashes should be omitted and the equals
23 sign is required. See swaynag(5) for more information.
24
25*-d, --debug*
26 Enable debugging.
27
28*-e, --edge* top|bottom
29 Set the edge to use.
30
31*-f, --font* <font>
32 Set the font to use.
33
34*-h, --help*
35 Show help message and quit.
36
37*-l, --detailed-message*
38 Read a detailed message from stdin. A button to toggle details will be
39 added. Details are shown in a scrollable multi-line text area.
40
41*-L, --detailed-button* <text>
42 Set the text for the button that toggles details. This has no effect if
43 there is not a detailed message. The default is _Toggle Details_.
44
45*-m, --message* <msg>
46 Set the message text.
47
48*-o, --output* <output>
49 Set the output to use. This should be the name of a _xdg\_output_.
50
51*-s, --dismiss-button* <text>
52 Sets the text for the dismiss nagbar button. The default is _X_.
53
54*-t, --type* <type>
55 Set the message type. Two types are created by default _error_ and
56 _warning_. Custom types can be defined in the config file. See
57 _--config_ and swaynag(5) for details. Both of the default types can be
58 overridden in the config file as well.
59
60*-v, --version*
61 Show the version number and quit.
62
63# APPEARANCE OPTIONS
64
65*--background* <RRGGBB[AA]>
66 Set the color of the background.
67
68*--border* <RRGGBB[AA]>
69 Set the color of the border.
70
71*--border-bottom* <RRGGBB[AA]>
72 Set the color of the bottom border.
73
74*--button-background* <RRGGBB[AA]>
75 Set the color for the background for buttons.
76
77*--text* <RRGGBB[AA]>
78 Set the text color.
79
80*--border-bottom-size* <size>
81 Set the thickness of the bottom border.
82
83*--message-padding* <padding>
84 Set the padding for the message.
85
86*--details-border-size* <size>
87 Set the thickness for the details border.
88
89*--button-border-size* <size>
90 Set the thickness for the button border.
91
92*--button-gap* <gap>
93 Set the size of the gap between buttons.
94
95*--button-dismiss-gap* <gap>
96 Set the size of the gap between the dismiss button and another button.
97
98*--button-margin-right* <margin>
99 Set the margin from the right of the dismiss button to edge.
100
101*--button-padding* <padding>
102 Set the padding for the button text.
103
104# SEE
105
106swaynag(5)
diff --git a/swaynag/swaynag.5.scd b/swaynag/swaynag.5.scd
new file mode 100644
index 00000000..d3daadf7
--- /dev/null
+++ b/swaynag/swaynag.5.scd
@@ -0,0 +1,100 @@
1swaynag(5)
2
3# NAME
4
5swaynag - swaynag configuration file
6
7# SYNOPSIS
8
9$HOME/.swaynag/config, $XDG\_CONFIG\_HOME/swaynag/config,
10SYSCONFDIR/swaynag/config
11
12# CONFIG FILE
13
14At the top of the config file, _swaynag_ options can be set using the format
15_long-option=value_. These will be used as default values if _swaynag_ is not
16given the option. This can be useful for setting a preferred font, output, and
17edge.
18
19Below the options, custom types may be defined. To define a type, use the
20following format:
21
22```
23[name-of-type]
24option=value
25```
26
27All colors may be given in the form _RRGGBB_ or _RRGGBBAA_. The following
28colors can be set:
29
30*background=<color>*
31 The background color for _swaynag_.
32
33*border=<color>*
34 The color to use for borders of buttons.
35
36*border-bottom=<color>*
37 The color of the border line at the bottom of _swaynag_.
38
39*button-background=<color>*
40 The background color for the buttons.
41
42*text=<color>*
43 The color of the text.
44
45The following sizing options can also be set:
46
47*border-bottom-size=<size>*
48 Set the thickness of the bottom border.
49
50*message-padding=<padding>*
51 Set the padding for the message.
52
53*details-border-size=<size>*
54 Set the thickness for the details border.
55
56*button-border-size=<size>*
57 Set the thickness for the button border.
58
59*button-gap=<gap>*
60 Set the size of the gap between buttons.
61
62*button-dismiss-gap=<gap>*
63 Set the size of the gap between the dismiss button and another button.
64
65*button-margin-right=<margin>*
66 Set the margin from the right of the dismiss button to edge.
67
68*button-padding=<padding>*
69 Set the padding for the button text.
70
71Additionally, the following options can be assigned a default per-type:
72
73*edge=top|bottom*
74 Set the edge to use.
75
76*font=<font>*
77 Set the font to use.
78
79*output=<output>*
80 Set the output to use. This should be the name of a _xdg\_output_.
81
82# EXAMPLE
83
84```
85font=Monospace 12
86edge=bottom
87
88[green]
89edge=top
90background=00AA00
91border=006600
92border-bottom=004400
93text=FFFFFF
94button-background=00CC00
95message-padding=10
96```
97
98# SEE
99
100swaynag(1)
diff --git a/swaynag/swaynag.c b/swaynag/swaynag.c
new file mode 100644
index 00000000..3966277d
--- /dev/null
+++ b/swaynag/swaynag.c
@@ -0,0 +1,451 @@
1#define _XOPEN_SOURCE 500
2#include <assert.h>
3#include <sys/stat.h>
4#include <sys/wait.h>
5#include <wayland-client.h>
6#include <wayland-cursor.h>
7#include "log.h"
8#include "list.h"
9#include "swaynag/render.h"
10#include "swaynag/swaynag.h"
11#include "swaynag/types.h"
12#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13
14static void nop() {
15 // Intentionally left blank
16}
17
18static bool terminal_execute(char *terminal, char *command) {
19 char fname[] = "/tmp/swaynagXXXXXX";
20 FILE *tmp= fdopen(mkstemp(fname), "w");
21 if (!tmp) {
22 wlr_log(WLR_ERROR, "Failed to create temp script");
23 return false;
24 }
25 wlr_log(WLR_DEBUG, "Created temp script: %s", fname);
26 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command);
27 fclose(tmp);
28 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR);
29 char cmd[strlen(terminal) + strlen(" -e ") + strlen(fname) + 1];
30 sprintf(cmd, "%s -e %s", terminal, fname);
31 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
32 return true;
33}
34
35static void swaynag_button_execute(struct swaynag *swaynag,
36 struct swaynag_button *button) {
37 wlr_log(WLR_DEBUG, "Executing [%s]: %s", button->text, button->action);
38 if (button->type == SWAYNAG_ACTION_DISMISS) {
39 swaynag->run_display = false;
40 } else if (button->type == SWAYNAG_ACTION_EXPAND) {
41 swaynag->details.visible = !swaynag->details.visible;
42 render_frame(swaynag);
43 } else {
44 if (fork() == 0) {
45 // Child process. Will be used to prevent zombie processes
46 setsid();
47 if (fork() == 0) {
48 // Child of the child. Will be reparented to the init process
49 char *terminal = getenv("TERMINAL");
50 if (terminal && strlen(terminal)) {
51 wlr_log(WLR_DEBUG, "Found $TERMINAL: %s", terminal);
52 if (!terminal_execute(terminal, button->action)) {
53 swaynag_destroy(swaynag);
54 exit(EXIT_FAILURE);
55 }
56 } else {
57 wlr_log(WLR_DEBUG, "$TERMINAL not found. Running directly");
58 execl("/bin/sh", "/bin/sh", "-c", button->action, NULL);
59 }
60 }
61 exit(EXIT_SUCCESS);
62 }
63 }
64 wait(0);
65}
66
67static void layer_surface_configure(void *data,
68 struct zwlr_layer_surface_v1 *surface,
69 uint32_t serial, uint32_t width, uint32_t height) {
70 struct swaynag *swaynag = data;
71 swaynag->width = width;
72 swaynag->height = height;
73 zwlr_layer_surface_v1_ack_configure(surface, serial);
74 render_frame(swaynag);
75}
76
77static void layer_surface_closed(void *data,
78 struct zwlr_layer_surface_v1 *surface) {
79 struct swaynag *swaynag = data;
80 swaynag_destroy(swaynag);
81}
82
83static struct zwlr_layer_surface_v1_listener layer_surface_listener = {
84 .configure = layer_surface_configure,
85 .closed = layer_surface_closed,
86};
87
88static void surface_enter(void *data, struct wl_surface *surface,
89 struct wl_output *output) {
90 struct swaynag *swaynag = data;
91 struct swaynag_output *swaynag_output;
92 wl_list_for_each(swaynag_output, &swaynag->outputs, link) {
93 if (swaynag_output->wl_output == output) {
94 wlr_log(WLR_DEBUG, "Surface enter on output %s",
95 swaynag_output->name);
96 swaynag->output = swaynag_output;
97 swaynag->scale = swaynag->output->scale;
98 render_frame(swaynag);
99 break;
100 }
101 };
102}
103
104static struct wl_surface_listener surface_listener = {
105 .enter = surface_enter,
106 .leave = nop,
107};
108
109static void update_cursor(struct swaynag *swaynag) {
110 struct swaynag_pointer *pointer = &swaynag->pointer;
111 pointer->cursor_theme = wl_cursor_theme_load(NULL, 24 * swaynag->scale,
112 swaynag->shm);
113 struct wl_cursor *cursor =
114 wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
115 pointer->cursor_image = cursor->images[0];
116 wl_surface_set_buffer_scale(pointer->cursor_surface,
117 swaynag->scale);
118 wl_surface_attach(pointer->cursor_surface,
119 wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
120 wl_pointer_set_cursor(pointer->pointer, pointer->serial,
121 pointer->cursor_surface,
122 pointer->cursor_image->hotspot_x / swaynag->scale,
123 pointer->cursor_image->hotspot_y / swaynag->scale);
124 wl_surface_commit(pointer->cursor_surface);
125}
126
127static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
128 uint32_t serial, struct wl_surface *surface,
129 wl_fixed_t surface_x, wl_fixed_t surface_y) {
130 struct swaynag *swaynag = data;
131 struct swaynag_pointer *pointer = &swaynag->pointer;
132 pointer->serial = serial;
133 update_cursor(swaynag);
134}
135
136static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
137 uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
138 struct swaynag *swaynag = data;
139 swaynag->pointer.x = wl_fixed_to_int(surface_x);
140 swaynag->pointer.y = wl_fixed_to_int(surface_y);
141}
142
143static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
144 uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
145 struct swaynag *swaynag = data;
146
147 if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
148 return;
149 }
150
151 double x = swaynag->pointer.x * swaynag->scale;
152 double y = swaynag->pointer.y * swaynag->scale;
153 for (int i = 0; i < swaynag->buttons->length; i++) {
154 struct swaynag_button *nagbutton = swaynag->buttons->items[i];
155 if (x >= nagbutton->x
156 && y >= nagbutton->y
157 && x < nagbutton->x + nagbutton->width
158 && y < nagbutton->y + nagbutton->height) {
159 swaynag_button_execute(swaynag, nagbutton);
160 return;
161 }
162 }
163
164 if (swaynag->details.visible &&
165 swaynag->details.total_lines != swaynag->details.visible_lines) {
166 struct swaynag_button button_up = swaynag->details.button_up;
167 if (x >= button_up.x
168 && y >= button_up.y
169 && x < button_up.x + button_up.width
170 && y < button_up.y + button_up.height
171 && swaynag->details.offset > 0) {
172 swaynag->details.offset--;
173 render_frame(swaynag);
174 return;
175 }
176
177 struct swaynag_button button_down = swaynag->details.button_down;
178 int bot = swaynag->details.total_lines;
179 bot -= swaynag->details.visible_lines;
180 if (x >= button_down.x
181 && y >= button_down.y
182 && x < button_down.x + button_down.width
183 && y < button_down.y + button_down.height
184 && swaynag->details.offset < bot) {
185 swaynag->details.offset++;
186 render_frame(swaynag);
187 return;
188 }
189 }
190}
191
192static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
193 uint32_t time, uint32_t axis, wl_fixed_t value) {
194 struct swaynag *swaynag = data;
195 if (!swaynag->details.visible
196 || swaynag->pointer.x < swaynag->details.x
197 || swaynag->pointer.y < swaynag->details.y
198 || swaynag->pointer.x >= swaynag->details.x + swaynag->details.width
199 || swaynag->pointer.y >= swaynag->details.y + swaynag->details.height
200 || swaynag->details.total_lines == swaynag->details.visible_lines) {
201 return;
202 }
203
204 int direction = wl_fixed_to_int(value);
205 int bot = swaynag->details.total_lines - swaynag->details.visible_lines;
206 if (direction < 0 && swaynag->details.offset > 0) {
207 swaynag->details.offset--;
208 } else if (direction > 0 && swaynag->details.offset < bot) {
209 swaynag->details.offset++;
210 }
211
212 render_frame(swaynag);
213}
214
215static struct wl_pointer_listener pointer_listener = {
216 .enter = wl_pointer_enter,
217 .leave = nop,
218 .motion = wl_pointer_motion,
219 .button = wl_pointer_button,
220 .axis = wl_pointer_axis,
221 .frame = nop,
222 .axis_source = nop,
223 .axis_stop = nop,
224 .axis_discrete = nop,
225};
226
227static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
228 enum wl_seat_capability caps) {
229 struct swaynag *swaynag = data;
230 if ((caps & WL_SEAT_CAPABILITY_POINTER)) {
231 swaynag->pointer.pointer = wl_seat_get_pointer(wl_seat);
232 wl_pointer_add_listener(swaynag->pointer.pointer, &pointer_listener,
233 swaynag);
234 }
235}
236
237const struct wl_seat_listener seat_listener = {
238 .capabilities = seat_handle_capabilities,
239 .name = nop,
240};
241
242static void output_scale(void *data, struct wl_output *output,
243 int32_t factor) {
244 struct swaynag_output *swaynag_output = data;
245 swaynag_output->scale = factor;
246 if (swaynag_output->swaynag->output == swaynag_output) {
247 swaynag_output->swaynag->scale = swaynag_output->scale;
248 update_cursor(swaynag_output->swaynag);
249 render_frame(swaynag_output->swaynag);
250 }
251}
252
253static struct wl_output_listener output_listener = {
254 .geometry = nop,
255 .mode = nop,
256 .done = nop,
257 .scale = output_scale,
258};
259
260static void xdg_output_handle_name(void *data,
261 struct zxdg_output_v1 *xdg_output, const char *name) {
262 struct swaynag_output *swaynag_output = data;
263 char *outname = swaynag_output->swaynag->type->output;
264 wlr_log(WLR_DEBUG, "Checking against output %s for %s", name, outname);
265 if (!swaynag_output->swaynag->output && outname && name
266 && strcmp(outname, name) == 0) {
267 wlr_log(WLR_DEBUG, "Using output %s", name);
268 swaynag_output->swaynag->output = swaynag_output;
269 }
270 swaynag_output->name = strdup(name);
271 zxdg_output_v1_destroy(xdg_output);
272 swaynag_output->swaynag->querying_outputs--;
273}
274
275static struct zxdg_output_v1_listener xdg_output_listener = {
276 .logical_position = nop,
277 .logical_size = nop,
278 .done = nop,
279 .name = xdg_output_handle_name,
280 .description = nop,
281};
282
283static void handle_global(void *data, struct wl_registry *registry,
284 uint32_t name, const char *interface, uint32_t version) {
285 struct swaynag *swaynag = data;
286 if (strcmp(interface, wl_compositor_interface.name) == 0) {
287 swaynag->compositor = wl_registry_bind(registry, name,
288 &wl_compositor_interface, 3);
289 } else if (strcmp(interface, wl_seat_interface.name) == 0) {
290 swaynag->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
291 wl_seat_add_listener(swaynag->seat, &seat_listener, swaynag);
292 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
293 swaynag->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
294 } else if (strcmp(interface, wl_output_interface.name) == 0) {
295 if (!swaynag->output && swaynag->xdg_output_manager) {
296 swaynag->querying_outputs++;
297 struct swaynag_output *output =
298 calloc(1, sizeof(struct swaynag_output));
299 output->wl_output = wl_registry_bind(registry, name,
300 &wl_output_interface, 3);
301 output->wl_name = name;
302 output->scale = 1;
303 output->swaynag = swaynag;
304 wl_list_insert(&swaynag->outputs, &output->link);
305 wl_output_add_listener(output->wl_output,
306 &output_listener, output);
307
308 struct zxdg_output_v1 *xdg_output;
309 xdg_output = zxdg_output_manager_v1_get_xdg_output(
310 swaynag->xdg_output_manager, output->wl_output);
311 zxdg_output_v1_add_listener(xdg_output,
312 &xdg_output_listener, output);
313 }
314 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
315 swaynag->layer_shell = wl_registry_bind(
316 registry, name, &zwlr_layer_shell_v1_interface, 1);
317 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0
318 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
319 swaynag->xdg_output_manager = wl_registry_bind(registry, name,
320 &zxdg_output_manager_v1_interface,
321 ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
322 }
323}
324
325static void handle_global_remove(void *data, struct wl_registry *registry,
326 uint32_t name) {
327 struct swaynag *swaynag = data;
328 if (swaynag->output->wl_name == name) {
329 swaynag->run_display = false;
330 }
331}
332
333static const struct wl_registry_listener registry_listener = {
334 .global = handle_global,
335 .global_remove = handle_global_remove,
336};
337
338void swaynag_setup(struct swaynag *swaynag) {
339 swaynag->display = wl_display_connect(NULL);
340 assert(swaynag->display);
341
342 swaynag->scale = 1;
343 wl_list_init(&swaynag->outputs);
344
345 struct wl_registry *registry = wl_display_get_registry(swaynag->display);
346 wl_registry_add_listener(registry, &registry_listener, swaynag);
347 wl_display_roundtrip(swaynag->display);
348 assert(swaynag->compositor && swaynag->layer_shell && swaynag->shm);
349
350 while (swaynag->querying_outputs > 0) {
351 wl_display_roundtrip(swaynag->display);
352 }
353
354 if (!swaynag->output && swaynag->type->output) {
355 wlr_log(WLR_ERROR, "Output '%s' not found", swaynag->type->output);
356 swaynag_destroy(swaynag);
357 exit(EXIT_FAILURE);
358 }
359
360 struct swaynag_pointer *pointer = &swaynag->pointer;
361 pointer->cursor_surface = wl_compositor_create_surface(swaynag->compositor);
362 assert(pointer->cursor_surface);
363
364 swaynag->surface = wl_compositor_create_surface(swaynag->compositor);
365 assert(swaynag->surface);
366 wl_surface_add_listener(swaynag->surface, &surface_listener, swaynag);
367
368 swaynag->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
369 swaynag->layer_shell, swaynag->surface,
370 swaynag->output ? swaynag->output->wl_output : NULL,
371 ZWLR_LAYER_SHELL_V1_LAYER_TOP, "swaynag");
372 assert(swaynag->layer_surface);
373 zwlr_layer_surface_v1_add_listener(swaynag->layer_surface,
374 &layer_surface_listener, swaynag);
375 zwlr_layer_surface_v1_set_anchor(swaynag->layer_surface,
376 swaynag->type->anchors);
377
378 wl_registry_destroy(registry);
379}
380
381void swaynag_run(struct swaynag *swaynag) {
382 swaynag->run_display = true;
383 render_frame(swaynag);
384 while (swaynag->run_display
385 && wl_display_dispatch(swaynag->display) != -1) {
386 // This is intentionally left blank
387 }
388}
389
390void swaynag_destroy(struct swaynag *swaynag) {
391 swaynag->run_display = false;
392
393 free(swaynag->message);
394 while (swaynag->buttons->length) {
395 struct swaynag_button *button = swaynag->buttons->items[0];
396 list_del(swaynag->buttons, 0);
397 free(button->text);
398 free(button->action);
399 free(button);
400 }
401 list_free(swaynag->buttons);
402 free(swaynag->details.message);
403 free(swaynag->details.button_up.text);
404 free(swaynag->details.button_down.text);
405
406 if (swaynag->type) {
407 swaynag_type_free(swaynag->type);
408 }
409
410 if (swaynag->layer_surface) {
411 zwlr_layer_surface_v1_destroy(swaynag->layer_surface);
412 }
413
414 if (swaynag->surface) {
415 wl_surface_destroy(swaynag->surface);
416 }
417
418 if (swaynag->pointer.cursor_theme) {
419 wl_cursor_theme_destroy(swaynag->pointer.cursor_theme);
420 }
421
422 if (&swaynag->buffers[0]) {
423 destroy_buffer(&swaynag->buffers[0]);
424 }
425
426 if (&swaynag->buffers[1]) {
427 destroy_buffer(&swaynag->buffers[1]);
428 }
429
430 if (swaynag->outputs.prev || swaynag->outputs.next) {
431 struct swaynag_output *output, *temp;
432 wl_list_for_each_safe(output, temp, &swaynag->outputs, link) {
433 wl_output_destroy(output->wl_output);
434 free(output->name);
435 wl_list_remove(&output->link);
436 free(output);
437 };
438 }
439
440 if (swaynag->compositor) {
441 wl_compositor_destroy(swaynag->compositor);
442 }
443
444 if (swaynag->shm) {
445 wl_shm_destroy(swaynag->shm);
446 }
447
448 if (swaynag->display) {
449 wl_display_disconnect(swaynag->display);
450 }
451}
diff --git a/swaynag/types.c b/swaynag/types.c
new file mode 100644
index 00000000..1e0a138b
--- /dev/null
+++ b/swaynag/types.c
@@ -0,0 +1,156 @@
1#define _XOPEN_SOURCE 500
2#include <getopt.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <stdint.h>
6#include <string.h>
7#include <strings.h>
8#include "list.h"
9#include "swaynag/config.h"
10#include "swaynag/types.h"
11#include "util.h"
12#include "wlr-layer-shell-unstable-v1-client-protocol.h"
13
14void swaynag_types_add_default(list_t *types) {
15 struct swaynag_type *type_defaults;
16 type_defaults = calloc(1, sizeof(struct swaynag_type));
17 type_defaults->name = strdup("<defaults>");
18 type_defaults->font = strdup("pango:Monospace 10");
19 type_defaults->anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
20 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
21 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
22 type_defaults->button_background = 0x333333FF;
23 type_defaults->background = 0x323232FF;
24 type_defaults->text = 0xFFFFFFFF;
25 type_defaults->border = 0x222222FF;
26 type_defaults->border_bottom = 0x444444FF;
27 type_defaults->bar_border_thickness = 2;
28 type_defaults->message_padding = 8;
29 type_defaults->details_border_thickness = 3;
30 type_defaults->button_border_thickness = 3;
31 type_defaults->button_gap = 20;
32 type_defaults->button_gap_close = 15;
33 type_defaults->button_margin_right = 2;
34 type_defaults->button_padding = 3;
35 list_add(types, type_defaults);
36
37 struct swaynag_type *type_error;
38 type_error = calloc(1, sizeof(struct swaynag_type));
39 type_error->button_background = 0x680A0AFF;
40 type_error->background = 0x900000FF;
41 type_error->text = 0xFFFFFFFF;
42 type_error->border = 0xD92424FF;
43 type_error->border_bottom = 0x470909FF;
44 type_error->name = strdup("error");
45 list_add(types, type_error);
46
47 struct swaynag_type *type_warning;
48 type_warning = calloc(1, sizeof(struct swaynag_type));
49 type_warning->name = strdup("warning");
50 type_warning->button_background = 0xFFC100FF;
51 type_warning->background = 0xFFA800FF;
52 type_warning->text = 0x000000FF;
53 type_warning->border = 0xAB7100FF;
54 type_warning->border_bottom = 0xAB7100FF;
55 list_add(types, type_warning);
56}
57
58struct swaynag_type *swaynag_type_get(list_t *types, char *name) {
59 for (int i = 0; i < types->length; i++) {
60 struct swaynag_type *type = types->items[i];
61 if (strcasecmp(type->name, name) == 0) {
62 return type;
63 }
64 }
65 return NULL;
66}
67
68void swaynag_type_merge(struct swaynag_type *dest, struct swaynag_type *src) {
69 if (!dest || !src) {
70 return;
71 }
72
73 if (!dest->font && src->font) {
74 dest->font = strdup(src->font);
75 }
76
77 if (!dest->output && src->output) {
78 dest->output = strdup(src->output);
79 }
80
81 if (dest->anchors == 0 && src->anchors > 0) {
82 dest->anchors = src->anchors;
83 }
84
85 // Colors
86 if (dest->button_background == 0 && src->button_background > 0) {
87 dest->button_background = src->button_background;
88 }
89
90 if (dest->background == 0 && src->background > 0) {
91 dest->background = src->background;
92 }
93
94 if (dest->text == 0 && src->text > 0) {
95 dest->text = src->text;
96 }
97
98 if (dest->border == 0 && src->border > 0) {
99 dest->border = src->border;
100 }
101
102 if (dest->border_bottom == 0 && src->border_bottom > 0) {
103 dest->border_bottom = src->border_bottom;
104 }
105
106 // Sizing
107 if (dest->bar_border_thickness == 0 && src->bar_border_thickness > 0) {
108 dest->bar_border_thickness = src->bar_border_thickness;
109 }
110
111 if (dest->message_padding == 0 && src->message_padding > 0) {
112 dest->message_padding = src->message_padding;
113 }
114
115 if (dest->details_border_thickness == 0
116 && src->details_border_thickness > 0) {
117 dest->details_border_thickness = src->details_border_thickness;
118 }
119
120 if (dest->button_border_thickness == 0
121 && src->button_border_thickness > 0) {
122 dest->button_border_thickness = src->button_border_thickness;
123 }
124
125 if (dest->button_gap == 0 && src->button_gap > 0) {
126 dest->button_gap = src->button_gap;
127 }
128
129 if (dest->button_gap_close == 0 && src->button_gap_close > 0) {
130 dest->button_gap_close = src->button_gap_close;
131 }
132
133 if (dest->button_margin_right == 0 && src->button_margin_right > 0) {
134 dest->button_margin_right = src->button_margin_right;
135 }
136
137 if (dest->button_padding == 0 && src->button_padding > 0) {
138 dest->button_padding = src->button_padding;
139 }
140}
141
142void swaynag_type_free(struct swaynag_type *type) {
143 free(type->name);
144 free(type->font);
145 free(type->output);
146 free(type);
147}
148
149void swaynag_types_free(list_t *types) {
150 while (types->length) {
151 struct swaynag_type *type = types->items[0];
152 swaynag_type_free(type);
153 list_del(types, 0);
154 }
155 list_free(types);
156}