aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.bg.md7
-rw-r--r--README.de.md9
-rw-r--r--README.el.md2
-rw-r--r--README.fr.md8
-rw-r--r--README.it.md9
-rw-r--r--README.ja.md3
-rw-r--r--README.md8
-rw-r--r--README.pt.md6
-rw-r--r--README.ru.md8
-rw-r--r--README.uk.md10
-rw-r--r--completions/bash/swaybar44
-rw-r--r--completions/zsh/_sway4
-rw-r--r--completions/zsh/_swaybar13
-rw-r--r--completions/zsh/_swayidle22
-rw-r--r--completions/zsh/_swaylock40
-rw-r--r--completions/zsh/_swaymsg12
-rw-r--r--include/sway/commands.h9
-rw-r--r--include/sway/config.h13
-rw-r--r--include/sway/input/cursor.h4
-rw-r--r--include/sway/input/input-manager.h6
-rw-r--r--include/sway/input/seat.h6
-rw-r--r--include/swaybar/bar.h12
-rw-r--r--include/swaybar/config.h9
-rw-r--r--include/swaybar/tray/host.h17
-rw-r--r--include/swaybar/tray/icon.h52
-rw-r--r--include/swaybar/tray/item.h45
-rw-r--r--include/swaybar/tray/sni.h82
-rw-r--r--include/swaybar/tray/tray.h40
-rw-r--r--include/swaybar/tray/watcher.h18
-rw-r--r--meson.build2
-rw-r--r--meson_options.txt1
-rw-r--r--sway/commands.c2
-rw-r--r--sway/commands/bar.c4
-rw-r--r--sway/commands/bar/activate_button.c8
-rw-r--r--sway/commands/bar/context_button.c8
-rw-r--r--sway/commands/bar/icon_theme.c25
-rw-r--r--sway/commands/bar/secondary_button.c8
-rw-r--r--sway/commands/bar/tray_bindsym.c55
-rw-r--r--sway/commands/bar/tray_output.c39
-rw-r--r--sway/commands/bar/tray_padding.c37
-rw-r--r--sway/commands/seat.c1
-rw-r--r--sway/commands/seat/hide_cursor.c (renamed from sway/commands/hide_cursor.c)14
-rw-r--r--sway/commands/tiling_drag_threshold.c22
-rw-r--r--sway/config.c4
-rw-r--r--sway/config/bar.c9
-rw-r--r--sway/config/seat.c5
-rw-r--r--sway/desktop/render.c2
-rw-r--r--sway/desktop/transaction.c4
-rw-r--r--sway/input/cursor.c93
-rw-r--r--sway/input/input-manager.c22
-rw-r--r--sway/input/seat.c29
-rw-r--r--sway/ipc-json.c37
-rw-r--r--sway/meson.build7
-rw-r--r--sway/server.c2
-rw-r--r--sway/sway-bar.5.scd25
-rw-r--r--sway/sway-input.5.scd6
-rw-r--r--sway/sway-output.5.scd12
-rw-r--r--sway/sway.1.scd10
-rw-r--r--sway/sway.5.scd27
-rw-r--r--sway/tree/container.c17
-rw-r--r--sway/tree/view.c10
-rw-r--r--swaybar/bar.c28
-rw-r--r--swaybar/config.c12
-rw-r--r--swaybar/ipc.c35
-rw-r--r--swaybar/meson.build44
-rw-r--r--swaybar/render.c9
-rw-r--r--swaybar/status_line.c1
-rw-r--r--swaybar/tray/host.c210
-rw-r--r--swaybar/tray/icon.c462
-rw-r--r--swaybar/tray/item.c443
-rw-r--r--swaybar/tray/tray.c127
-rw-r--r--swaybar/tray/watcher.c212
-rw-r--r--swayidle/main.c13
-rw-r--r--swaylock/password.c8
-rw-r--r--swaylock/seat.c6
75 files changed, 2368 insertions, 307 deletions
diff --git a/README.bg.md b/README.bg.md
index efc99f15..87a15bed 100644
--- a/README.bg.md
+++ b/README.bg.md
@@ -1,8 +1,9 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor" е в процес на разработка, съвместим с i3, [Wayland](http://wayland.freedesktop.org/) композитор. 3Sway е в процес на разработка, съвместим с i3,
4Прочетете [FAQ](https://github.com/swaywm/sway/wiki). Присъединете се в 4[Wayland](http://wayland.freedesktop.org/) композитор. Прочетете
5[IRC канала](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на 5[FAQ](https://github.com/swaywm/sway/wiki). Присъединете се в [IRC
6канала](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на
6irc.freenode.net). 7irc.freenode.net).
7 8
8[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
diff --git a/README.de.md b/README.de.md
index 2c2e14e8..bf92b196 100644
--- a/README.de.md
+++ b/README.de.md
@@ -3,10 +3,11 @@
3Der Fortschritt dieser Übersetzung kann [hier](https://github.com/swaywm/sway/issues/1318) 3Der Fortschritt dieser Übersetzung kann [hier](https://github.com/swaywm/sway/issues/1318)
4eingesehen werden. 4eingesehen werden.
5 5
6"**S**irCmpwn's **Way**land compositor" ist ein i3-kompatibler 6Sway ist ein i3-kompatibler
7[Wayland](http://wayland.freedesktop.org/)-Kompositor. Lies die 7[Wayland](http://wayland.freedesktop.org/)-Kompositor. Lies die
8[FAQ](https://github.com/swaywm/sway/wiki#faq). Tritt dem 8[FAQ](https://github.com/swaywm/sway/wiki#faq). Tritt dem
9[IRC-Channel](http://webchat.freenode.net/?channels=sway&uio=d4) bei (#sway in irc.freenode.net). 9[IRC-Channel](http://webchat.freenode.net/?channels=sway&uio=d4) bei (#sway in
10irc.freenode.net).
10 11
11[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 12[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
12 13
diff --git a/README.el.md b/README.el.md
index 3c9d65c9..79acd422 100644
--- a/README.el.md
+++ b/README.el.md
@@ -1,6 +1,6 @@
1# sway 1# sway
2 2
3"Ο Sway (**S**irCmpwn's **Way**land) είναι ένας **υπό ανάπτυξη** [Wayland](http://wayland.freedesktop.org/) διαχειριστής παραθύρων συμβατός με τον αντίστοιχο διαχειριστή παραθύρων i3 για τον X11. 3Sway είναι ένας **υπό ανάπτυξη** [Wayland](http://wayland.freedesktop.org/) διαχειριστής παραθύρων συμβατός με τον αντίστοιχο διαχειριστή παραθύρων i3 για τον X11.
4Διαβάστε τις [Συνήθεις Ερωτήσεις](https://github.com/swaywm/sway/wiki). Συνδεθείτε στο [κανάλι μας στο IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway στο 4Διαβάστε τις [Συνήθεις Ερωτήσεις](https://github.com/swaywm/sway/wiki). Συνδεθείτε στο [κανάλι μας στο IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway στο
5irc.freenode.net). 5irc.freenode.net).
6 6
diff --git a/README.fr.md b/README.fr.md
index 935d288f..8ad4d3b6 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -1,9 +1,9 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor" est un compositeur [Wayland](http://wayland.freedesktop.org/) 3Sway est un compositeur [Wayland](http://wayland.freedesktop.org/) compatible
4compatible avec i3, **en cours de développement**. 4avec i3, **en cours de développement**. Lisez la
5Lisez la [FAQ](https://github.com/swaywm/sway/wiki). Rejoignez le 5[FAQ](https://github.com/swaywm/sway/wiki). Rejoignez le [canal
6[canal IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway sur 6IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway sur
7irc.freenode.net). 7irc.freenode.net).
8 8
9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
diff --git a/README.it.md b/README.it.md
index af986844..8a83bc78 100644
--- a/README.it.md
+++ b/README.it.md
@@ -1,10 +1,9 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor" è un compositor 3Sway è un compositor [Wayland](http://wayland.freedesktop.org/) **in via di
4[Wayland](http://wayland.freedesktop.org/) **in via di sviluppo** 4sviluppo** compatibile con i3. Leggi le [FAQ (in
5compatibile con i3. 5Inglese)](https://github.com/swaywm/sway/wiki). Unisciti al [canale IRC (in
6Leggi le [FAQ (in Inglese)](https://github.com/swaywm/sway/wiki). Unisciti al 6Inglese)](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on
7[canale IRC (in Inglese)](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on
8irc.freenode.net). 7irc.freenode.net).
9 8
10[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
diff --git a/README.ja.md b/README.ja.md
index b9e541f0..d82e78b2 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -1,7 +1,6 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor"は**開発中**の 3Swayは**開発中**のi3互換な[Wayland](http://wayland.freedesktop.org/)コンポジタです。
4i3互換な[Wayland](http://wayland.freedesktop.org/)コンポジタです。
5[FAQ](https://github.com/swaywm/sway/wiki)も合わせてご覧ください。 4[FAQ](https://github.com/swaywm/sway/wiki)も合わせてご覧ください。
6[IRC チャンネル](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net)もあります。 5[IRC チャンネル](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net)もあります。
7 6
diff --git a/README.md b/README.md
index 6cb212fc..1deb9a4c 100644
--- a/README.md
+++ b/README.md
@@ -3,10 +3,10 @@
3[**English**](https://github.com/swaywm/sway/blob/master/README.md#sway--) - [日本語](https://github.com/swaywm/sway/blob/master/README.ja.md#sway--) - [Deutsch](https://github.com/swaywm/sway/blob/master/README.de.md#sway--) - [Ελληνικά](https://github.com/swaywm/sway/blob/master/README.el.md#sway--) - [Français](https://github.com/swaywm/sway/blob/master/README.fr.md#sway--) - [Українська](https://github.com/swaywm/sway/blob/master/README.uk.md#sway--) - [Italiano](https://github.com/swaywm/sway/blob/master/README.it.md#sway--) - [Português](https://github.com/swaywm/sway/blob/master/README.pt.md#sway--) - 3[**English**](https://github.com/swaywm/sway/blob/master/README.md#sway--) - [日本語](https://github.com/swaywm/sway/blob/master/README.ja.md#sway--) - [Deutsch](https://github.com/swaywm/sway/blob/master/README.de.md#sway--) - [Ελληνικά](https://github.com/swaywm/sway/blob/master/README.el.md#sway--) - [Français](https://github.com/swaywm/sway/blob/master/README.fr.md#sway--) - [Українська](https://github.com/swaywm/sway/blob/master/README.uk.md#sway--) - [Italiano](https://github.com/swaywm/sway/blob/master/README.it.md#sway--) - [Português](https://github.com/swaywm/sway/blob/master/README.pt.md#sway--) -
4[Русский](https://github.com/swaywm/sway/blob/master/README.ru.md#sway--) - [Български](https://github.com/swaywm/sway/blob/master/README.bg.md#sway--) 4[Русский](https://github.com/swaywm/sway/blob/master/README.ru.md#sway--) - [Български](https://github.com/swaywm/sway/blob/master/README.bg.md#sway--)
5 5
6"**S**irCmpwn's **Way**land compositor" is a **work in progress** 6sway is a **work in progress** i3-compatible
7i3-compatible [Wayland](http://wayland.freedesktop.org/) compositor. 7[Wayland](http://wayland.freedesktop.org/) compositor. Read the
8Read the [FAQ](https://github.com/swaywm/sway/wiki). Join the 8[FAQ](https://github.com/swaywm/sway/wiki). Join the [IRC
9[IRC channel](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on 9channel](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on
10irc.freenode.net). 10irc.freenode.net).
11 11
12If you'd like to support sway development, please contribute to [SirCmpwn's 12If you'd like to support sway development, please contribute to [SirCmpwn's
diff --git a/README.pt.md b/README.pt.md
index 91e709c1..8cdfa548 100644
--- a/README.pt.md
+++ b/README.pt.md
@@ -1,8 +1,8 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor" é um compositor [Wayland](http://wayland.freedesktop.org/) 3Sway é um compositor [Wayland](http://wayland.freedesktop.org/) compatível com o
4compatível com o i3. Leia o [FAQ](https://github.com/swaywm/sway/wiki). Participe do 4i3. Leia o [FAQ](https://github.com/swaywm/sway/wiki). Participe do [canal
5[canal IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway no 5IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway no
6irc.freenode.net). 6irc.freenode.net).
7 7
8[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 8[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
diff --git a/README.ru.md b/README.ru.md
index 4b34dc2d..255e36aa 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -1,9 +1,9 @@
1# sway 1# sway
2 2
3"**S**irCmpwn's **Way**land compositor" на данный момент **(в разработке)** 3Sway на данный момент **(в разработке)** i3-совместимый
4i3-совместимый [Wayland](http://wayland.freedesktop.org/) композитор. 4[Wayland](http://wayland.freedesktop.org/) композитор. Прочитайте
5Прочитайте [FAQ](https://github.com/swaywm/sway/wiki). Присоединяйтесь к 5[FAQ](https://github.com/swaywm/sway/wiki). Присоединяйтесь к [IRC
6[IRC каналу](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на 6каналу](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на
7irc.freenode.net). 7irc.freenode.net).
8 8
9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
diff --git a/README.uk.md b/README.uk.md
index 9ae7425a..5e9345b3 100644
--- a/README.uk.md
+++ b/README.uk.md
@@ -1,10 +1,10 @@
1# sway 1# sway
2 2
3**Sway** ("**S**irCmpwn's **Way**land compositor") це сумісний з i3 композитор 3**Sway** це сумісний з i3 композитор [Wayland](http://wayland.freedesktop.org/)
4[Wayland](http://wayland.freedesktop.org/) (**у стані розробки**). 4(**у стані розробки**). Ознайомтесь з
5Ознайомтесь з [ЧаПами](https://github.com/swaywm/sway/wiki). 5[ЧаПами](https://github.com/swaywm/sway/wiki). Приєднуйтесь до [спільноти в
6Приєднуйтесь до [спільноти в IRC](http://webchat.freenode.net/?channels=sway&uio=d4) 6IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на
7(#sway на irc.freenode.net). 7irc.freenode.net).
8 8
9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) 9[![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png)
10 10
diff --git a/completions/bash/swaybar b/completions/bash/swaybar
new file mode 100644
index 00000000..1e085c65
--- /dev/null
+++ b/completions/bash/swaybar
@@ -0,0 +1,44 @@
1# swaybar(1) completion
2
3_swaybar()
4{
5 local cur prev
6 _get_comp_words_by_ref cur prev
7
8 short=(
9 -h
10 -v
11 -s
12 -b
13 -d
14 )
15
16 long=(
17 --help
18 --version
19 --socket
20 --bar_id
21 --debug
22 )
23
24 case $prev in
25 -s|--socket)
26 _filedir
27 return
28 ;;
29 -b|--bar_id)
30 bars=($(swaymsg -t get_bar_config | jq -r '.[]'))
31 COMPREPLY=($(compgen -W "${bars[*]}" -- "$cur"))
32 return
33 ;;
34 esac
35
36 if [[ $cur == --* ]]; then
37 COMPREPLY=($(compgen -W "${long[*]}" -- "$cur"))
38 else
39 COMPREPLY=($(compgen -W "${short[*]}" -- "$cur"))
40 COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur"))
41 fi
42
43} &&
44complete -F _swaybar swaybar
diff --git a/completions/zsh/_sway b/completions/zsh/_sway
index 05112002..a7f55cc5 100644
--- a/completions/zsh/_sway
+++ b/completions/zsh/_sway
@@ -13,8 +13,8 @@
13# 13#
14# ------------------------------- 14# -------------------------------
15_arguments -s \ 15_arguments -s \
16 '(-v --version)'{-v,--version}'[shows version]' \ 16 '(-v --version)'{-v,--version}'[Show the version number and quit]' \
17 '(-h --help)'{-h,--help}'[shows help message]' \ 17 '(-h --help)'{-h,--help}'[Show help message and quit]' \
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]' \
diff --git a/completions/zsh/_swaybar b/completions/zsh/_swaybar
new file mode 100644
index 00000000..af2cee95
--- /dev/null
+++ b/completions/zsh/_swaybar
@@ -0,0 +1,13 @@
1#compdef swaybar
2#
3# Completion script for swaybar
4#
5
6local bars=($(swaymsg -t get_bar_config | jq -r '.[]'))
7
8_arguments -s \
9 '(-h --help)'{-h,--help}'[Show help message and quit]' \
10 '(-v --version)'{-v,--version}'[Show version and quit]' \
11 '(-s --socket)'{-s,--socket}'[Connect to sway via socket]:filename:_files' \
12 '(-b --bar_id)'{-b,--bar-id}'[Bar ID for which to get the configuration]:filename:($bars)'\
13 '(-d --debug)'{-d,--debug}'[Enable debugging]'
diff --git a/completions/zsh/_swayidle b/completions/zsh/_swayidle
new file mode 100644
index 00000000..b419bc2c
--- /dev/null
+++ b/completions/zsh/_swayidle
@@ -0,0 +1,22 @@
1#compdef swayidle
2#
3# Completion script for swayidle
4#
5
6local events=('timeout:Execute timeout command if there is no activity for timeout seconds'
7 'before-sleep:Execute before-sleep command before sleep')
8local resume=('resume:Execute command when there is activity again')
9
10if (($#words <= 2)); then
11 _arguments -C \
12 '(-h --help)'{-h,--help}'[Show help message and quit]' \
13 '(-d)'-d'[Enable debug output]'
14 _describe -t "events" 'swayidle' events
15
16elif [[ "$words[-3]" == before-sleep || "$words[-3]" == resume ]]; then
17 _describe -t "events" 'swayidle' events
18
19elif [[ "$words[-4]" == timeout ]]; then
20 _describe -t "events" 'swayidle' events
21 _describe -t "resume" 'swayidle' resume
22fi
diff --git a/completions/zsh/_swaylock b/completions/zsh/_swaylock
index 8fb4834c..9bc84ec9 100644
--- a/completions/zsh/_swaylock
+++ b/completions/zsh/_swaylock
@@ -4,11 +4,37 @@
4# 4#
5 5
6_arguments -s \ 6_arguments -s \
7 '(-v --version)'{-v,--version}'[Show the version number and quit]' \ 7 '(-C --config)'{-C,--config}'[Path to the config file]:filename:_files' \
8 '(-h --help)'{-h,--help}'[Show help message and quit]' \ 8 '(-c --color)'{-c,--color}'[Turn the screen into the given color instead of white]:color:' \
9 '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal]' \ 9 '(-e --ignore-empty-password)'{-e,--ignore-empty-password}'[When an empty password is provided, do not validate it]' \
10 '(-c --color)'{-c,--color}'[Specify a color (rrggbb)]' \ 10 '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal after locking]' \
11 '(-i --image)'{-i,--image}'[Display an image]:files:_files' \ 11 '(-h --help)'{-h,--help}'[Show help message and quit]' \
12 '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \ 12 '(-i --image)'{-i,--image}'[Display an image]:filename:_files' \
13 '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \
14 '(-t --tiling)'{-t,--tiling}'[Same as --scaling=tile]' \
13 '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' \ 15 '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' \
14 '(--socket)'--socket'[Use the specified socket path.]:files:_files' \ 16 '(-v --version)'{-v,--version}'[Show the version number and quit]' \
17 '(--bs-hl-color)'--bs-hl-color'[Sets the color of backspace highlights segments]:color:' \
18 '(--font)'--font'[Sets the font of the text]:font:' \
19 '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \
20 '(--indicator-thickness)'--indicator-thickness'[Sets the indicator thickness]:thickness:' \
21 '(--inside-color)'--inside-color'[Sets the color of the inside of the indicator]:color:' \
22 '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when cleared]:color:' \
23 '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when verifying]:color:' \
24 '(--inside-wrong-color)'--inside-wrong-color'[Sets the color of the inside of the indicator when invalid]:color:' \
25 '(--key-hl-color)'--key-hl-color'[Sets the color of the key press highlight segments]:color:' \
26 '(--line-color)'--line-color'[Sets the color of the line between the inside and ring]:color:' \
27 '(--line-clear-color)'--line-clear-color'[Sets the color of the line between the inside and ring when cleared]:color:' \
28 '(--line-ver-color)'--line-ver-color'[Sets the color of the line between the inside and ring when verifying]:color:' \
29 '(--line-wrong-color)'--line-wrong-color'[Sets the color of the line between the inside and ring when invalid]:color:' \
30 '(-n --line-uses-inside)'{-n,--line-uses-inside}'[Use the inside color for the line between the inside and ring]' \
31 '(-r --line-uses-ring)'{-r,--line--uses-ring}'[Use the ring color for the line between the inside and ring]' \
32 '(--ring-color)'--ring-color'[Sets the color of the ring of the indicator]:color:' \
33 '(--ring-clear-color)'--ring-clear-color'[Sets the color of the ring of the indicator when cleared]:color:' \
34 '(--ring-ver-color)'--ring-ver-color'[Sets the color of the ring of the indicator when verifying]:color:' \
35 '(--ring-wrong-color)'--ring-wrong-color'[Sets the color of the ring of the indicator when invalid]:color:' \
36 '(--separator-color)'--separator-color'[Sets the color of the lines that separate highlight segments]:color:' \
37 '(--text-color)'--text-color'[Sets the color of the text]:color:' \
38 '(--text-clear-color)'--text-clear-color'[Sets the color of the text when cleared]:color:' \
39 '(--text-ver-color)'--text-ver-color'[Sets the color of the text when verifying]:color:' \
40 '(--text-wrong-color)'--text-wrong-color'[Sets the color of the text when invalid]:color:' \ No newline at end of file
diff --git a/completions/zsh/_swaymsg b/completions/zsh/_swaymsg
index a7a1c8e0..0ba45d4a 100644
--- a/completions/zsh/_swaymsg
+++ b/completions/zsh/_swaymsg
@@ -28,8 +28,10 @@ types=(
28) 28)
29 29
30_arguments -s \ 30_arguments -s \
31 '(-v --version)'{-v,--version}'[Print the version (of swaymsg) and quit]' \ 31 '(-v --version)'{-v,--version}'[Show the version number and quit]' \
32 '(-h --help)'{-h,--help}'[Shows help message]' \ 32 '(-m --monitor)'{-m,--monitor}'[Monitor until killed (-t SUBSCRIBE only)]' \
33 '(-q --quiet)'{-q,--quiet}'[Sends the IPC message but does not print the response from sway]' \ 33 '(-h --help)'{-h,--help}'[Show help message and quit]' \
34 '(-s --socket)'{-s,--socket}'[Use the specified socket path.]:files:_files' \ 34 '(-q --quiet)'{-q,--quiet}'[Be quiet]' \
35 '(-t --type)'{-t,--type}'[Specify the type of IPC message.]:type:{_describe "type" types}' 35 '(-r --raw)'{-r,--raw}'[Use raw output even if using a tty]' \
36 '(-s --socket)'{-s,--socket}'[Use the specified socket path]:files:_files' \
37 '(-t --type)'{-t,--type}'[Specify the message type]:type:{_describe "type" types}'
diff --git a/include/sway/commands.h b/include/sway/commands.h
index 89e18c66..7d0ff838 100644
--- a/include/sway/commands.h
+++ b/include/sway/commands.h
@@ -133,7 +133,6 @@ sway_cmd cmd_force_display_urgency_hint;
133sway_cmd cmd_force_focus_wrapping; 133sway_cmd cmd_force_focus_wrapping;
134sway_cmd cmd_fullscreen; 134sway_cmd cmd_fullscreen;
135sway_cmd cmd_gaps; 135sway_cmd cmd_gaps;
136sway_cmd cmd_hide_cursor;
137sway_cmd cmd_hide_edge_borders; 136sway_cmd cmd_hide_edge_borders;
138sway_cmd cmd_include; 137sway_cmd cmd_include;
139sway_cmd cmd_input; 138sway_cmd cmd_input;
@@ -173,6 +172,7 @@ sway_cmd cmd_swaybg_command;
173sway_cmd cmd_swaynag_command; 172sway_cmd cmd_swaynag_command;
174sway_cmd cmd_swap; 173sway_cmd cmd_swap;
175sway_cmd cmd_tiling_drag; 174sway_cmd cmd_tiling_drag;
175sway_cmd cmd_tiling_drag_threshold;
176sway_cmd cmd_title_align; 176sway_cmd cmd_title_align;
177sway_cmd cmd_title_format; 177sway_cmd cmd_title_format;
178sway_cmd cmd_titlebar_border_thickness; 178sway_cmd cmd_titlebar_border_thickness;
@@ -183,11 +183,9 @@ sway_cmd cmd_workspace;
183sway_cmd cmd_ws_auto_back_and_forth; 183sway_cmd cmd_ws_auto_back_and_forth;
184sway_cmd cmd_workspace_layout; 184sway_cmd cmd_workspace_layout;
185 185
186sway_cmd bar_cmd_activate_button;
187sway_cmd bar_cmd_binding_mode_indicator; 186sway_cmd bar_cmd_binding_mode_indicator;
188sway_cmd bar_cmd_bindsym; 187sway_cmd bar_cmd_bindsym;
189sway_cmd bar_cmd_colors; 188sway_cmd bar_cmd_colors;
190sway_cmd bar_cmd_context_button;
191sway_cmd bar_cmd_font; 189sway_cmd bar_cmd_font;
192sway_cmd bar_cmd_gaps; 190sway_cmd bar_cmd_gaps;
193sway_cmd bar_cmd_mode; 191sway_cmd bar_cmd_mode;
@@ -198,13 +196,13 @@ sway_cmd bar_cmd_hidden_state;
198sway_cmd bar_cmd_icon_theme; 196sway_cmd bar_cmd_icon_theme;
199sway_cmd bar_cmd_id; 197sway_cmd bar_cmd_id;
200sway_cmd bar_cmd_position; 198sway_cmd bar_cmd_position;
201sway_cmd bar_cmd_secondary_button;
202sway_cmd bar_cmd_separator_symbol; 199sway_cmd bar_cmd_separator_symbol;
203sway_cmd bar_cmd_status_command; 200sway_cmd bar_cmd_status_command;
204sway_cmd bar_cmd_pango_markup; 201sway_cmd bar_cmd_pango_markup;
205sway_cmd bar_cmd_strip_workspace_numbers; 202sway_cmd bar_cmd_strip_workspace_numbers;
206sway_cmd bar_cmd_strip_workspace_name; 203sway_cmd bar_cmd_strip_workspace_name;
207sway_cmd bar_cmd_swaybar_command; 204sway_cmd bar_cmd_swaybar_command;
205sway_cmd bar_cmd_tray_bindsym;
208sway_cmd bar_cmd_tray_output; 206sway_cmd bar_cmd_tray_output;
209sway_cmd bar_cmd_tray_padding; 207sway_cmd bar_cmd_tray_padding;
210sway_cmd bar_cmd_wrap_scroll; 208sway_cmd bar_cmd_wrap_scroll;
@@ -260,8 +258,9 @@ sway_cmd output_cmd_scale;
260sway_cmd output_cmd_transform; 258sway_cmd output_cmd_transform;
261 259
262sway_cmd seat_cmd_attach; 260sway_cmd seat_cmd_attach;
263sway_cmd seat_cmd_fallback;
264sway_cmd seat_cmd_cursor; 261sway_cmd seat_cmd_cursor;
262sway_cmd seat_cmd_fallback;
263sway_cmd seat_cmd_hide_cursor;
265 264
266sway_cmd cmd_ipc_cmd; 265sway_cmd cmd_ipc_cmd;
267sway_cmd cmd_ipc_events; 266sway_cmd cmd_ipc_events;
diff --git a/include/sway/config.h b/include/sway/config.h
index 6610f009..ebf16e6a 100644
--- a/include/sway/config.h
+++ b/include/sway/config.h
@@ -6,6 +6,7 @@
6#include <time.h> 6#include <time.h>
7#include <wlr/types/wlr_box.h> 7#include <wlr/types/wlr_box.h>
8#include <xkbcommon/xkbcommon.h> 8#include <xkbcommon/xkbcommon.h>
9#include "../include/config.h"
9#include "list.h" 10#include "list.h"
10#include "swaynag.h" 11#include "swaynag.h"
11#include "tree/container.h" 12#include "tree/container.h"
@@ -140,6 +141,7 @@ struct seat_config {
140 char *name; 141 char *name;
141 int fallback; // -1 means not set 142 int fallback; // -1 means not set
142 list_t *attachments; // list of seat_attachment configs 143 list_t *attachments; // list of seat_attachment configs
144 int hide_cursor_timeout;
143}; 145};
144 146
145enum config_dpms { 147enum config_dpms {
@@ -252,6 +254,13 @@ struct bar_config {
252 char *binding_mode_bg; 254 char *binding_mode_bg;
253 char *binding_mode_text; 255 char *binding_mode_text;
254 } colors; 256 } colors;
257
258#if HAVE_TRAY
259 char *icon_theme;
260 const char *tray_bindings[10]; // mouse buttons 0-9
261 list_t *tray_outputs; // char *
262 int tray_padding;
263#endif
255}; 264};
256 265
257struct bar_binding { 266struct bar_binding {
@@ -417,7 +426,9 @@ struct sway_config {
417 bool auto_back_and_forth; 426 bool auto_back_and_forth;
418 bool show_marks; 427 bool show_marks;
419 enum alignment title_align; 428 enum alignment title_align;
429
420 bool tiling_drag; 430 bool tiling_drag;
431 int tiling_drag_threshold;
421 432
422 bool smart_gaps; 433 bool smart_gaps;
423 int gaps_inner; 434 int gaps_inner;
@@ -436,8 +447,6 @@ struct sway_config {
436 enum edge_border_types hide_edge_borders; 447 enum edge_border_types hide_edge_borders;
437 enum edge_border_types saved_edge_borders; 448 enum edge_border_types saved_edge_borders;
438 449
439 int hide_cursor_timeout;
440
441 // border colors 450 // border colors
442 struct { 451 struct {
443 struct border_colors focused; 452 struct border_colors focused;
diff --git a/include/sway/input/cursor.h b/include/sway/input/cursor.h
index 21a26f68..22e278b0 100644
--- a/include/sway/input/cursor.h
+++ b/include/sway/input/cursor.h
@@ -60,6 +60,10 @@ struct sway_cursor *sway_cursor_create(struct sway_seat *seat);
60 */ 60 */
61void cursor_rebase(struct sway_cursor *cursor); 61void cursor_rebase(struct sway_cursor *cursor);
62 62
63void cursor_handle_activity(struct sway_cursor *cursor);
64void cursor_unhide(struct sway_cursor *cursor);
65int cursor_get_timeout(struct sway_cursor *cursor);
66
63/** 67/**
64 * Like cursor_rebase, but also allows focus to change when the cursor enters a 68 * Like cursor_rebase, but also allows focus to change when the cursor enters a
65 * new container. 69 * new container.
diff --git a/include/sway/input/input-manager.h b/include/sway/input/input-manager.h
index 219aa9ba..08e749dc 100644
--- a/include/sway/input/input-manager.h
+++ b/include/sway/input/input-manager.h
@@ -44,6 +44,12 @@ struct sway_seat *input_manager_get_default_seat(void);
44struct sway_seat *input_manager_get_seat(const char *seat_name); 44struct sway_seat *input_manager_get_seat(const char *seat_name);
45 45
46/** 46/**
47 * If none of the seat configs have a fallback setting (either true or false),
48 * create the default seat (if needed) and set it as the fallback
49 */
50void input_manager_verify_fallback_seat(void);
51
52/**
47 * Gets the last seat the user interacted with 53 * Gets the last seat the user interacted with
48 */ 54 */
49struct sway_seat *input_manager_current_seat(void); 55struct sway_seat *input_manager_current_seat(void);
diff --git a/include/sway/input/seat.h b/include/sway/input/seat.h
index bef2af77..a3c20346 100644
--- a/include/sway/input/seat.h
+++ b/include/sway/input/seat.h
@@ -39,6 +39,7 @@ enum sway_seat_operation {
39 OP_NONE, 39 OP_NONE,
40 OP_DOWN, 40 OP_DOWN,
41 OP_MOVE_FLOATING, 41 OP_MOVE_FLOATING,
42 OP_MOVE_TILING_THRESHOLD,
42 OP_MOVE_TILING, 43 OP_MOVE_TILING,
43 OP_RESIZE_FLOATING, 44 OP_RESIZE_FLOATING,
44 OP_RESIZE_TILING, 45 OP_RESIZE_TILING,
@@ -174,6 +175,8 @@ void seat_apply_config(struct sway_seat *seat, struct seat_config *seat_config);
174 175
175struct seat_config *seat_get_config(struct sway_seat *seat); 176struct seat_config *seat_get_config(struct sway_seat *seat);
176 177
178struct seat_config *seat_get_config_by_name(const char *name);
179
177bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); 180bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface);
178 181
179void drag_icon_update_position(struct sway_drag_icon *icon); 182void drag_icon_update_position(struct sway_drag_icon *icon);
@@ -184,6 +187,9 @@ void seat_begin_down(struct sway_seat *seat, struct sway_container *con,
184void seat_begin_move_floating(struct sway_seat *seat, 187void seat_begin_move_floating(struct sway_seat *seat,
185 struct sway_container *con, uint32_t button); 188 struct sway_container *con, uint32_t button);
186 189
190void seat_begin_move_tiling_threshold(struct sway_seat *seat,
191 struct sway_container *con, uint32_t button);
192
187void seat_begin_move_tiling(struct sway_seat *seat, 193void seat_begin_move_tiling(struct sway_seat *seat,
188 struct sway_container *con, uint32_t button); 194 struct sway_container *con, uint32_t button);
189 195
diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h
index 57c5114e..e377b8de 100644
--- a/include/swaybar/bar.h
+++ b/include/swaybar/bar.h
@@ -1,6 +1,7 @@
1#ifndef _SWAYBAR_BAR_H 1#ifndef _SWAYBAR_BAR_H
2#define _SWAYBAR_BAR_H 2#define _SWAYBAR_BAR_H
3#include <wayland-client.h> 3#include <wayland-client.h>
4#include "config.h"
4#include "input.h" 5#include "input.h"
5#include "pool-buffer.h" 6#include "pool-buffer.h"
6#include "wlr-layer-shell-unstable-v1-client-protocol.h" 7#include "wlr-layer-shell-unstable-v1-client-protocol.h"
@@ -8,6 +9,9 @@
8 9
9struct swaybar_config; 10struct swaybar_config;
10struct swaybar_output; 11struct swaybar_output;
12#if HAVE_TRAY
13struct swaybar_tray;
14#endif
11struct swaybar_workspace; 15struct swaybar_workspace;
12struct loop; 16struct loop;
13 17
@@ -38,6 +42,10 @@ struct swaybar {
38 int ipc_socketfd; 42 int ipc_socketfd;
39 43
40 struct wl_list outputs; // swaybar_output::link 44 struct wl_list outputs; // swaybar_output::link
45
46#if HAVE_TRAY
47 struct swaybar_tray *tray;
48#endif
41}; 49};
42 50
43struct swaybar_output { 51struct swaybar_output {
@@ -62,6 +70,8 @@ struct swaybar_output {
62 struct pool_buffer *current_buffer; 70 struct pool_buffer *current_buffer;
63 bool dirty; 71 bool dirty;
64 bool frame_scheduled; 72 bool frame_scheduled;
73
74 uint32_t output_height, output_width, output_x, output_y;
65}; 75};
66 76
67struct swaybar_workspace { 77struct swaybar_workspace {
@@ -78,6 +88,8 @@ bool bar_setup(struct swaybar *bar, const char *socket_path);
78void bar_run(struct swaybar *bar); 88void bar_run(struct swaybar *bar);
79void bar_teardown(struct swaybar *bar); 89void bar_teardown(struct swaybar *bar);
80 90
91void set_bar_dirty(struct swaybar *bar);
92
81/* 93/*
82 * Determines whether the bar should be visible and changes it to be so. 94 * Determines whether the bar should be visible and changes it to be so.
83 * If the current visibility of the bar is the different to what it should be, 95 * If the current visibility of the bar is the different to what it should be,
diff --git a/include/swaybar/config.h b/include/swaybar/config.h
index fd7c6ec4..1f6577bd 100644
--- a/include/swaybar/config.h
+++ b/include/swaybar/config.h
@@ -3,6 +3,7 @@
3#include <stdbool.h> 3#include <stdbool.h>
4#include <stdint.h> 4#include <stdint.h>
5#include <wayland-client.h> 5#include <wayland-client.h>
6#include "../include/config.h"
6#include "list.h" 7#include "list.h"
7#include "util.h" 8#include "util.h"
8 9
@@ -64,6 +65,14 @@ struct swaybar_config {
64 struct box_colors urgent_workspace; 65 struct box_colors urgent_workspace;
65 struct box_colors binding_mode; 66 struct box_colors binding_mode;
66 } colors; 67 } colors;
68
69#if HAVE_TRAY
70 char *icon_theme;
71 char *tray_bindings[10]; // mouse buttons 0-9
72 bool tray_hidden;
73 list_t *tray_outputs; // char *
74 int tray_padding;
75#endif
67}; 76};
68 77
69struct swaybar_config *init_config(void); 78struct swaybar_config *init_config(void);
diff --git a/include/swaybar/tray/host.h b/include/swaybar/tray/host.h
new file mode 100644
index 00000000..2d4cf82b
--- /dev/null
+++ b/include/swaybar/tray/host.h
@@ -0,0 +1,17 @@
1#ifndef _SWAYBAR_TRAY_HOST_H
2#define _SWAYBAR_TRAY_HOST_H
3
4#include <stdbool.h>
5
6struct swaybar_tray;
7
8struct swaybar_host {
9 struct swaybar_tray *tray;
10 char *service;
11 char *watcher_interface;
12};
13
14bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray);
15void finish_host(struct swaybar_host *host);
16
17#endif
diff --git a/include/swaybar/tray/icon.h b/include/swaybar/tray/icon.h
index 1cc6ff9c..7a6c400c 100644
--- a/include/swaybar/tray/icon.h
+++ b/include/swaybar/tray/icon.h
@@ -1,16 +1,44 @@
1#ifndef _SWAYBAR_ICON_H 1#ifndef _SWAYBAR_TRAY_ICON_H
2#define _SWAYBAR_ICON_H 2#define _SWAYBAR_TRAY_ICON_H
3 3
4#include <stdint.h> 4#include "list.h"
5#include <stdbool.h>
6#include <client/cairo.h>
7 5
8/** 6enum subdir_type {
9 * Returns the image found by `name` that is closest to `size` 7 THRESHOLD,
10 */ 8 SCALABLE,
11cairo_surface_t *find_icon(const char *name, int size); 9 FIXED
10};
11
12struct icon_theme_subdir {
13 char *name;
14 int size;
15 enum subdir_type type;
16 int max_size;
17 int min_size;
18 int threshold;
19};
20
21struct icon_theme {
22 char *name;
23 char *comment;
24 char *inherits;
25 list_t *directories; // char *
12 26
13/* Struct used internally only */ 27 char *dir;
14struct subdir; 28 list_t *subdirs; // struct icon_theme_subdir *
29};
30
31void init_themes(list_t **themes, list_t **basedirs);
32void finish_themes(list_t *themes, list_t *basedirs);
33
34/*
35 * Finds an icon of a specified size given a list of themes and base directories.
36 * If the icon is found, the pointers min_size & max_size are set to minimum &
37 * maximum size that the icon can be scaled to, respectively.
38 * Returns: path of icon (which should be freed), or NULL if the icon is not found.
39 */
40char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
41 char *theme, int *min_size, int *max_size);
42char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size);
15 43
16#endif /* _SWAYBAR_ICON_H */ 44#endif
diff --git a/include/swaybar/tray/item.h b/include/swaybar/tray/item.h
new file mode 100644
index 00000000..9bba7951
--- /dev/null
+++ b/include/swaybar/tray/item.h
@@ -0,0 +1,45 @@
1#ifndef _SWAYBAR_TRAY_ITEM_H
2#define _SWAYBAR_TRAY_ITEM_H
3
4#include <cairo.h>
5#include <stdbool.h>
6#include <stdint.h>
7#include "swaybar/tray/tray.h"
8#include "list.h"
9
10struct swaybar_output;
11
12struct swaybar_pixmap {
13 int size;
14 unsigned char pixels[];
15};
16
17struct swaybar_sni {
18 // icon properties
19 struct swaybar_tray *tray;
20 cairo_surface_t *icon;
21 int min_size;
22 int max_size;
23
24 // dbus properties
25 char *watcher_id;
26 char *service;
27 char *path;
28 char *interface;
29
30 char *status;
31 char *icon_name;
32 list_t *icon_pixmap; // struct swaybar_pixmap *
33 char *attention_icon_name;
34 list_t *attention_icon_pixmap; // struct swaybar_pixmap *
35 bool item_is_menu;
36 char *menu;
37 char *icon_theme_path; // non-standard KDE property
38};
39
40struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray);
41void destroy_sni(struct swaybar_sni *sni);
42uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
43 struct swaybar_sni *sni);
44
45#endif
diff --git a/include/swaybar/tray/sni.h b/include/swaybar/tray/sni.h
deleted file mode 100644
index c2544e2a..00000000
--- a/include/swaybar/tray/sni.h
+++ /dev/null
@@ -1,82 +0,0 @@
1#ifndef _SWAYBAR_SNI_H
2#define _SWAYBAR_SNI_H
3
4#include <stdbool.h>
5#include <client/cairo.h>
6
7struct StatusNotifierItem {
8 /* Name registered to sni watcher */
9 char *name;
10 /* Unique bus name, needed for determining signal origins */
11 char *unique_name;
12 bool kde_special_snowflake;
13
14 cairo_surface_t *image;
15 bool dirty;
16};
17
18/* Each output holds an sni_icon_ref of each item to render */
19struct sni_icon_ref {
20 cairo_surface_t *icon;
21 struct StatusNotifierItem *ref;
22};
23
24struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item,
25 int height);
26
27void sni_icon_ref_free(struct sni_icon_ref *sni_ref);
28
29/**
30 * Will return a new item and get its icon. (see warning below)
31 * May return `NULL` if `name` is not valid.
32 */
33struct StatusNotifierItem *sni_create(const char *name);
34
35/**
36 * `item` must be a struct StatusNotifierItem *
37 * `str` must be a NUL terminated char *
38 *
39 * Returns 0 if `item` has a name of `str`
40 */
41int sni_str_cmp(const void *item, const void *str);
42
43/**
44 * Returns 0 if `item` has a unique name of `str` or if
45 * `item->unique_name == NULL`
46 */
47int sni_uniq_cmp(const void *item, const void *str);
48
49/**
50 * Gets an icon for the given item if found.
51 *
52 * XXX
53 * This function keeps a reference to the item until it gets responses, make
54 * sure that the reference and item are valid during this time.
55 */
56void get_icon(struct StatusNotifierItem *item);
57
58/**
59 * Calls the "activate" method on the given StatusNotifierItem
60 *
61 * x and y should be where the item was clicked
62 */
63void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
64
65/**
66 * Asks the item to draw a context menu at the given x and y coords
67 */
68void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
69
70/**
71 * Calls the "secondary activate" method on the given StatusNotifierItem
72 *
73 * x and y should be where the item was clicked
74 */
75void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y);
76
77/**
78 * Deconstructs `item`
79 */
80void sni_free(struct StatusNotifierItem *item);
81
82#endif /* _SWAYBAR_SNI_H */
diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h
new file mode 100644
index 00000000..8958b69a
--- /dev/null
+++ b/include/swaybar/tray/tray.h
@@ -0,0 +1,40 @@
1#ifndef _SWAYBAR_TRAY_TRAY_H
2#define _SWAYBAR_TRAY_TRAY_H
3
4#include "config.h"
5#ifdef HAVE_SYSTEMD
6#include <systemd/sd-bus.h>
7#elif HAVE_ELOGIND
8#include <elogind/sd-bus.h>
9#endif
10#include <cairo.h>
11#include <stdint.h>
12#include "swaybar/tray/host.h"
13#include "list.h"
14
15struct swaybar;
16struct swaybar_output;
17struct swaybar_watcher;
18
19struct swaybar_tray {
20 struct swaybar *bar;
21
22 int fd;
23 sd_bus *bus;
24
25 struct swaybar_host host_xdg;
26 struct swaybar_host host_kde;
27 list_t *items; // struct swaybar_sni *
28 struct swaybar_watcher *watcher_xdg;
29 struct swaybar_watcher *watcher_kde;
30
31 list_t *basedirs; // char *
32 list_t *themes; // struct swaybar_theme *
33};
34
35struct swaybar_tray *create_tray(struct swaybar *bar);
36void destroy_tray(struct swaybar_tray *tray);
37void tray_in(int fd, short mask, void *data);
38uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x);
39
40#endif
diff --git a/include/swaybar/tray/watcher.h b/include/swaybar/tray/watcher.h
new file mode 100644
index 00000000..8f276da8
--- /dev/null
+++ b/include/swaybar/tray/watcher.h
@@ -0,0 +1,18 @@
1#ifndef _SWAYBAR_TRAY_WATCHER_H
2#define _SWAYBAR_TRAY_WATCHER_H
3
4#include "swaybar/tray/tray.h"
5#include "list.h"
6
7struct swaybar_watcher {
8 char *interface;
9 sd_bus *bus;
10 list_t *hosts;
11 list_t *items;
12 int version;
13};
14
15struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus);
16void destroy_watcher(struct swaybar_watcher *watcher);
17
18#endif
diff --git a/meson.build b/meson.build
index e1e0fc2d..b1353b59 100644
--- a/meson.build
+++ b/meson.build
@@ -66,6 +66,7 @@ endif
66conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) 66conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found())
67conf_data.set10('HAVE_SYSTEMD', systemd.found()) 67conf_data.set10('HAVE_SYSTEMD', systemd.found())
68conf_data.set10('HAVE_ELOGIND', elogind.found()) 68conf_data.set10('HAVE_ELOGIND', elogind.found())
69conf_data.set10('HAVE_TRAY', get_option('enable-tray') and (systemd.found() or elogind.found()))
69 70
70if not systemd.found() and not elogind.found() 71if not systemd.found() and not elogind.found()
71 warning('The sway binary must be setuid when compiled without (e)logind') 72 warning('The sway binary must be setuid when compiled without (e)logind')
@@ -212,6 +213,7 @@ endif
212if (get_option('bash-completions')) 213if (get_option('bash-completions'))
213 bash_files = files( 214 bash_files = files(
214 'completions/bash/sway', 215 'completions/bash/sway',
216 'completions/bash/swaybar',
215 'completions/bash/swayidle', 217 'completions/bash/swayidle',
216 'completions/bash/swaylock', 218 'completions/bash/swaylock',
217 'completions/bash/swaymsg', 219 'completions/bash/swaymsg',
diff --git a/meson_options.txt b/meson_options.txt
index 2db852fc..4640618e 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -6,3 +6,4 @@ option('zsh-completions', type: 'boolean', value: true, description: 'Install zs
6option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') 6option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.')
7option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') 7option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.')
8option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') 8option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications')
9option('enable-tray', type: 'boolean', value: false, description: 'Enable support for swaybar tray')
diff --git a/sway/commands.c b/sway/commands.c
index 51bfe13a..0883b57b 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -71,7 +71,6 @@ static struct cmd_handler handlers[] = {
71 { "force_focus_wrapping", cmd_force_focus_wrapping }, 71 { "force_focus_wrapping", cmd_force_focus_wrapping },
72 { "fullscreen", cmd_fullscreen }, 72 { "fullscreen", cmd_fullscreen },
73 { "gaps", cmd_gaps }, 73 { "gaps", cmd_gaps },
74 { "hide_cursor", cmd_hide_cursor },
75 { "hide_edge_borders", cmd_hide_edge_borders }, 74 { "hide_edge_borders", cmd_hide_edge_borders },
76 { "include", cmd_include }, 75 { "include", cmd_include },
77 { "input", cmd_input }, 76 { "input", cmd_input },
@@ -88,6 +87,7 @@ static struct cmd_handler handlers[] = {
88 { "smart_borders", cmd_smart_borders }, 87 { "smart_borders", cmd_smart_borders },
89 { "smart_gaps", cmd_smart_gaps }, 88 { "smart_gaps", cmd_smart_gaps },
90 { "tiling_drag", cmd_tiling_drag }, 89 { "tiling_drag", cmd_tiling_drag },
90 { "tiling_drag_threshold", cmd_tiling_drag_threshold },
91 { "title_align", cmd_title_align }, 91 { "title_align", cmd_title_align },
92 { "titlebar_border_thickness", cmd_titlebar_border_thickness }, 92 { "titlebar_border_thickness", cmd_titlebar_border_thickness },
93 { "titlebar_padding", cmd_titlebar_padding }, 93 { "titlebar_padding", cmd_titlebar_padding },
diff --git a/sway/commands/bar.c b/sway/commands/bar.c
index 0cf94907..507ee10a 100644
--- a/sway/commands/bar.c
+++ b/sway/commands/bar.c
@@ -8,11 +8,9 @@
8 8
9// Must be in alphabetical order for bsearch 9// Must be in alphabetical order for bsearch
10static struct cmd_handler bar_handlers[] = { 10static struct cmd_handler bar_handlers[] = {
11 { "activate_button", bar_cmd_activate_button },
12 { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, 11 { "binding_mode_indicator", bar_cmd_binding_mode_indicator },
13 { "bindsym", bar_cmd_bindsym }, 12 { "bindsym", bar_cmd_bindsym },
14 { "colors", bar_cmd_colors }, 13 { "colors", bar_cmd_colors },
15 { "context_button", bar_cmd_context_button },
16 { "font", bar_cmd_font }, 14 { "font", bar_cmd_font },
17 { "gaps", bar_cmd_gaps }, 15 { "gaps", bar_cmd_gaps },
18 { "height", bar_cmd_height }, 16 { "height", bar_cmd_height },
@@ -23,11 +21,11 @@ static struct cmd_handler bar_handlers[] = {
23 { "output", bar_cmd_output }, 21 { "output", bar_cmd_output },
24 { "pango_markup", bar_cmd_pango_markup }, 22 { "pango_markup", bar_cmd_pango_markup },
25 { "position", bar_cmd_position }, 23 { "position", bar_cmd_position },
26 { "secondary_button", bar_cmd_secondary_button },
27 { "separator_symbol", bar_cmd_separator_symbol }, 24 { "separator_symbol", bar_cmd_separator_symbol },
28 { "status_command", bar_cmd_status_command }, 25 { "status_command", bar_cmd_status_command },
29 { "strip_workspace_name", bar_cmd_strip_workspace_name }, 26 { "strip_workspace_name", bar_cmd_strip_workspace_name },
30 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, 27 { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers },
28 { "tray_bindsym", bar_cmd_tray_bindsym },
31 { "tray_output", bar_cmd_tray_output }, 29 { "tray_output", bar_cmd_tray_output },
32 { "tray_padding", bar_cmd_tray_padding }, 30 { "tray_padding", bar_cmd_tray_padding },
33 { "workspace_buttons", bar_cmd_workspace_buttons }, 31 { "workspace_buttons", bar_cmd_workspace_buttons },
diff --git a/sway/commands/bar/activate_button.c b/sway/commands/bar/activate_button.c
deleted file mode 100644
index 7310e7ec..00000000
--- a/sway/commands/bar/activate_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_activate_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "activate_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/context_button.c b/sway/commands/bar/context_button.c
deleted file mode 100644
index 3b76885a..00000000
--- a/sway/commands/bar/context_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_context_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "context_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c
index 0e30409b..9d3b6040 100644
--- a/sway/commands/bar/icon_theme.c
+++ b/sway/commands/bar/icon_theme.c
@@ -1,7 +1,28 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
3#include "config.h"
2#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
6#include "log.h"
3 7
4struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { 8struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) {
5 // TODO TRAY 9#if HAVE_TRAY
6 return cmd_results_new(CMD_INVALID, "icon_theme", "TODO TRAY"); 10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "icon_theme", EXPECTED_EQUAL_TO, 1))) {
12 return error;
13 }
14
15 if (!config->current_bar) {
16 return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
17 }
18
19 wlr_log(WLR_DEBUG, "[Bar %s] Setting icon theme to %s",
20 config->current_bar->id, argv[0]);
21 free(config->current_bar->icon_theme);
22 config->current_bar->icon_theme = strdup(argv[0]);
23 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
24#else
25 return cmd_results_new(CMD_INVALID, "icon_theme",
26 "Sway has been compiled without tray support");
27#endif
7} 28}
diff --git a/sway/commands/bar/secondary_button.c b/sway/commands/bar/secondary_button.c
deleted file mode 100644
index 449124cb..00000000
--- a/sway/commands/bar/secondary_button.c
+++ /dev/null
@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include "sway/commands.h"
3#include "log.h"
4
5struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) {
6 // TODO TRAY
7 return cmd_results_new(CMD_INVALID, "secondary_button", "TODO TRAY");
8}
diff --git a/sway/commands/bar/tray_bindsym.c b/sway/commands/bar/tray_bindsym.c
new file mode 100644
index 00000000..ad413446
--- /dev/null
+++ b/sway/commands/bar/tray_bindsym.c
@@ -0,0 +1,55 @@
1#include <strings.h>
2#include "config.h"
3#include "sway/commands.h"
4#include "sway/config.h"
5#include "log.h"
6
7struct cmd_results *bar_cmd_tray_bindsym(int argc, char **argv) {
8#if HAVE_TRAY
9 struct cmd_results *error = NULL;
10 if ((error = checkarg(argc, "tray_bindsym", EXPECTED_EQUAL_TO, 2))) {
11 return error;
12 }
13
14 if (!config->current_bar) {
15 return cmd_results_new(CMD_FAILURE, "tray_bindsym", "No bar defined.");
16 }
17
18 int button = 0;
19 if (strncasecmp(argv[0], "button", strlen("button")) == 0 &&
20 strlen(argv[0]) == strlen("button0")) {
21 button = argv[0][strlen("button")] - '0';
22 }
23 if (button < 1 || button > 9) {
24 return cmd_results_new(CMD_FAILURE, "tray_bindsym",
25 "[Bar %s] Only buttons 1 to 9 are supported",
26 config->current_bar->id);
27 }
28
29 static const char *commands[] = {
30 "ContextMenu",
31 "Activate",
32 "SecondaryActivate",
33 "ScrollDown",
34 "ScrollLeft",
35 "ScrollRight",
36 "ScrollUp",
37 "nop"
38 };
39
40 for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) {
41 if (strcasecmp(argv[1], commands[i]) == 0) {
42 wlr_log(WLR_DEBUG, "[Bar %s] Binding button %d to %s",
43 config->current_bar->id, button, commands[i]);
44 config->current_bar->tray_bindings[button] = commands[i];
45 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
46 }
47 }
48
49 return cmd_results_new(CMD_INVALID, "tray_bindsym",
50 "[Bar %s] Invalid command %s", config->current_bar->id, argv[1]);
51#else
52 return cmd_results_new(CMD_INVALID, "tray_bindsym",
53 "Sway has been compiled without tray support");
54#endif
55}
diff --git a/sway/commands/bar/tray_output.c b/sway/commands/bar/tray_output.c
index e6c77128..a1169c20 100644
--- a/sway/commands/bar/tray_output.c
+++ b/sway/commands/bar/tray_output.c
@@ -1,7 +1,42 @@
1#define _POSIX_C_SOURCE 200809L
1#include <string.h> 2#include <string.h>
3#include "config.h"
2#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
6#include "list.h"
7#include "log.h"
3 8
4struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { 9struct cmd_results *bar_cmd_tray_output(int argc, char **argv) {
5 // TODO TRAY 10#if HAVE_TRAY
6 return cmd_results_new(CMD_INVALID, "tray_output", "TODO TRAY"); 11 struct cmd_results *error = NULL;
12 if ((error = checkarg(argc, "tray_output", EXPECTED_EQUAL_TO, 1))) {
13 return error;
14 }
15
16 if (!config->current_bar) {
17 return cmd_results_new(CMD_FAILURE, "tray_output", "No bar defined.");
18 }
19
20 list_t *outputs = config->current_bar->tray_outputs;
21 if (!outputs) {
22 config->current_bar->tray_outputs = outputs = create_list();
23 }
24
25 if (strcmp(argv[0], "none") == 0) {
26 wlr_log(WLR_DEBUG, "Hiding tray on bar: %s", config->current_bar->id);
27 for (int i = 0; i < outputs->length; ++i) {
28 free(outputs->items[i]);
29 }
30 outputs->length = 0;
31 } else {
32 wlr_log(WLR_DEBUG, "Showing tray on output '%s' for bar: %s", argv[0],
33 config->current_bar->id);
34 }
35 list_add(outputs, strdup(argv[0]));
36
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38#else
39 return cmd_results_new(CMD_INVALID, "tray_output",
40 "Sway has been compiled without tray support");
41#endif
7} 42}
diff --git a/sway/commands/bar/tray_padding.c b/sway/commands/bar/tray_padding.c
index 91c56f19..eb795b00 100644
--- a/sway/commands/bar/tray_padding.c
+++ b/sway/commands/bar/tray_padding.c
@@ -1,9 +1,42 @@
1#include <stdlib.h> 1#include <stdlib.h>
2#include <strings.h> 2#include <strings.h>
3#include "config.h"
3#include "sway/commands.h" 4#include "sway/commands.h"
5#include "sway/config.h"
4#include "log.h" 6#include "log.h"
5 7
6struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { 8struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) {
7 // TODO TRAY 9#if HAVE_TRAY
8 return cmd_results_new(CMD_INVALID, "tray_padding", "TODO TRAY"); 10 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) {
12 return error;
13 }
14 if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_MOST, 2))) {
15 return error;
16 }
17
18 if (!config->current_bar) {
19 return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined.");
20 }
21 struct bar_config *bar = config->current_bar;
22
23 char *end;
24 int padding = strtol(argv[0], &end, 10);
25 if (padding < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) {
26 return cmd_results_new(CMD_INVALID, "tray_padding",
27 "[Bar %s] Invalid tray padding value: %s", bar->id, argv[0]);
28 }
29
30 if (argc == 2 && strcasecmp(argv[1], "px") != 0) {
31 return cmd_results_new(CMD_INVALID, "tray_padding",
32 "Expected 'tray_padding <px> [px]'");
33 }
34
35 wlr_log(WLR_DEBUG, "[Bar %s] Setting tray padding to %d", bar->id, padding);
36 config->current_bar->tray_padding = padding;
37 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
38#else
39 return cmd_results_new(CMD_INVALID, "tray_padding",
40 "Sway has been compiled without tray support");
41#endif
9} 42}
diff --git a/sway/commands/seat.c b/sway/commands/seat.c
index 56acd204..3e7ffed9 100644
--- a/sway/commands/seat.c
+++ b/sway/commands/seat.c
@@ -10,6 +10,7 @@ static struct cmd_handler seat_handlers[] = {
10 { "attach", seat_cmd_attach }, 10 { "attach", seat_cmd_attach },
11 { "cursor", seat_cmd_cursor }, 11 { "cursor", seat_cmd_cursor },
12 { "fallback", seat_cmd_fallback }, 12 { "fallback", seat_cmd_fallback },
13 { "hide_cursor", seat_cmd_hide_cursor },
13}; 14};
14 15
15struct cmd_results *cmd_seat(int argc, char **argv) { 16struct cmd_results *cmd_seat(int argc, char **argv) {
diff --git a/sway/commands/hide_cursor.c b/sway/commands/seat/hide_cursor.c
index 3778fcff..343573b5 100644
--- a/sway/commands/hide_cursor.c
+++ b/sway/commands/seat/hide_cursor.c
@@ -2,15 +2,17 @@
2#include <string.h> 2#include <string.h>
3#include "sway/commands.h" 3#include "sway/commands.h"
4#include "sway/config.h" 4#include "sway/config.h"
5#include "sway/input/cursor.h"
6#include "sway/input/seat.h" 5#include "sway/input/seat.h"
7#include "stringop.h" 6#include "stringop.h"
8 7
9struct cmd_results *cmd_hide_cursor(int argc, char **argv) { 8struct cmd_results *seat_cmd_hide_cursor(int argc, char **argv) {
10 struct cmd_results *error = NULL; 9 struct cmd_results *error = NULL;
11 if ((error = checkarg(argc, "hide_cursor", EXPECTED_EQUAL_TO, 1))) { 10 if ((error = checkarg(argc, "hide_cursor", EXPECTED_EQUAL_TO, 1))) {
12 return error; 11 return error;
13 } 12 }
13 if (!config->handler_context.seat_config) {
14 return cmd_results_new(CMD_FAILURE, "hide_cursor", "No seat defined");
15 }
14 16
15 char *end; 17 char *end;
16 int timeout = strtol(argv[0], &end, 10); 18 int timeout = strtol(argv[0], &end, 10);
@@ -21,13 +23,7 @@ struct cmd_results *cmd_hide_cursor(int argc, char **argv) {
21 if (timeout < 100 && timeout != 0) { 23 if (timeout < 100 && timeout != 0) {
22 timeout = 100; 24 timeout = 100;
23 } 25 }
24 config->hide_cursor_timeout = timeout; 26 config->handler_context.seat_config->hide_cursor_timeout = timeout;
25
26 struct sway_seat *seat;
27 wl_list_for_each(seat, &server.input->seats, link) {
28 wl_event_source_timer_update(seat->cursor->hide_source,
29 config->hide_cursor_timeout);
30 }
31 27
32 return cmd_results_new(CMD_SUCCESS, NULL, NULL); 28 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
33} 29}
diff --git a/sway/commands/tiling_drag_threshold.c b/sway/commands/tiling_drag_threshold.c
new file mode 100644
index 00000000..6b0531c3
--- /dev/null
+++ b/sway/commands/tiling_drag_threshold.c
@@ -0,0 +1,22 @@
1#include <string.h>
2#include "sway/commands.h"
3#include "sway/config.h"
4#include "log.h"
5
6struct cmd_results *cmd_tiling_drag_threshold(int argc, char **argv) {
7 struct cmd_results *error = NULL;
8 if ((error = checkarg(argc, "tiling_drag_threshold", EXPECTED_EQUAL_TO, 1))) {
9 return error;
10 }
11
12 char *inv;
13 int value = strtol(argv[0], &inv, 10);
14 if (*inv != '\0' || value < 0) {
15 return cmd_results_new(CMD_INVALID, "tiling_drag_threshold",
16 "Invalid threshold specified");
17 }
18
19 config->tiling_drag_threshold = value;
20
21 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
22}
diff --git a/sway/config.c b/sway/config.c
index bb18c739..5d631b7e 100644
--- a/sway/config.c
+++ b/sway/config.c
@@ -233,6 +233,7 @@ static void config_defaults(struct sway_config *config) {
233 config->show_marks = true; 233 config->show_marks = true;
234 config->title_align = ALIGN_LEFT; 234 config->title_align = ALIGN_LEFT;
235 config->tiling_drag = true; 235 config->tiling_drag = true;
236 config->tiling_drag_threshold = 9;
236 237
237 config->smart_gaps = false; 238 config->smart_gaps = false;
238 config->gaps_inner = 0; 239 config->gaps_inner = 0;
@@ -257,8 +258,6 @@ static void config_defaults(struct sway_config *config) {
257 config->hide_edge_borders = E_NONE; 258 config->hide_edge_borders = E_NONE;
258 config->saved_edge_borders = E_NONE; 259 config->saved_edge_borders = E_NONE;
259 260
260 config->hide_cursor_timeout = 0;
261
262 // border colors 261 // border colors
263 set_color(config->border_colors.focused.border, 0x4C7899); 262 set_color(config->border_colors.focused.border, 0x4C7899);
264 set_color(config->border_colors.focused.background, 0x285577); 263 set_color(config->border_colors.focused.background, 0x285577);
@@ -465,6 +464,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) {
465 if (config->swaynag_config_errors.pid > 0) { 464 if (config->swaynag_config_errors.pid > 0) {
466 swaynag_show(&config->swaynag_config_errors); 465 swaynag_show(&config->swaynag_config_errors);
467 } 466 }
467 input_manager_verify_fallback_seat();
468 } 468 }
469 469
470 if (old_config) { 470 if (old_config) {
diff --git a/sway/config/bar.c b/sway/config/bar.c
index 45c9e998..670219f1 100644
--- a/sway/config/bar.c
+++ b/sway/config/bar.c
@@ -12,6 +12,7 @@
12#include <signal.h> 12#include <signal.h>
13#include "sway/config.h" 13#include "sway/config.h"
14#include "sway/output.h" 14#include "sway/output.h"
15#include "config.h"
15#include "stringop.h" 16#include "stringop.h"
16#include "list.h" 17#include "list.h"
17#include "log.h" 18#include "log.h"
@@ -77,6 +78,10 @@ void free_bar_config(struct bar_config *bar) {
77 free(bar->colors.binding_mode_border); 78 free(bar->colors.binding_mode_border);
78 free(bar->colors.binding_mode_bg); 79 free(bar->colors.binding_mode_bg);
79 free(bar->colors.binding_mode_text); 80 free(bar->colors.binding_mode_text);
81#if HAVE_TRAY
82 list_free_items_and_destroy(bar->tray_outputs);
83 free(bar->icon_theme);
84#endif
80 free(bar); 85 free(bar);
81} 86}
82 87
@@ -165,6 +170,10 @@ struct bar_config *default_bar_config(void) {
165 bar->colors.binding_mode_bg = NULL; 170 bar->colors.binding_mode_bg = NULL;
166 bar->colors.binding_mode_text = NULL; 171 bar->colors.binding_mode_text = NULL;
167 172
173#if HAVE_TRAY
174 bar->tray_padding = 2;
175#endif
176
168 list_add(config->bars, bar); 177 list_add(config->bars, bar);
169 return bar; 178 return bar;
170cleanup: 179cleanup:
diff --git a/sway/config/seat.c b/sway/config/seat.c
index c248990a..d7316c68 100644
--- a/sway/config/seat.c
+++ b/sway/config/seat.c
@@ -25,6 +25,7 @@ struct seat_config *new_seat_config(const char* name) {
25 free(seat); 25 free(seat);
26 return NULL; 26 return NULL;
27 } 27 }
28 seat->hide_cursor_timeout = -1;
28 29
29 return seat; 30 return seat;
30} 31}
@@ -137,6 +138,10 @@ void merge_seat_config(struct seat_config *dest, struct seat_config *source) {
137 } 138 }
138 } 139 }
139 } 140 }
141
142 if (source->hide_cursor_timeout != -1) {
143 dest->hide_cursor_timeout = source->hide_cursor_timeout;
144 }
140} 145}
141 146
142struct seat_config *copy_seat_config(struct seat_config *seat) { 147struct seat_config *copy_seat_config(struct seat_config *seat) {
diff --git a/sway/desktop/render.c b/sway/desktop/render.c
index 14881e96..6c9fe23c 100644
--- a/sway/desktop/render.c
+++ b/sway/desktop/render.c
@@ -1017,7 +1017,7 @@ void output_render(struct sway_output *output, struct timespec *when,
1017 if (fullscreen_con->view) { 1017 if (fullscreen_con->view) {
1018 if (fullscreen_con->view->saved_buffer) { 1018 if (fullscreen_con->view->saved_buffer) {
1019 render_saved_view(fullscreen_con->view, output, damage, 1.0f); 1019 render_saved_view(fullscreen_con->view, output, damage, 1.0f);
1020 } else { 1020 } else if (fullscreen_con->view->surface) {
1021 render_view_toplevels(fullscreen_con->view, 1021 render_view_toplevels(fullscreen_con->view,
1022 output, damage, 1.0f); 1022 output, damage, 1.0f);
1023 } 1023 }
diff --git a/sway/desktop/transaction.c b/sway/desktop/transaction.c
index bf0038b4..f46938e2 100644
--- a/sway/desktop/transaction.c
+++ b/sway/desktop/transaction.c
@@ -363,7 +363,7 @@ static void transaction_progress_queue(void) {
363 363
364static int handle_timeout(void *data) { 364static int handle_timeout(void *data) {
365 struct sway_transaction *transaction = data; 365 struct sway_transaction *transaction = data;
366 wlr_log(WLR_DEBUG, "Transaction %p timed out (%li waiting)", 366 wlr_log(WLR_DEBUG, "Transaction %p timed out (%zi waiting)",
367 transaction, transaction->num_waiting); 367 transaction, transaction->num_waiting);
368 transaction->num_waiting = 0; 368 transaction->num_waiting = 0;
369 transaction_progress_queue(); 369 transaction_progress_queue();
@@ -472,7 +472,7 @@ static void set_instruction_ready(
472 struct timespec *start = &transaction->commit_time; 472 struct timespec *start = &transaction->commit_time;
473 float ms = (now.tv_sec - start->tv_sec) * 1000 + 473 float ms = (now.tv_sec - start->tv_sec) * 1000 +
474 (now.tv_nsec - start->tv_nsec) / 1000000.0; 474 (now.tv_nsec - start->tv_nsec) / 1000000.0;
475 wlr_log(WLR_DEBUG, "Transaction %p: %li/%li ready in %.1fms (%s)", 475 wlr_log(WLR_DEBUG, "Transaction %p: %zi/%zi ready in %.1fms (%s)",
476 transaction, 476 transaction,
477 transaction->num_configures - transaction->num_waiting + 1, 477 transaction->num_configures - transaction->num_waiting + 1,
478 transaction->num_configures, ms, 478 transaction->num_configures, ms,
diff --git a/sway/input/cursor.c b/sway/input/cursor.c
index 22c5b075..510030ae 100644
--- a/sway/input/cursor.c
+++ b/sway/input/cursor.c
@@ -384,6 +384,30 @@ static void handle_move_tiling_motion(struct sway_seat *seat,
384 desktop_damage_box(&seat->op_drop_box); 384 desktop_damage_box(&seat->op_drop_box);
385} 385}
386 386
387static void handle_move_tiling_threshold_motion(struct sway_seat *seat,
388 struct sway_cursor *cursor) {
389 double cx = seat->cursor->cursor->x;
390 double cy = seat->cursor->cursor->y;
391 double sx = seat->op_ref_lx;
392 double sy = seat->op_ref_ly;
393
394 // Get the scaled threshold for the output. Even if the operation goes
395 // across multiple outputs of varying scales, just use the scale for the
396 // output that the cursor is currently on for simplicity.
397 struct wlr_output *wlr_output = wlr_output_layout_output_at(
398 root->output_layout, cx, cy);
399 double output_scale = wlr_output ? wlr_output->scale : 1;
400 double threshold = config->tiling_drag_threshold * output_scale;
401 threshold *= threshold;
402
403 // If the threshold has been exceeded, start the actual drag
404 if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) {
405 seat->operation = OP_MOVE_TILING;
406 cursor_set_image(cursor, "grab", NULL);
407 handle_move_tiling_motion(seat, cursor);
408 }
409}
410
387static void calculate_floating_constraints(struct sway_container *con, 411static void calculate_floating_constraints(struct sway_container *con,
388 int *min_width, int *max_width, int *min_height, int *max_height) { 412 int *min_width, int *max_width, int *min_height, int *max_height) {
389 if (config->floating_minimum_width == -1) { // no minimum 413 if (config->floating_minimum_width == -1) { // no minimum
@@ -597,21 +621,40 @@ static int hide_notify(void *data) {
597 return 1; 621 return 1;
598} 622}
599 623
600static void handle_activity(struct sway_cursor *cursor) { 624int cursor_get_timeout(struct sway_cursor *cursor){
601 wl_event_source_timer_update(cursor->hide_source, 625 struct seat_config *sc = seat_get_config(cursor->seat);
602 config->hide_cursor_timeout); 626 if (!sc) {
627 sc = seat_get_config_by_name("*");
628 }
629 int timeout = sc ? sc->hide_cursor_timeout : 0;
630 if (timeout < 0) {
631 timeout = 0;
632 }
633 return timeout;
634}
635
636void cursor_handle_activity(struct sway_cursor *cursor) {
637 wl_event_source_timer_update(
638 cursor->hide_source, cursor_get_timeout(cursor));
639
603 wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); 640 wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat);
604 if (cursor->hidden) { 641 if (cursor->hidden) {
605 cursor->hidden = false; 642 cursor_unhide(cursor);
606 if (cursor->image_surface) { 643 }
607 cursor_set_image_surface(cursor, cursor->image_surface, 644}
608 cursor->hotspot_x, cursor->hotspot_y, 645
609 cursor->image_client); 646void cursor_unhide(struct sway_cursor *cursor) {
610 } else { 647 cursor->hidden = false;
611 const char *image = cursor->image; 648 if (cursor->image_surface) {
612 cursor->image = NULL; 649 cursor_set_image_surface(cursor,
613 cursor_set_image(cursor, image, cursor->image_client); 650 cursor->image_surface,
614 } 651 cursor->hotspot_x,
652 cursor->hotspot_y,
653 cursor->image_client);
654 } else {
655 const char *image = cursor->image;
656 cursor->image = NULL;
657 cursor_set_image(cursor, image, cursor->image_client);
615 } 658 }
616} 659}
617 660
@@ -632,6 +675,9 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor,
632 case OP_MOVE_FLOATING: 675 case OP_MOVE_FLOATING:
633 handle_move_floating_motion(seat, cursor); 676 handle_move_floating_motion(seat, cursor);
634 break; 677 break;
678 case OP_MOVE_TILING_THRESHOLD:
679 handle_move_tiling_threshold_motion(seat, cursor);
680 break;
635 case OP_MOVE_TILING: 681 case OP_MOVE_TILING:
636 handle_move_tiling_motion(seat, cursor); 682 handle_move_tiling_motion(seat, cursor);
637 break; 683 break;
@@ -709,7 +755,7 @@ static void handle_cursor_motion(struct wl_listener *listener, void *data) {
709 wlr_cursor_move(cursor->cursor, event->device, 755 wlr_cursor_move(cursor->cursor, event->device,
710 event->delta_x, event->delta_y); 756 event->delta_x, event->delta_y);
711 cursor_send_pointer_motion(cursor, event->time_msec); 757 cursor_send_pointer_motion(cursor, event->time_msec);
712 handle_activity(cursor); 758 cursor_handle_activity(cursor);
713 transaction_commit_dirty(); 759 transaction_commit_dirty();
714} 760}
715 761
@@ -720,7 +766,7 @@ static void handle_cursor_motion_absolute(
720 struct wlr_event_pointer_motion_absolute *event = data; 766 struct wlr_event_pointer_motion_absolute *event = data;
721 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y); 767 wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y);
722 cursor_send_pointer_motion(cursor, event->time_msec); 768 cursor_send_pointer_motion(cursor, event->time_msec);
723 handle_activity(cursor); 769 cursor_handle_activity(cursor);
724 transaction_commit_dirty(); 770 transaction_commit_dirty();
725} 771}
726 772
@@ -976,12 +1022,21 @@ void dispatch_cursor_button(struct sway_cursor *cursor,
976 if (config->tiling_drag && (mod_pressed || on_titlebar) && 1022 if (config->tiling_drag && (mod_pressed || on_titlebar) &&
977 state == WLR_BUTTON_PRESSED && !is_floating_or_child && 1023 state == WLR_BUTTON_PRESSED && !is_floating_or_child &&
978 cont && !cont->is_fullscreen) { 1024 cont && !cont->is_fullscreen) {
979 if (on_titlebar) { 1025 struct sway_container *focus = seat_get_focused_container(seat);
1026 bool focused = focus == cont || container_has_ancestor(focus, cont);
1027 if (on_titlebar && !focused) {
980 node = seat_get_focus_inactive(seat, &cont->node); 1028 node = seat_get_focus_inactive(seat, &cont->node);
981 seat_set_focus(seat, node); 1029 seat_set_focus(seat, node);
982 } 1030 }
1031
983 seat_pointer_notify_button(seat, time_msec, button, state); 1032 seat_pointer_notify_button(seat, time_msec, button, state);
984 seat_begin_move_tiling(seat, cont, button); 1033
1034 // If moving a container by it's title bar, use a threshold for the drag
1035 if (!mod_pressed && config->tiling_drag_threshold > 0) {
1036 seat_begin_move_tiling_threshold(seat, cont, button);
1037 } else {
1038 seat_begin_move_tiling(seat, cont, button);
1039 }
985 return; 1040 return;
986 } 1041 }
987 1042
@@ -1009,7 +1064,7 @@ static void handle_cursor_button(struct wl_listener *listener, void *data) {
1009 struct wlr_event_pointer_button *event = data; 1064 struct wlr_event_pointer_button *event = data;
1010 dispatch_cursor_button(cursor, event->device, 1065 dispatch_cursor_button(cursor, event->device,
1011 event->time_msec, event->button, event->state); 1066 event->time_msec, event->button, event->state);
1012 handle_activity(cursor); 1067 cursor_handle_activity(cursor);
1013 transaction_commit_dirty(); 1068 transaction_commit_dirty();
1014} 1069}
1015 1070
@@ -1119,7 +1174,7 @@ static void handle_cursor_axis(struct wl_listener *listener, void *data) {
1119 struct sway_cursor *cursor = wl_container_of(listener, cursor, axis); 1174 struct sway_cursor *cursor = wl_container_of(listener, cursor, axis);
1120 struct wlr_event_pointer_axis *event = data; 1175 struct wlr_event_pointer_axis *event = data;
1121 dispatch_cursor_axis(cursor, event); 1176 dispatch_cursor_axis(cursor, event);
1122 handle_activity(cursor); 1177 cursor_handle_activity(cursor);
1123 transaction_commit_dirty(); 1178 transaction_commit_dirty();
1124} 1179}
1125 1180
diff --git a/sway/input/input-manager.c b/sway/input/input-manager.c
index 055f6752..61087733 100644
--- a/sway/input/input-manager.c
+++ b/sway/input/input-manager.c
@@ -95,6 +95,18 @@ static bool input_has_seat_fallback_configuration(void) {
95 return false; 95 return false;
96} 96}
97 97
98void input_manager_verify_fallback_seat(void) {
99 struct sway_seat *seat = NULL;
100 if (!input_has_seat_fallback_configuration()) {
101 wlr_log(WLR_DEBUG, "no fallback seat config - creating default");
102 seat = input_manager_get_default_seat();
103 struct seat_config *sc = new_seat_config(seat->wlr_seat->name);
104 sc->fallback = true;
105 sc = store_seat_config(sc);
106 input_manager_apply_seat_config(sc);
107 }
108}
109
98static void input_manager_libinput_config_keyboard( 110static void input_manager_libinput_config_keyboard(
99 struct sway_input_device *input_device) { 111 struct sway_input_device *input_device) {
100 struct wlr_input_device *wlr_device = input_device->wlr_device; 112 struct wlr_input_device *wlr_device = input_device->wlr_device;
@@ -296,16 +308,10 @@ static void handle_new_input(struct wl_listener *listener, void *data) {
296 wl_signal_add(&device->events.destroy, &input_device->device_destroy); 308 wl_signal_add(&device->events.destroy, &input_device->device_destroy);
297 input_device->device_destroy.notify = handle_device_destroy; 309 input_device->device_destroy.notify = handle_device_destroy;
298 310
299 struct sway_seat *seat = NULL; 311 input_manager_verify_fallback_seat();
300 if (!input_has_seat_fallback_configuration()) {
301 wlr_log(WLR_DEBUG, "no seat config - creating default seat config");
302 seat = input_manager_get_default_seat();
303 struct seat_config *sc = new_seat_config(seat->wlr_seat->name);
304 sc->fallback = true;
305 store_seat_config(sc);
306 }
307 312
308 bool added = false; 313 bool added = false;
314 struct sway_seat *seat = NULL;
309 wl_list_for_each(seat, &input->seats, link) { 315 wl_list_for_each(seat, &input->seats, link) {
310 struct seat_config *seat_config = seat_get_config(seat); 316 struct seat_config *seat_config = seat_get_config(seat);
311 bool has_attachment = seat_config && 317 bool has_attachment = seat_config &&
diff --git a/sway/input/seat.c b/sway/input/seat.c
index e0f0db1d..52790039 100644
--- a/sway/input/seat.c
+++ b/sway/input/seat.c
@@ -995,6 +995,8 @@ void seat_apply_config(struct sway_seat *seat,
995 wl_list_for_each(seat_device, &seat->devices, link) { 995 wl_list_for_each(seat_device, &seat->devices, link) {
996 seat_configure_device(seat, seat_device->input_device); 996 seat_configure_device(seat, seat_device->input_device);
997 } 997 }
998
999 cursor_handle_activity(seat->cursor);
998} 1000}
999 1001
1000struct seat_config *seat_get_config(struct sway_seat *seat) { 1002struct seat_config *seat_get_config(struct sway_seat *seat) {
@@ -1009,6 +1011,18 @@ struct seat_config *seat_get_config(struct sway_seat *seat) {
1009 return NULL; 1011 return NULL;
1010} 1012}
1011 1013
1014struct seat_config *seat_get_config_by_name(const char *name) {
1015 struct seat_config *seat_config = NULL;
1016 for (int i = 0; i < config->seat_configs->length; ++i ) {
1017 seat_config = config->seat_configs->items[i];
1018 if (strcmp(name, seat_config->name) == 0) {
1019 return seat_config;
1020 }
1021 }
1022
1023 return NULL;
1024}
1025
1012void seat_begin_down(struct sway_seat *seat, struct sway_container *con, 1026void seat_begin_down(struct sway_seat *seat, struct sway_container *con,
1013 uint32_t button, double sx, double sy) { 1027 uint32_t button, double sx, double sy) {
1014 seat->operation = OP_DOWN; 1028 seat->operation = OP_DOWN;
@@ -1038,6 +1052,17 @@ void seat_begin_move_floating(struct sway_seat *seat,
1038 cursor_set_image(seat->cursor, "grab", NULL); 1052 cursor_set_image(seat->cursor, "grab", NULL);
1039} 1053}
1040 1054
1055void seat_begin_move_tiling_threshold(struct sway_seat *seat,
1056 struct sway_container *con, uint32_t button) {
1057 seat->operation = OP_MOVE_TILING_THRESHOLD;
1058 seat->op_container = con;
1059 seat->op_button = button;
1060 seat->op_target_node = NULL;
1061 seat->op_target_edge = 0;
1062 seat->op_ref_lx = seat->cursor->cursor->x;
1063 seat->op_ref_ly = seat->cursor->cursor->y;
1064}
1065
1041void seat_begin_move_tiling(struct sway_seat *seat, 1066void seat_begin_move_tiling(struct sway_seat *seat,
1042 struct sway_container *con, uint32_t button) { 1067 struct sway_container *con, uint32_t button) {
1043 seat->operation = OP_MOVE_TILING; 1068 seat->operation = OP_MOVE_TILING;
@@ -1206,4 +1231,8 @@ void seat_consider_warp_to_focus(struct sway_seat *seat) {
1206 } else { 1231 } else {
1207 cursor_warp_to_workspace(seat->cursor, focus->sway_workspace); 1232 cursor_warp_to_workspace(seat->cursor, focus->sway_workspace);
1208 } 1233 }
1234 if (seat->cursor->hidden){
1235 cursor_unhide(seat->cursor);
1236 wl_event_source_timer_update(seat->cursor->hide_source, cursor_get_timeout(seat->cursor));
1237 }
1209} 1238}
diff --git a/sway/ipc-json.c b/sway/ipc-json.c
index 96701dc2..53e0e335 100644
--- a/sway/ipc-json.c
+++ b/sway/ipc-json.c
@@ -1,6 +1,7 @@
1#include <json-c/json.h> 1#include <json-c/json.h>
2#include <stdio.h> 2#include <stdio.h>
3#include <ctype.h> 3#include <ctype.h>
4#include "config.h"
4#include "log.h" 5#include "log.h"
5#include "sway/config.h" 6#include "sway/config.h"
6#include "sway/ipc-json.h" 7#include "sway/ipc-json.h"
@@ -785,5 +786,41 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) {
785 } 786 }
786 json_object_object_add(json, "outputs", outputs); 787 json_object_object_add(json, "outputs", outputs);
787 } 788 }
789#if HAVE_TRAY
790 // Add tray outputs if defined
791 if (bar->tray_outputs && bar->tray_outputs->length > 0) {
792 json_object *tray_outputs = json_object_new_array();
793 for (int i = 0; i < bar->tray_outputs->length; ++i) {
794 const char *name = bar->tray_outputs->items[i];
795 json_object_array_add(tray_outputs, json_object_new_string(name));
796 }
797 json_object_object_add(json, "tray_outputs", tray_outputs);
798 }
799
800 json_object *tray_bindings = json_object_new_array();
801 for (int i = 0; i < 10; ++i) {
802 if (bar->tray_bindings[i]) {
803 json_object *bind = json_object_new_object();
804 json_object_object_add(bind, "input_code",
805 json_object_new_int(i));
806 json_object_object_add(bind, "command",
807 json_object_new_string(bar->tray_bindings[i]));
808 json_object_array_add(tray_bindings, bind);
809 }
810 }
811 if (json_object_array_length(tray_bindings) > 0) {
812 json_object_object_add(json, "tray_bindings", tray_bindings);
813 } else {
814 json_object_put(tray_bindings);
815 }
816
817 if (bar->icon_theme) {
818 json_object_object_add(json, "icon_theme",
819 json_object_new_string(bar->icon_theme));
820 }
821
822 json_object_object_add(json, "tray_padding",
823 json_object_new_int(bar->tray_padding));
824#endif
788 return json; 825 return json;
789} 826}
diff --git a/sway/meson.build b/sway/meson.build
index 48ce6b45..98676ce0 100644
--- a/sway/meson.build
+++ b/sway/meson.build
@@ -56,7 +56,6 @@ sway_sources = files(
56 'commands/force_focus_wrapping.c', 56 'commands/force_focus_wrapping.c',
57 'commands/fullscreen.c', 57 'commands/fullscreen.c',
58 'commands/gaps.c', 58 'commands/gaps.c',
59 'commands/hide_cursor.c',
60 'commands/hide_edge_borders.c', 59 'commands/hide_edge_borders.c',
61 'commands/kill.c', 60 'commands/kill.c',
62 'commands/mark.c', 61 'commands/mark.c',
@@ -79,6 +78,7 @@ sway_sources = files(
79 'commands/seat/attach.c', 78 'commands/seat/attach.c',
80 'commands/seat/cursor.c', 79 'commands/seat/cursor.c',
81 'commands/seat/fallback.c', 80 'commands/seat/fallback.c',
81 'commands/seat/hide_cursor.c',
82 'commands/set.c', 82 'commands/set.c',
83 'commands/show_marks.c', 83 'commands/show_marks.c',
84 'commands/smart_borders.c', 84 'commands/smart_borders.c',
@@ -89,6 +89,7 @@ sway_sources = files(
89 'commands/swaynag_command.c', 89 'commands/swaynag_command.c',
90 'commands/swap.c', 90 'commands/swap.c',
91 'commands/tiling_drag.c', 91 'commands/tiling_drag.c',
92 'commands/tiling_drag_threshold.c',
92 'commands/title_align.c', 93 'commands/title_align.c',
93 'commands/title_format.c', 94 'commands/title_format.c',
94 'commands/titlebar_border_thickness.c', 95 'commands/titlebar_border_thickness.c',
@@ -99,11 +100,9 @@ sway_sources = files(
99 'commands/workspace_layout.c', 100 'commands/workspace_layout.c',
100 'commands/ws_auto_back_and_forth.c', 101 'commands/ws_auto_back_and_forth.c',
101 102
102 'commands/bar/activate_button.c',
103 'commands/bar/binding_mode_indicator.c', 103 'commands/bar/binding_mode_indicator.c',
104 'commands/bar/bindsym.c', 104 'commands/bar/bindsym.c',
105 'commands/bar/colors.c', 105 'commands/bar/colors.c',
106 'commands/bar/context_button.c',
107 'commands/bar/font.c', 106 'commands/bar/font.c',
108 'commands/bar/gaps.c', 107 'commands/bar/gaps.c',
109 'commands/bar/height.c', 108 'commands/bar/height.c',
@@ -115,12 +114,12 @@ sway_sources = files(
115 'commands/bar/output.c', 114 'commands/bar/output.c',
116 'commands/bar/pango_markup.c', 115 'commands/bar/pango_markup.c',
117 'commands/bar/position.c', 116 'commands/bar/position.c',
118 'commands/bar/secondary_button.c',
119 'commands/bar/separator_symbol.c', 117 'commands/bar/separator_symbol.c',
120 'commands/bar/status_command.c', 118 'commands/bar/status_command.c',
121 'commands/bar/strip_workspace_numbers.c', 119 'commands/bar/strip_workspace_numbers.c',
122 'commands/bar/strip_workspace_name.c', 120 'commands/bar/strip_workspace_name.c',
123 'commands/bar/swaybar_command.c', 121 'commands/bar/swaybar_command.c',
122 'commands/bar/tray_bindsym.c',
124 'commands/bar/tray_output.c', 123 'commands/bar/tray_output.c',
125 'commands/bar/tray_padding.c', 124 'commands/bar/tray_padding.c',
126 'commands/bar/workspace_buttons.c', 125 'commands/bar/workspace_buttons.c',
diff --git a/sway/server.c b/sway/server.c
index b1d7d3fc..13264a2c 100644
--- a/sway/server.c
+++ b/sway/server.c
@@ -7,6 +7,7 @@
7#include <wlr/backend/session.h> 7#include <wlr/backend/session.h>
8#include <wlr/render/wlr_renderer.h> 8#include <wlr/render/wlr_renderer.h>
9#include <wlr/types/wlr_compositor.h> 9#include <wlr/types/wlr_compositor.h>
10#include <wlr/types/wlr_data_control_v1.h>
10#include <wlr/types/wlr_export_dmabuf_v1.h> 11#include <wlr/types/wlr_export_dmabuf_v1.h>
11#include <wlr/types/wlr_gamma_control_v1.h> 12#include <wlr/types/wlr_gamma_control_v1.h>
12#include <wlr/types/wlr_gamma_control.h> 13#include <wlr/types/wlr_gamma_control.h>
@@ -140,6 +141,7 @@ bool server_init(struct sway_server *server) {
140 141
141 wlr_export_dmabuf_manager_v1_create(server->wl_display); 142 wlr_export_dmabuf_manager_v1_create(server->wl_display);
142 wlr_screencopy_manager_v1_create(server->wl_display); 143 wlr_screencopy_manager_v1_create(server->wl_display);
144 wlr_data_control_manager_v1_create(server->wl_display);
143 145
144 server->socket = wl_display_add_socket_auto(server->wl_display); 146 server->socket = wl_display_add_socket_auto(server->wl_display);
145 if (!server->socket) { 147 if (!server->socket) {
diff --git a/sway/sway-bar.5.scd b/sway/sway-bar.5.scd
index a3c6af2e..2357591d 100644
--- a/sway/sway-bar.5.scd
+++ b/sway/sway-bar.5.scd
@@ -100,27 +100,20 @@ The following commands configure the tray.
100The _button_ argument in all cases is a platform-specific button code. On Linux 100The _button_ argument in all cases is a platform-specific button code. On Linux
101you can find a list of these at linux/input-event-codes.h. 101you can find a list of these at linux/input-event-codes.h.
102 102
103*activate\_button* <button> 103*tray\_bindsym* button<n> ContextMenu|Activate|SecondaryActivate|ScrollDown|ScrollLeft|ScrollRight|ScrollUp|nop
104 Sets the button to be used for the _activate_ (primary click) tray item 104 Binds mouse button _n_ (1 to 9) to the specified action. Use the command
105 event. The default is BTN\_LEFT (0x110). 105 _nop_ to disable the default action (Activate for button 1, ContextMenu for
106 106 button 2 and SecondaryActivate for button 3).
107*context\_button* <button>
108 Sets the button to be used for the _context menu_ (right click) tray item
109 event. The default is BTN\_RIGHT (0x111).
110
111*secondary\_button* <button>
112 Sets the button to be used for the _secondary_ (middle click) tray item
113 event. The default is BTN\_MIDDLE (0x112).
114
115*tray\_output* none|all|<output>
116 Sets the output that the tray will appear on or none. Unlike i3bar, swaybar
117 is able to show icons on any number of bars and outputs without races.
118 The default is _all_.
119 107
120*tray\_padding* <px> [px] 108*tray\_padding* <px> [px]
121 Sets the pixel padding of the system tray. This padding will surround the 109 Sets the pixel padding of the system tray. This padding will surround the
122 tray on all sides and between each item. The default value for _px_ is 2. 110 tray on all sides and between each item. The default value for _px_ is 2.
123 111
112*tray\_output* none|<output>
113 Restrict the tray to a certain output, can be specified multiple times. If
114 omitted, the tray will be displayed on all outputs. Unlike i3bar, swaybar
115 can show icons on any number of bars and outputs without races.
116
124*icon\_theme* <name> 117*icon\_theme* <name>
125 Sets the icon theme that sway will look for item icons in. This option has 118 Sets the icon theme that sway will look for item icons in. This option has
126 no default value, because sway will always default to the fallback theme, 119 no default value, because sway will always default to the fallback theme,
diff --git a/sway/sway-input.5.scd b/sway/sway-input.5.scd
index 45994644..820194a9 100644
--- a/sway/sway-input.5.scd
+++ b/sway/sway-input.5.scd
@@ -145,6 +145,12 @@ in their own "seat").
145 Set this seat as the fallback seat. A fallback seat will attach any device 145 Set this seat as the fallback seat. A fallback seat will attach any device
146 not explicitly attached to another seat (similar to a "default" seat). 146 not explicitly attached to another seat (similar to a "default" seat).
147 147
148*seat* <name> hide\_cursor <timeout>
149 Hides the cursor image after the specified _timeout_ (in milliseconds)
150 has elapsed with no activity on that cursor. A timeout of 0 (default)
151 disables hiding the cursor. The minimal timeout is 100 and any value less
152 than that (aside from 0), will be increased to 100.
153
148# SEE ALSO 154# SEE ALSO
149 155
150*sway*(5) *sway-output*(5) 156*sway*(5) *sway-output*(5)
diff --git a/sway/sway-output.5.scd b/sway/sway-output.5.scd
index 9940d8e8..28524478 100644
--- a/sway/sway-output.5.scd
+++ b/sway/sway-output.5.scd
@@ -38,17 +38,20 @@ must be separated by one space. For example:
38 Places the specified output at the specific position in the global 38 Places the specified output at the specific position in the global
39 coordinate space. If scaling is active, it has to be considered when 39 coordinate space. If scaling is active, it has to be considered when
40 positioning. For example, if the scaling factor for the left output is 2, 40 positioning. For example, if the scaling factor for the left output is 2,
41 the relative position for the right output has to be divided by 2. 41 the relative position for the right output has to be divided by 2. The
42 reference point is the top left corner so if you want the bottoms aligned
43 this has to be considered as well.
42 44
43 Example: 45 Example:
44 46
45 output HDMI1 scale 2 47 output HDMI1 scale 2
46 48
47 output HDMI1 pos 0 0 res 3200x1800 49 output HDMI1 pos 0 1020 res 3200x1800
48 50
49 output eDP1 pos 1600 0 res 1920x1080 51 output eDP1 pos 1600 0 res 1920x1080
50 52
51 Note that the x-pos of eDP1 is 1600 = 3200/2. 53 Note that the left x-pos of eDP1 is 1600 = 3200/2 and the bottom y-pos is
54 1020 + (1800 / 2) = 1920 = 0 + 1920
52 55
53*output* <name> scale <factor> 56*output* <name> scale <factor>
54 Scales the specified output by the specified scale _factor_. An integer is 57 Scales the specified output by the specified scale _factor_. An integer is
@@ -57,7 +60,8 @@ must be separated by one space. For example:
57 represent the contents of your windows - they will be rendered at the next 60 represent the contents of your windows - they will be rendered at the next
58 highest integral scale factor and downscaled. You may be better served by 61 highest integral scale factor and downscaled. You may be better served by
59 setting an integral scale factor and adjusting the font size of your 62 setting an integral scale factor and adjusting the font size of your
60 applications to taste. 63 applications to taste. HiDPI isn't supported with Xwayland clients (windows
64 will blur).
61 65
62*output* <name> background|bg <file> <mode> [<fallback\_color>] 66*output* <name> background|bg <file> <mode> [<fallback\_color>]
63 Sets the wallpaper for the given output to the specified file, using the 67 Sets the wallpaper for the given output to the specified file, using the
diff --git a/sway/sway.1.scd b/sway/sway.1.scd
index f66c4cdb..09c8ccfd 100644
--- a/sway/sway.1.scd
+++ b/sway/sway.1.scd
@@ -71,18 +71,14 @@ with *i3-msg*(1) or even with *i3*(1).
71 71
72The following environment variables have an effect on sway: 72The following environment variables have an effect on sway:
73 73
74_SWAY\_CURSOR\_THEME_
75 Specifies the name of the cursor theme to use.
76
77_SWAY\_CURSOR\_SIZE_
78 Specifies the size of the cursor to use.
79
80_SWAYSOCK_ 74_SWAYSOCK_
81 Specifies the path to the sway IPC socket. 75 Specifies the path to the sway IPC socket.
82 76
83_XKB\_DEFAULT\_RULES_, _XKB\_DEFAULT\_MODEL_, _XKB\_DEFAULT\_LAYOUT_, 77_XKB\_DEFAULT\_RULES_, _XKB\_DEFAULT\_MODEL_, _XKB\_DEFAULT\_LAYOUT_,
84_XKB\_DEFAULT\_VARIANT_, _XKB\_DEFAULT\_OPTIONS_ 78_XKB\_DEFAULT\_VARIANT_, _XKB\_DEFAULT\_OPTIONS_
85 Configures the xkb keyboard settings. See *xkeyboard-config*(7). 79 Configures the xkb keyboard settings. See *xkeyboard-config*(7). The
80 preferred way to configure the keyboard is via the configuration file, see
81 *sway-input*(5).
86 82
87# AUTHORS 83# AUTHORS
88 84
diff --git a/sway/sway.5.scd b/sway/sway.5.scd
index 2befcfac..3757a097 100644
--- a/sway/sway.5.scd
+++ b/sway/sway.5.scd
@@ -327,7 +327,8 @@ runtime.
327 A view that does not have focus. 327 A view that does not have focus.
328 328
329 *client.urgent* 329 *client.urgent*
330 A view with an urgency hint. *Note*: This is not currently implemented. 330 A view with an urgency hint. *Note*: Native Wayland windows do not
331 support urgency. Urgency only works for Xwayland windows.
331 332
332 The meaning of each color is: 333 The meaning of each color is:
333 334
@@ -431,7 +432,7 @@ The default colors are:
431 432
432*focus\_follows\_mouse* yes|no|always 433*focus\_follows\_mouse* yes|no|always
433 If set to _yes_, moving your mouse over a window will focus that window. If 434 If set to _yes_, moving your mouse over a window will focus that window. If
434 set to _always_, the window under the cursor will always be focused, even 435 set to _always_, the window under the cursor will always be focused, even
435 after switching between workspaces. 436 after switching between workspaces.
436 437
437*focus\_wrapping* yes|no|force 438*focus\_wrapping* yes|no|force
@@ -450,11 +451,11 @@ The default colors are:
450 Thickness of the titlebar border in pixels 451 Thickness of the titlebar border in pixels
451 452
452*titlebar\_padding* <horizontal> [<vertical>] 453*titlebar\_padding* <horizontal> [<vertical>]
453 Padding of the text in the titlebar. _horizontal_ value affects horizontal 454 Padding of the text in the titlebar. _horizontal_ value affects horizontal
454 padding of the text while _vertical_ value affects vertical padding (space 455 padding of the text while _vertical_ value affects vertical padding (space
455 above and below text). Padding includes titlebar borders so their value 456 above and below text). Padding includes titlebar borders so their value
456 should be greater than titlebar\_border\_thickness. If _vertical_ value is 457 should be greater than titlebar\_border\_thickness. If _vertical_ value is
457 not specified it is set to the _horizontal_ value. 458 not specified it is set to the _horizontal_ value.
458 459
459*for\_window* <criteria> <command> 460*for\_window* <criteria> <command>
460 Whenever a window that matches _criteria_ appears, run list of commands. 461 Whenever a window that matches _criteria_ appears, run list of commands.
@@ -477,12 +478,6 @@ The default colors are:
477 This affects new workspaces only, and is used when the workspace doesn't 478 This affects new workspaces only, and is used when the workspace doesn't
478 have its own gaps settings (see: workspace <ws> gaps ...). 479 have its own gaps settings (see: workspace <ws> gaps ...).
479 480
480*hide\_cursor* <timeout>
481 Hides the cursor image after the specified _timeout_ (in milliseconds)
482 has elapsed with no activity on that cursor. A timeout of 0 (default)
483 disables hiding the cursor. The minimal timeout is 100 and any value less
484 than that (aside from 0), will be increased to 100.
485
486*hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps 481*hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps
487 Hides window borders adjacent to the screen edges. Default is _none_. 482 Hides window borders adjacent to the screen edges. Default is _none_.
488 483
@@ -578,6 +573,14 @@ The default colors are:
578 the _floating\_mod_ will also allow the container to be dragged. _toggle_ 573 the _floating\_mod_ will also allow the container to be dragged. _toggle_
579 should not be used in the config file. 574 should not be used in the config file.
580 575
576*tiling\_drag\_threshold* <threshold>
577 Sets the threshold that must be exceeded for a container to be dragged by
578 its titlebar. This has no effect if _floating\_mod_ is used or if
579 _tiling\_drag_ is set to _disable_. Once the threshold has been exceeded
580 once, the drag starts and the cursor can come back inside the threshold
581 without stopping the drag. _threshold_ is multiplied by the scale of the
582 output that the cursor on. The default is 9.
583
581*title\_align* left|center|right 584*title\_align* left|center|right
582 Sets the title alignment. If _right_ is selected and _show\_marks_ is set 585 Sets the title alignment. If _right_ is selected and _show\_marks_ is set
583 to _yes_, the marks will be shown on the _left_ side instead of the 586 to _yes_, the marks will be shown on the _left_ side instead of the
diff --git a/sway/tree/container.c b/sway/tree/container.c
index 0a96088e..99262356 100644
--- a/sway/tree/container.c
+++ b/sway/tree/container.c
@@ -453,19 +453,26 @@ static void update_title_texture(struct sway_container *con,
453 int width = 0; 453 int width = 0;
454 int height = con->title_height * scale; 454 int height = con->title_height * scale;
455 455
456 cairo_t *c = cairo_create(NULL); 456 // We must use a non-nil cairo_t for cairo_set_font_options to work.
457 // Therefore, we cannot use cairo_create(NULL).
458 cairo_surface_t *dummy_surface = cairo_image_surface_create(
459 CAIRO_FORMAT_ARGB32, 0, 0);
460 cairo_t *c = cairo_create(dummy_surface);
461 cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
462 cairo_font_options_t *fo = cairo_font_options_create();
463 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
464 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
465 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(output->wlr_output->subpixel));
466 cairo_set_font_options(c, fo);
457 get_text_size(c, config->font, &width, NULL, NULL, scale, 467 get_text_size(c, config->font, &width, NULL, NULL, scale,
458 config->pango_markup, "%s", con->formatted_title); 468 config->pango_markup, "%s", con->formatted_title);
469 cairo_surface_destroy(dummy_surface);
459 cairo_destroy(c); 470 cairo_destroy(c);
460 471
461 cairo_surface_t *surface = cairo_image_surface_create( 472 cairo_surface_t *surface = cairo_image_surface_create(
462 CAIRO_FORMAT_ARGB32, width, height); 473 CAIRO_FORMAT_ARGB32, width, height);
463 cairo_t *cairo = cairo_create(surface); 474 cairo_t *cairo = cairo_create(surface);
464 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); 475 cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
465 cairo_font_options_t *fo = cairo_font_options_create();
466 cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
467 cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
468 cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(output->wlr_output->subpixel));
469 cairo_set_font_options(cairo, fo); 476 cairo_set_font_options(cairo, fo);
470 cairo_font_options_destroy(fo); 477 cairo_font_options_destroy(fo);
471 cairo_set_source_rgba(cairo, class->background[0], class->background[1], 478 cairo_set_source_rgba(cairo, class->background[0], class->background[1],
diff --git a/sway/tree/view.c b/sway/tree/view.c
index deb20676..5371ee20 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -654,14 +654,8 @@ void view_unmap(struct sway_view *view) {
654 654
655 struct sway_seat *seat; 655 struct sway_seat *seat;
656 wl_list_for_each(seat, &server.input->seats, link) { 656 wl_list_for_each(seat, &server.input->seats, link) {
657 if (config->mouse_warping == WARP_CONTAINER) { 657 seat->cursor->image_surface = NULL;
658 struct sway_node *node = seat_get_focus(seat); 658 seat_consider_warp_to_focus(seat);
659 if (node && node->type == N_CONTAINER) {
660 cursor_warp_to_container(seat->cursor, node->sway_container);
661 } else if (node && node->type == N_WORKSPACE) {
662 cursor_warp_to_workspace(seat->cursor, node->sway_workspace);
663 }
664 }
665 } 659 }
666 660
667 transaction_commit_dirty(); 661 transaction_commit_dirty();
diff --git a/swaybar/bar.c b/swaybar/bar.c
index 53e798bc..7aed4dca 100644
--- a/swaybar/bar.c
+++ b/swaybar/bar.c
@@ -11,6 +11,7 @@
11#include <wayland-client.h> 11#include <wayland-client.h>
12#include <wayland-cursor.h> 12#include <wayland-cursor.h>
13#include <wlr/util/log.h> 13#include <wlr/util/log.h>
14#include "config.h"
14#include "swaybar/bar.h" 15#include "swaybar/bar.h"
15#include "swaybar/config.h" 16#include "swaybar/config.h"
16#include "swaybar/i3bar.h" 17#include "swaybar/i3bar.h"
@@ -18,6 +19,9 @@
18#include "swaybar/ipc.h" 19#include "swaybar/ipc.h"
19#include "swaybar/status_line.h" 20#include "swaybar/status_line.h"
20#include "swaybar/render.h" 21#include "swaybar/render.h"
22#if HAVE_TRAY
23#include "swaybar/tray/tray.h"
24#endif
21#include "ipc-client.h" 25#include "ipc-client.h"
22#include "list.h" 26#include "list.h"
23#include "log.h" 27#include "log.h"
@@ -120,7 +124,7 @@ static void destroy_layer_surface(struct swaybar_output *output) {
120 output->frame_scheduled = false; 124 output->frame_scheduled = false;
121} 125}
122 126
123static void set_bar_dirty(struct swaybar *bar) { 127void set_bar_dirty(struct swaybar *bar) {
124 struct swaybar_output *output; 128 struct swaybar_output *output;
125 wl_list_for_each(output, &bar->outputs, link) { 129 wl_list_for_each(output, &bar->outputs, link) {
126 set_output_dirty(output); 130 set_output_dirty(output);
@@ -211,12 +215,16 @@ struct wl_output_listener output_listener = {
211 215
212static void xdg_output_handle_logical_position(void *data, 216static void xdg_output_handle_logical_position(void *data,
213 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { 217 struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
214 // Who cares 218 struct swaybar_output *output = data;
219 output->output_x = x;
220 output->output_y = y;
215} 221}
216 222
217static void xdg_output_handle_logical_size(void *data, 223static void xdg_output_handle_logical_size(void *data,
218 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { 224 struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
219 // Who cares 225 struct swaybar_output *output = data;
226 output->output_height = height;
227 output->output_width = width;
220} 228}
221 229
222static void xdg_output_handle_done(void *data, 230static void xdg_output_handle_done(void *data,
@@ -362,6 +370,12 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) {
362 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor); 370 pointer->cursor_surface = wl_compositor_create_surface(bar->compositor);
363 assert(pointer->cursor_surface); 371 assert(pointer->cursor_surface);
364 372
373#if HAVE_TRAY
374 if (!bar->config->tray_hidden) {
375 bar->tray = create_tray(bar);
376 }
377#endif
378
365 if (bar->config->workspace_buttons) { 379 if (bar->config->workspace_buttons) {
366 ipc_get_workspaces(bar); 380 ipc_get_workspaces(bar);
367 } 381 }
@@ -403,6 +417,11 @@ void bar_run(struct swaybar *bar) {
403 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN, 417 loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN,
404 status_in, bar); 418 status_in, bar);
405 } 419 }
420#if HAVE_TRAY
421 if (bar->tray) {
422 loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus);
423 }
424#endif
406 while (1) { 425 while (1) {
407 errno = 0; 426 errno = 0;
408 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) { 427 if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) {
@@ -420,6 +439,9 @@ static void free_outputs(struct wl_list *list) {
420} 439}
421 440
422void bar_teardown(struct swaybar *bar) { 441void bar_teardown(struct swaybar *bar) {
442#if HAVE_TRAY
443 destroy_tray(bar->tray);
444#endif
423 free_outputs(&bar->outputs); 445 free_outputs(&bar->outputs);
424 if (bar->config) { 446 if (bar->config) {
425 free_config(bar->config); 447 free_config(bar->config);
diff --git a/swaybar/config.c b/swaybar/config.c
index 10c78c8a..9cafe061 100644
--- a/swaybar/config.c
+++ b/swaybar/config.c
@@ -4,6 +4,7 @@
4#include <wlr/util/log.h> 4#include <wlr/util/log.h>
5#include "swaybar/config.h" 5#include "swaybar/config.h"
6#include "wlr-layer-shell-unstable-v1-client-protocol.h" 6#include "wlr-layer-shell-unstable-v1-client-protocol.h"
7#include "config.h"
7#include "stringop.h" 8#include "stringop.h"
8#include "list.h" 9#include "list.h"
9 10
@@ -73,6 +74,10 @@ struct swaybar_config *init_config(void) {
73 config->colors.binding_mode.background = 0x900000FF; 74 config->colors.binding_mode.background = 0x900000FF;
74 config->colors.binding_mode.text = 0xFFFFFFFF; 75 config->colors.binding_mode.text = 0xFFFFFFFF;
75 76
77#if HAVE_TRAY
78 config->tray_padding = 2;
79#endif
80
76 return config; 81 return config;
77} 82}
78 83
@@ -102,5 +107,12 @@ void free_config(struct swaybar_config *config) {
102 free(coutput->name); 107 free(coutput->name);
103 free(coutput); 108 free(coutput);
104 } 109 }
110#if HAVE_TRAY
111 list_free_items_and_destroy(config->tray_outputs);
112 for (int i = 0; i < 10; ++i) {
113 free(config->tray_bindings[i]);
114 }
115 free(config->icon_theme);
116#endif
105 free(config); 117 free(config);
106} 118}
diff --git a/swaybar/ipc.c b/swaybar/ipc.c
index 2b930786..8e7a542e 100644
--- a/swaybar/ipc.c
+++ b/swaybar/ipc.c
@@ -6,6 +6,7 @@
6#include <wlr/util/log.h> 6#include <wlr/util/log.h>
7#include "swaybar/config.h" 7#include "swaybar/config.h"
8#include "swaybar/ipc.h" 8#include "swaybar/ipc.h"
9#include "config.h"
9#include "ipc-client.h" 10#include "ipc-client.h"
10#include "list.h" 11#include "list.h"
11 12
@@ -282,6 +283,40 @@ static bool ipc_parse_config(
282 ipc_parse_colors(config, colors); 283 ipc_parse_colors(config, colors);
283 } 284 }
284 285
286#if HAVE_TRAY
287 json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme;
288
289 if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) {
290 config->tray_outputs = create_list();
291 int length = json_object_array_length(tray_outputs);
292 for (int i = 0; i < length; ++i) {
293 json_object *o = json_object_array_get_idx(tray_outputs, i);
294 list_add(config->tray_outputs, strdup(json_object_get_string(o)));
295 }
296 config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0;
297 }
298
299 if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) {
300 config->tray_padding = json_object_get_int(tray_padding);
301 }
302
303 if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) {
304 int length = json_object_array_length(tray_bindings);
305 for (int i = 0; i < length; ++i) {
306 json_object *bind = json_object_array_get_idx(tray_bindings, i);
307 json_object *button, *command;
308 json_object_object_get_ex(bind, "input_code", &button);
309 json_object_object_get_ex(bind, "command", &command);
310 config->tray_bindings[json_object_get_int(button)] =
311 strdup(json_object_get_string(command));
312 }
313 }
314
315 if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) {
316 config->icon_theme = strdup(json_object_get_string(icon_theme));
317 }
318#endif
319
285 json_object_put(bar_config); 320 json_object_put(bar_config);
286 return true; 321 return true;
287} 322}
diff --git a/swaybar/meson.build b/swaybar/meson.build
index c27cf2c2..312ca97b 100644
--- a/swaybar/meson.build
+++ b/swaybar/meson.build
@@ -1,3 +1,32 @@
1tray_files = get_option('enable-tray') ? [
2 'tray/host.c',
3 'tray/icon.c',
4 'tray/item.c',
5 'tray/tray.c',
6 'tray/watcher.c'
7] : []
8
9swaybar_deps = [
10 cairo,
11 client_protos,
12 gdk_pixbuf,
13 jsonc,
14 math,
15 pango,
16 pangocairo,
17 rt,
18 wayland_client,
19 wayland_cursor,
20 wlroots,
21]
22if get_option('enable-tray')
23 if systemd.found()
24 swaybar_deps += systemd
25 elif elogind.found()
26 swaybar_deps += elogind
27 endif
28endif
29
1executable( 30executable(
2 'swaybar', [ 31 'swaybar', [
3 'bar.c', 32 'bar.c',
@@ -8,21 +37,10 @@ executable(
8 'main.c', 37 'main.c',
9 'render.c', 38 'render.c',
10 'status_line.c', 39 'status_line.c',
40 tray_files
11 ], 41 ],
12 include_directories: [sway_inc], 42 include_directories: [sway_inc],
13 dependencies: [ 43 dependencies: swaybar_deps,
14 cairo,
15 client_protos,
16 gdk_pixbuf,
17 jsonc,
18 math,
19 pango,
20 pangocairo,
21 rt,
22 wayland_client,
23 wayland_cursor,
24 wlroots,
25 ],
26 link_with: [lib_sway_common, lib_sway_client], 44 link_with: [lib_sway_common, lib_sway_client],
27 install_rpath : rpathdir, 45 install_rpath : rpathdir,
28 install: true 46 install: true
diff --git a/swaybar/render.c b/swaybar/render.c
index 96118c42..9fe4ee9c 100644
--- a/swaybar/render.c
+++ b/swaybar/render.c
@@ -14,6 +14,9 @@
14#include "swaybar/ipc.h" 14#include "swaybar/ipc.h"
15#include "swaybar/render.h" 15#include "swaybar/render.h"
16#include "swaybar/status_line.h" 16#include "swaybar/status_line.h"
17#if HAVE_TRAY
18#include "swaybar/tray/tray.h"
19#endif
17#include "wlr-layer-shell-unstable-v1-client-protocol.h" 20#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18 21
19static const int WS_HORIZONTAL_PADDING = 5; 22static const int WS_HORIZONTAL_PADDING = 5;
@@ -453,6 +456,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) {
453 * utilize the available space. 456 * utilize the available space.
454 */ 457 */
455 double x = output->width * output->scale; 458 double x = output->width * output->scale;
459#if HAVE_TRAY
460 if (bar->tray) {
461 uint32_t h = render_tray(cairo, output, &x);
462 max_height = h > max_height ? h : max_height;
463 }
464#endif
456 if (bar->status) { 465 if (bar->status) {
457 uint32_t h = render_status_line(cairo, output, &x); 466 uint32_t h = render_status_line(cairo, output, &x);
458 max_height = h > max_height ? h : max_height; 467 max_height = h > max_height ? h : max_height;
diff --git a/swaybar/status_line.c b/swaybar/status_line.c
index 744d2785..2e6ef173 100644
--- a/swaybar/status_line.c
+++ b/swaybar/status_line.c
@@ -185,7 +185,6 @@ void status_line_free(struct status_line *status) {
185 } 185 }
186 free(status->read); 186 free(status->read);
187 free(status->write); 187 free(status->write);
188 free((char*) status->text);
189 free(status->buffer); 188 free(status->buffer);
190 free(status); 189 free(status);
191} 190}
diff --git a/swaybar/tray/host.c b/swaybar/tray/host.c
new file mode 100644
index 00000000..30339fec
--- /dev/null
+++ b/swaybar/tray/host.c
@@ -0,0 +1,210 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include <unistd.h>
7#include "swaybar/bar.h"
8#include "swaybar/tray/host.h"
9#include "swaybar/tray/item.h"
10#include "swaybar/tray/tray.h"
11#include "list.h"
12#include "log.h"
13
14static const char *watcher_path = "/StatusNotifierWatcher";
15
16static int cmp_sni_id(const void *item, const void *cmp_to) {
17 const struct swaybar_sni *sni = item;
18 return strcmp(sni->watcher_id, cmp_to);
19}
20
21static void add_sni(struct swaybar_tray *tray, char *id) {
22 int idx = list_seq_find(tray->items, cmp_sni_id, id);
23 if (idx == -1) {
24 wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
25 struct swaybar_sni *sni = create_sni(id, tray);
26 if (sni) {
27 list_add(tray->items, sni);
28 }
29 }
30}
31
32static int handle_sni_registered(sd_bus_message *msg, void *data,
33 sd_bus_error *error) {
34 char *id;
35 int ret = sd_bus_message_read(msg, "s", &id);
36 if (ret < 0) {
37 wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
38 }
39
40 struct swaybar_tray *tray = data;
41 add_sni(tray, id);
42
43 return ret;
44}
45
46static int handle_sni_unregistered(sd_bus_message *msg, void *data,
47 sd_bus_error *error) {
48 char *id;
49 int ret = sd_bus_message_read(msg, "s", &id);
50 if (ret < 0) {
51 wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret));
52 }
53
54 struct swaybar_tray *tray = data;
55 int idx = list_seq_find(tray->items, cmp_sni_id, id);
56 if (idx != -1) {
57 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
58 destroy_sni(tray->items->items[idx]);
59 list_del(tray->items, idx);
60 set_bar_dirty(tray->bar);
61 }
62 return ret;
63}
64
65static int get_registered_snis_callback(sd_bus_message *msg, void *data,
66 sd_bus_error *error) {
67 if (sd_bus_message_is_method_error(msg, NULL)) {
68 sd_bus_error err = *sd_bus_message_get_error(msg);
69 wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message);
70 return -sd_bus_error_get_errno(&err);
71 }
72
73 int ret = sd_bus_message_enter_container(msg, 'v', NULL);
74 if (ret < 0) {
75 wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
76 return ret;
77 }
78
79 char **ids;
80 ret = sd_bus_message_read_strv(msg, &ids);
81 if (ret < 0) {
82 wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret));
83 return ret;
84 }
85
86 if (ids) {
87 struct swaybar_tray *tray = data;
88 for (char **id = ids; *id; ++id) {
89 add_sni(tray, *id);
90 }
91 }
92
93 return ret;
94}
95
96static bool register_to_watcher(struct swaybar_host *host) {
97 // this is called asynchronously in case the watcher is owned by this process
98 int ret = sd_bus_call_method_async(host->tray->bus, NULL,
99 host->watcher_interface, watcher_path, host->watcher_interface,
100 "RegisterStatusNotifierHost", NULL, NULL, "s", host->service);
101 if (ret < 0) {
102 wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret));
103 return false;
104 }
105
106 ret = sd_bus_call_method_async(host->tray->bus, NULL,
107 host->watcher_interface, watcher_path,
108 "org.freedesktop.DBus.Properties", "Get",
109 get_registered_snis_callback, host->tray, "ss",
110 host->watcher_interface, "RegisteredStatusNotifierItems");
111 if (ret < 0) {
112 wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret));
113 }
114
115 return ret >= 0;
116}
117
118static int handle_new_watcher(sd_bus_message *msg,
119 void *data, sd_bus_error *error) {
120 char *service, *old_owner, *new_owner;
121 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
122 if (ret < 0) {
123 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
124 return ret;
125 }
126
127 if (!*old_owner) {
128 struct swaybar_host *host = data;
129 if (strcmp(service, host->watcher_interface) == 0) {
130 register_to_watcher(host);
131 }
132 }
133
134 return 0;
135}
136
137bool init_host(struct swaybar_host *host, char *protocol,
138 struct swaybar_tray *tray) {
139 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
140 host->watcher_interface = malloc(len);
141 if (!host->watcher_interface) {
142 return false;
143 }
144 snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol);
145
146 sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL;
147 int ret = sd_bus_match_signal(tray->bus, &reg_slot, host->watcher_interface,
148 watcher_path, host->watcher_interface,
149 "StatusNotifierItemRegistered", handle_sni_registered, tray);
150 if (ret < 0) {
151 wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s",
152 strerror(-ret));
153 goto error;
154 }
155 ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface,
156 watcher_path, host->watcher_interface,
157 "StatusNotifierItemUnregistered", handle_sni_unregistered, tray);
158 if (ret < 0) {
159 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
160 strerror(-ret));
161 goto error;
162 }
163
164 ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus",
165 "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged",
166 handle_new_watcher, host);
167 if (ret < 0) {
168 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
169 strerror(-ret));
170 goto error;
171 }
172
173 pid_t pid = getpid();
174 size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d",
175 protocol, pid) + 1;
176 host->service = malloc(service_len);
177 if (!host->service) {
178 goto error;
179 }
180 snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid);
181 ret = sd_bus_request_name(tray->bus, host->service, 0);
182 if (ret < 0) {
183 wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret));
184 goto error;
185 }
186
187 host->tray = tray;
188 if (!register_to_watcher(host)) {
189 goto error;
190 }
191
192 sd_bus_slot_set_floating(reg_slot, 1);
193 sd_bus_slot_set_floating(unreg_slot, 1);
194 sd_bus_slot_set_floating(watcher_slot, 1);
195
196 wlr_log(WLR_DEBUG, "Registered %s", host->service);
197 return true;
198error:
199 sd_bus_slot_unref(reg_slot);
200 sd_bus_slot_unref(unreg_slot);
201 sd_bus_slot_unref(watcher_slot);
202 finish_host(host);
203 return false;
204}
205
206void finish_host(struct swaybar_host *host) {
207 sd_bus_release_name(host->tray->bus, host->service);
208 free(host->service);
209 free(host->watcher_interface);
210}
diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c
new file mode 100644
index 00000000..67805858
--- /dev/null
+++ b/swaybar/tray/icon.c
@@ -0,0 +1,462 @@
1#define _POSIX_C_SOURCE 200809L
2#include <ctype.h>
3#include <dirent.h>
4#include <stdbool.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <sys/stat.h>
9#include <unistd.h>
10#include <wordexp.h>
11#include "swaybar/tray/icon.h"
12#include "config.h"
13#include "list.h"
14#include "log.h"
15#include "stringop.h"
16
17static bool dir_exists(char *path) {
18 struct stat sb;
19 return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode);
20}
21
22static list_t *get_basedirs(void) {
23 list_t *basedirs = create_list();
24 list_add(basedirs, strdup("$HOME/.icons")); // deprecated
25
26 char *data_home = getenv("XDG_DATA_HOME");
27 list_add(basedirs, strdup(data_home && *data_home ?
28 "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons"));
29
30 list_add(basedirs, strdup("/usr/share/pixmaps"));
31
32 char *data_dirs = getenv("XDG_DATA_DIRS");
33 if (!(data_dirs && *data_dirs)) {
34 data_dirs = "/usr/local/share:/usr/share";
35 }
36 data_dirs = strdup(data_dirs);
37 char *dir = strtok(data_dirs, ":");
38 do {
39 size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1;
40 char *path = malloc(path_len);
41 snprintf(path, path_len, "%s/icons", dir);
42 list_add(basedirs, path);
43 } while ((dir = strtok(NULL, ":")));
44 free(data_dirs);
45
46 list_t *basedirs_expanded = create_list();
47 for (int i = 0; i < basedirs->length; ++i) {
48 wordexp_t p;
49 if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) {
50 if (dir_exists(p.we_wordv[0])) {
51 list_add(basedirs_expanded, strdup(p.we_wordv[0]));
52 }
53 wordfree(&p);
54 }
55 }
56
57 list_free_items_and_destroy(basedirs);
58
59 return basedirs_expanded;
60}
61
62static void destroy_theme(struct icon_theme *theme) {
63 if (!theme) {
64 return;
65 }
66 free(theme->name);
67 free(theme->comment);
68 free(theme->inherits);
69 list_free_items_and_destroy(theme->directories);
70 free(theme->dir);
71
72 for (int i = 0; i < theme->subdirs->length; ++i) {
73 struct icon_theme_subdir *subdir = theme->subdirs->items[i];
74 free(subdir->name);
75 free(subdir);
76 }
77 list_free(theme->subdirs);
78 free(theme);
79}
80
81static int cmp_group(const void *item, const void *cmp_to) {
82 return strcmp(item, cmp_to);
83}
84
85static bool group_handler(char *old_group, char *new_group,
86 struct icon_theme *theme) {
87 if (!old_group) { // first group must be "Icon Theme"
88 return strcmp(new_group, "Icon Theme");
89 }
90
91 if (strcmp(old_group, "Icon Theme") == 0) {
92 if (!(theme->name && theme->comment && theme->directories)) {
93 return true;
94 }
95 } else {
96 if (theme->subdirs->length == 0) { // skip
97 return false;
98 }
99
100 struct icon_theme_subdir *subdir =
101 theme->subdirs->items[theme->subdirs->length - 1];
102 if (!subdir->size) return true;
103
104 switch (subdir->type) {
105 case FIXED: subdir->max_size = subdir->min_size = subdir->size;
106 break;
107 case SCALABLE: {
108 if (!subdir->max_size) subdir->max_size = subdir->size;
109 if (!subdir->min_size) subdir->min_size = subdir->size;
110 break;
111 }
112 case THRESHOLD:
113 subdir->max_size = subdir->size + subdir->threshold;
114 subdir->min_size = subdir->size - subdir->threshold;
115 }
116 }
117
118 if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) {
119 struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir));
120 if (!subdir) {
121 return true;
122 }
123 subdir->name = strdup(new_group);
124 subdir->threshold = 2;
125 list_add(theme->subdirs, subdir);
126 }
127
128 return false;
129}
130
131static int entry_handler(char *group, char *key, char *value,
132 struct icon_theme *theme) {
133 if (strcmp(group, "Icon Theme") == 0) {
134 if (strcmp(key, "Name") == 0) {
135 theme->name = strdup(value);
136 } else if (strcmp(key, "Comment") == 0) {
137 theme->comment = strdup(value);
138 } else if (strcmp(key, "Inherists") == 0) {
139 theme->inherits = strdup(value);
140 } else if (strcmp(key, "Directories") == 0) {
141 theme->directories = split_string(value, ",");
142 } // Ignored: ScaledDirectories, Hidden, Example
143 } else {
144 if (theme->subdirs->length == 0) { // skip
145 return false;
146 }
147
148 struct icon_theme_subdir *subdir =
149 theme->subdirs->items[theme->subdirs->length - 1];
150 if (strcmp(subdir->name, group) != 0) { // skip
151 return false;
152 }
153
154 char *end;
155 int n = strtol(value, &end, 10);
156 if (strcmp(key, "Size") == 0) {
157 subdir->size = n;
158 return *end != '\0';
159 } else if (strcmp(key, "Type") == 0) {
160 if (strcmp(value, "Fixed") == 0) {
161 subdir->type = FIXED;
162 } else if (strcmp(value, "Scalable") == 0) {
163 subdir->type = SCALABLE;
164 } else if (strcmp(value, "Threshold") == 0) {
165 subdir->type = THRESHOLD;
166 } else {
167 return true;
168 }
169 } else if (strcmp(key, "MaxSize") == 0) {
170 subdir->max_size = n;
171 return *end != '\0';
172 } else if (strcmp(key, "MinSize") == 0) {
173 subdir->min_size = n;
174 return *end != '\0';
175 } else if (strcmp(key, "Threshold") == 0) {
176 subdir->threshold = n;
177 return *end != '\0';
178 } // Ignored: Scale, Applications
179 }
180 return false;
181}
182
183/*
184 * This is a Freedesktop Desktop Entry parser (essentially INI)
185 * It calls entry_handler for every entry
186 * and group_handler between every group (as well as at both ends)
187 * Handlers return whether an error occured, which stops parsing
188 */
189static struct icon_theme *read_theme_file(char *basedir, char *theme_name) {
190 // look for index.theme file
191 size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir,
192 theme_name) + 1;
193 char *path = malloc(path_len);
194 snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name);
195 FILE *theme_file = fopen(path, "r");
196 if (!theme_file) {
197 return NULL;
198 }
199
200 struct icon_theme *theme = calloc(1, sizeof(struct icon_theme));
201 if (!theme) {
202 return NULL;
203 }
204 theme->subdirs = create_list();
205
206 bool error = false;
207 char *group = NULL;
208 char *full_line = NULL;
209 size_t full_len = 0;
210 ssize_t nread;
211 while ((nread = getline(&full_line, &full_len, theme_file)) != -1) {
212 char *line = full_line - 1;
213 while (isspace(*++line)) {} // remove leading whitespace
214 if (!*line || line[0] == '#') continue; // ignore blank lines & comments
215
216 int len = nread - (line - full_line);
217 while (isspace(line[--len])) {}
218 line[++len] = '\0'; // remove trailing whitespace
219
220 if (line[0] == '[') { // group header
221 // check well-formed
222 if (line[--len] != ']') {
223 error = true;
224 break;
225 }
226 int i = 1;
227 for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {}
228 if (i < len) {
229 error = true;
230 break;
231 }
232
233 // call handler
234 line[len] = '\0';
235 error = group_handler(group, &line[1], theme);
236 if (error) {
237 break;
238 }
239 free(group);
240 group = strdup(&line[1]);
241 } else { // key-value pair
242 // check well-formed
243 int eok = 0;
244 for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale?
245 int i = eok - 1;
246 while (isspace(line[++i])) {}
247 if (line[i] != '=') {
248 error = true;
249 break;
250 }
251
252 line[eok] = '\0'; // split into key-value pair
253 char *value = &line[i];
254 while (isspace(*++value)) {}
255 // TODO unescape value
256 error = entry_handler(group, line, value, theme);
257 if (error) {
258 break;
259 }
260 }
261 }
262
263 if (!error && group) {
264 error = group_handler(group, NULL, theme);
265 }
266
267 free(group);
268 free(full_line);
269 fclose(theme_file);
270
271 if (!error) {
272 theme->dir = strdup(theme_name);
273 return theme;
274 } else {
275 destroy_theme(theme);
276 return NULL;
277 }
278}
279
280static list_t *load_themes_in_dir(char *basedir) {
281 DIR *dir;
282 if (!(dir = opendir(basedir))) {
283 return NULL;
284 }
285
286 list_t *themes = create_list();
287 struct dirent *entry;
288 while ((entry = readdir(dir))) {
289 if (entry->d_name[0] == '.') continue;
290
291 struct icon_theme *theme = read_theme_file(basedir, entry->d_name);
292 if (theme) {
293 list_add(themes, theme);
294 }
295 }
296 return themes;
297}
298
299void init_themes(list_t **themes, list_t **basedirs) {
300 *basedirs = get_basedirs();
301
302 *themes = create_list();
303 for (int i = 0; i < (*basedirs)->length; ++i) {
304 list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]);
305 list_cat(*themes, dir_themes);
306 list_free(dir_themes);
307 }
308
309 list_t *theme_names = create_list();
310 for (int i = 0; i < (*themes)->length; ++i) {
311 struct icon_theme *theme = (*themes)->items[i];
312 list_add(theme_names, theme->name);
313 }
314 wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", "));
315 list_free(theme_names);
316}
317
318void finish_themes(list_t *themes, list_t *basedirs) {
319 for (int i = 0; i < themes->length; ++i) {
320 destroy_theme(themes->items[i]);
321 }
322 list_free(themes);
323 list_free_items_and_destroy(basedirs);
324}
325
326static char *find_icon_in_subdir(char *name, char *basedir, char *theme,
327 char *subdir) {
328 static const char *extensions[] = {
329#if HAVE_GDK_PIXBUF
330 "svg",
331#endif
332 "png",
333#if HAVE_GDK_PIXBUF
334 "xpm"
335#endif
336 };
337
338 size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme,
339 subdir, name) + 1;
340 char *path = malloc(path_len);
341
342 for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) {
343 snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir,
344 name, extensions[i]);
345 if (access(path, R_OK) == 0) {
346 return path;
347 }
348 }
349
350 free(path);
351 return NULL;
352}
353
354static bool theme_exists_in_basedir(char *theme, char *basedir) {
355 size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1;
356 char *path = malloc(path_len);
357 snprintf(path, path_len, "%s/%s", basedir, theme);
358 bool ret = dir_exists(path);
359 free(path);
360 return ret;
361}
362
363static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name,
364 int size, char *theme_name, int *min_size, int *max_size) {
365 struct icon_theme *theme = NULL;
366 for (int i = 0; i < themes->length; ++i) {
367 theme = themes->items[i];
368 if (strcmp(theme->name, theme_name) == 0) {
369 break;
370 }
371 theme = NULL;
372 }
373 if (!theme) return NULL;
374
375 char *icon = NULL;
376 for (int i = 0; i < basedirs->length; ++i) {
377 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
378 continue;
379 }
380 // search backwards to hopefully hit scalable/larger icons first
381 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
382 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
383 if (size >= subdir->min_size && size <= subdir->max_size) {
384 if ((icon = find_icon_in_subdir(name, basedirs->items[i],
385 theme->dir, subdir->name))) {
386 *min_size = subdir->min_size;
387 *max_size = subdir->max_size;
388 return icon;
389 }
390 }
391 }
392 }
393
394 // inexact match
395 unsigned smallest_error = -1; // UINT_MAX
396 for (int i = 0; i < basedirs->length; ++i) {
397 if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) {
398 continue;
399 }
400 for (int j = theme->subdirs->length - 1; j >= 0; --j) {
401 struct icon_theme_subdir *subdir = theme->subdirs->items[j];
402 unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0)
403 + (size < subdir->min_size ? subdir->min_size - size : 0);
404 if (error < smallest_error) {
405 char *test_icon = find_icon_in_subdir(name, basedirs->items[i],
406 theme->dir, subdir->name);
407 if (test_icon) {
408 icon = test_icon;
409 smallest_error = error;
410 *min_size = subdir->min_size;
411 *max_size = subdir->max_size;
412 }
413 }
414 }
415 }
416
417 if (!icon && theme->inherits) {
418 icon = find_icon_with_theme(basedirs, themes, name, size,
419 theme->inherits, min_size, max_size);
420 }
421
422 return icon;
423}
424
425char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) {
426 char *icon = find_icon_in_subdir(name, dir, "", "");
427 if (icon) {
428 *min_size = 1;
429 *max_size = 512;
430 }
431 return icon;
432
433}
434
435static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size,
436 int *max_size) {
437 for (int i = 0; i < basedirs->length; ++i) {
438 char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size);
439 if (icon) {
440 return icon;
441 }
442 }
443 return NULL;
444}
445
446char *find_icon(list_t *themes, list_t *basedirs, char *name, int size,
447 char *theme, int *min_size, int *max_size) {
448 // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes
449 char *icon = NULL;
450 if (theme) {
451 icon = find_icon_with_theme(basedirs, themes, name, size, theme,
452 min_size, max_size);
453 }
454 if (!icon) {
455 icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor",
456 min_size, max_size);
457 }
458 if (!icon) {
459 icon = find_fallback_icon(basedirs, name, min_size, max_size);
460 }
461 return icon;
462}
diff --git a/swaybar/tray/item.c b/swaybar/tray/item.c
new file mode 100644
index 00000000..41cacd16
--- /dev/null
+++ b/swaybar/tray/item.c
@@ -0,0 +1,443 @@
1#define _POSIX_C_SOURCE 200809L
2#include <cairo.h>
3#include <stdbool.h>
4#include <stdlib.h>
5#include <string.h>
6#include "swaybar/bar.h"
7#include "swaybar/config.h"
8#include "swaybar/input.h"
9#include "swaybar/tray/host.h"
10#include "swaybar/tray/icon.h"
11#include "swaybar/tray/item.h"
12#include "swaybar/tray/tray.h"
13#include "background-image.h"
14#include "cairo.h"
15#include "list.h"
16#include "log.h"
17#include "wlr-layer-shell-unstable-v1-client-protocol.h"
18
19// TODO menu
20
21static bool sni_ready(struct swaybar_sni *sni) {
22 return sni->status && (sni->status[0] == 'N' ?
23 sni->attention_icon_name || sni->attention_icon_pixmap :
24 sni->icon_name || sni->icon_pixmap);
25}
26
27static void set_sni_dirty(struct swaybar_sni *sni) {
28 if (sni_ready(sni)) {
29 sni->min_size = sni->max_size = 0; // invalidate previous icon
30 set_bar_dirty(sni->tray->bar);
31 }
32}
33
34static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni,
35 const char *prop, list_t **dest) {
36 int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)");
37 if (ret < 0) {
38 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
39 return ret;
40 }
41
42 if (sd_bus_message_at_end(msg, 0)) {
43 return ret;
44 }
45
46 list_t *pixmaps = create_list();
47 if (!pixmaps) {
48 return -12; // -ENOMEM
49 }
50
51 while (!sd_bus_message_at_end(msg, 0)) {
52 ret = sd_bus_message_enter_container(msg, 'r', "iiay");
53 if (ret < 0) {
54 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
55 goto error;
56 }
57
58 int size;
59 ret = sd_bus_message_read(msg, "ii", NULL, &size);
60 if (ret < 0) {
61 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
62 goto error;
63 }
64
65 const void *pixels;
66 size_t npixels;
67 ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels);
68 if (ret < 0) {
69 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
70 goto error;
71 }
72
73 struct swaybar_pixmap *pixmap =
74 malloc(sizeof(struct swaybar_pixmap) + npixels);
75 pixmap->size = size;
76 memcpy(pixmap->pixels, pixels, npixels);
77 list_add(pixmaps, pixmap);
78
79 sd_bus_message_exit_container(msg);
80 }
81 *dest = pixmaps;
82
83 return ret;
84error:
85 list_free_items_and_destroy(pixmaps);
86 return ret;
87}
88
89struct get_property_data {
90 struct swaybar_sni *sni;
91 const char *prop;
92 const char *type;
93 void *dest;
94};
95
96static int get_property_callback(sd_bus_message *msg, void *data,
97 sd_bus_error *error) {
98 struct get_property_data *d = data;
99 struct swaybar_sni *sni = d->sni;
100 const char *prop = d->prop;
101 const char *type = d->type;
102 void *dest = d->dest;
103
104 int ret;
105 if (sd_bus_message_is_method_error(msg, NULL)) {
106 sd_bus_error err = *sd_bus_message_get_error(msg);
107 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message);
108 ret = -sd_bus_error_get_errno(&err);
109 goto cleanup;
110 }
111
112 ret = sd_bus_message_enter_container(msg, 'v', type);
113 if (ret < 0) {
114 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret));
115 goto cleanup;
116 }
117
118 if (!type) {
119 ret = read_pixmap(msg, sni, prop, dest);
120 if (ret < 0) {
121 goto cleanup;
122 }
123 } else {
124 ret = sd_bus_message_read(msg, type, dest);
125 if (ret < 0) {
126 wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop,
127 strerror(-ret));
128 goto cleanup;
129 } else if (*type == 's' || *type == 'o') {
130 char **str = dest;
131 *str = strdup(*str);
132 }
133 }
134
135 if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ?
136 prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) {
137 set_sni_dirty(sni);
138 }
139cleanup:
140 free(data);
141 return ret;
142}
143
144static void sni_get_property_async(struct swaybar_sni *sni, const char *prop,
145 const char *type, void *dest) {
146 struct get_property_data *data = malloc(sizeof(struct get_property_data));
147 data->sni = sni;
148 data->prop = prop;
149 data->type = type;
150 data->dest = dest;
151 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
152 sni->path, "org.freedesktop.DBus.Properties", "Get",
153 get_property_callback, data, "ss", sni->interface, prop);
154 if (ret < 0) {
155 wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret));
156 }
157}
158
159static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) {
160 struct swaybar_sni *sni = data;
161 wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id);
162
163 free(sni->icon_name);
164 sni->icon_name = NULL;
165 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
166
167 list_free_items_and_destroy(sni->icon_pixmap);
168 sni->icon_pixmap = NULL;
169 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
170
171 return 0;
172}
173
174static int handle_new_attention_icon(sd_bus_message *msg, void *data,
175 sd_bus_error *error) {
176 struct swaybar_sni *sni = data;
177 wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id);
178
179 free(sni->attention_icon_name);
180 sni->attention_icon_name = NULL;
181 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
182
183 list_free_items_and_destroy(sni->attention_icon_pixmap);
184 sni->attention_icon_pixmap = NULL;
185 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
186
187 return 0;
188}
189
190static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) {
191 char *status;
192 int ret = sd_bus_message_read(msg, "s", &status);
193 if (ret < 0) {
194 wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret));
195 } else {
196 struct swaybar_sni *sni = data;
197 free(sni->status);
198 sni->status = strdup(status);
199 wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status);
200 set_sni_dirty(sni);
201 }
202 return ret;
203}
204
205static void sni_match_signal(struct swaybar_sni *sni, char *signal,
206 sd_bus_message_handler_t callback) {
207 int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path,
208 sni->interface, signal, callback, sni);
209 if (ret < 0) {
210 wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal,
211 strerror(-ret));
212 }
213}
214
215struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) {
216 struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni));
217 if (!sni) {
218 return NULL;
219 }
220 sni->tray = tray;
221 sni->watcher_id = strdup(id);
222 char *path_ptr = strchr(id, '/');
223 if (!path_ptr) {
224 sni->service = strdup(id);
225 sni->path = strdup("/StatusNotifierItem");
226 sni->interface = "org.freedesktop.StatusNotifierItem";
227 } else {
228 sni->service = strndup(id, path_ptr - id);
229 sni->path = strdup(path_ptr);
230 sni->interface = "org.kde.StatusNotifierItem";
231 sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path);
232 }
233
234 // Ignored: Category, Id, Title, WindowId, OverlayIconName,
235 // OverlayIconPixmap, AttentionMovieName, ToolTip
236 sni_get_property_async(sni, "Status", "s", &sni->status);
237 sni_get_property_async(sni, "IconName", "s", &sni->icon_name);
238 sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap);
239 sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name);
240 sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap);
241 sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu);
242 sni_get_property_async(sni, "Menu", "o", &sni->menu);
243
244 sni_match_signal(sni, "NewIcon", handle_new_icon);
245 sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon);
246 sni_match_signal(sni, "NewStatus", handle_new_status);
247
248 return sni;
249}
250
251void destroy_sni(struct swaybar_sni *sni) {
252 if (!sni) {
253 return;
254 }
255
256 free(sni->watcher_id);
257 free(sni->service);
258 free(sni->path);
259 free(sni->status);
260 free(sni->icon_name);
261 free(sni->icon_pixmap);
262 free(sni->attention_icon_name);
263 free(sni->menu);
264 free(sni);
265}
266
267static void handle_click(struct swaybar_sni *sni, int x, int y,
268 enum x11_button button, int delta) {
269 const char *method = sni->tray->bar->config->tray_bindings[button];
270 if (!method) {
271 static const char *default_bindings[10] = {
272 "nop",
273 "Activate",
274 "SecondaryActivate",
275 "ContextMenu",
276 "ScrollUp",
277 "ScrollDown",
278 "ScrollLeft",
279 "ScrollRight",
280 "nop",
281 "nop"
282 };
283 method = default_bindings[button];
284 }
285 if (strcmp(method, "nop") == 0) {
286 return;
287 }
288 if (sni->item_is_menu && strcmp(method, "Activate") == 0) {
289 method = "ContextMenu";
290 }
291
292 if (strncmp(method, "Scroll", strlen("Scroll")) == 0) {
293 char dir = method[strlen("Scroll")];
294 char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal";
295 int sign = (dir == 'U' || dir == 'L') ? -1 : 1;
296
297 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
298 sni->path, sni->interface, "Scroll", NULL, NULL, "is",
299 delta*sign, orientation);
300 if (ret < 0) {
301 wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret));
302 }
303 } else {
304 int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service,
305 sni->path, sni->interface, method, NULL, NULL, "ii", x, y);
306 if (ret < 0) {
307 wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret));
308 }
309 }
310}
311
312static int cmp_sni_id(const void *item, const void *cmp_to) {
313 const struct swaybar_sni *sni = item;
314 return strcmp(sni->watcher_id, cmp_to);
315}
316
317static enum hotspot_event_handling icon_hotspot_callback(
318 struct swaybar_output *output, struct swaybar_hotspot *hotspot,
319 int x, int y, enum x11_button button, void *data) {
320 wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data);
321
322 struct swaybar_tray *tray = output->bar->tray;
323 int idx = list_seq_find(tray->items, cmp_sni_id, data);
324
325 if (idx != -1) {
326 struct swaybar_sni *sni = tray->items->items[idx];
327 // guess global position since wayland doesn't expose it
328 struct swaybar_config *config = tray->bar->config;
329 int global_x = output->output_x + config->gaps.left + x;
330 bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
331 int global_y = output->output_y + (top_bar ? config->gaps.top + y:
332 (int) output->output_height - config->gaps.bottom - y);
333
334 wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y);
335 handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event
336 return HOTSPOT_IGNORE;
337 } else {
338 wlr_log(WLR_DEBUG, "but it doesn't exist");
339 }
340
341 return HOTSPOT_PROCESS;
342}
343
344uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x,
345 struct swaybar_sni *sni) {
346 uint32_t height = output->height * output->scale;
347 int padding = output->bar->config->tray_padding;
348 int ideal_size = height - 2*padding;
349 if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) {
350 bool icon_found = false;
351 char *icon_name = sni->status[0] == 'N' ?
352 sni->attention_icon_name : sni->icon_name;
353 if (icon_name) {
354 char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs,
355 icon_name, ideal_size, output->bar->config->icon_theme,
356 &sni->min_size, &sni->max_size);
357 if (!icon_path && sni->icon_theme_path) {
358 icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path,
359 &sni->min_size, &sni->max_size);
360 }
361 if (icon_path) {
362 cairo_surface_destroy(sni->icon);
363 sni->icon = load_background_image(icon_path);
364 free(icon_path);
365 icon_found = true;
366 }
367 }
368 if (!icon_found) {
369 list_t *pixmaps = sni->status[0] == 'N' ?
370 sni->attention_icon_pixmap : sni->icon_pixmap;
371 if (pixmaps) {
372 int idx = -1;
373 unsigned smallest_error = -1; // UINT_MAX
374 for (int i = 0; i < pixmaps->length; ++i) {
375 struct swaybar_pixmap *pixmap = pixmaps->items[i];
376 unsigned error = (ideal_size - pixmap->size) *
377 (ideal_size < pixmap->size ? -1 : 1);
378 if (error < smallest_error) {
379 smallest_error = error;
380 idx = i;
381 }
382 }
383 struct swaybar_pixmap *pixmap = pixmaps->items[idx];
384 cairo_surface_destroy(sni->icon);
385 sni->icon = cairo_image_surface_create_for_data(pixmap->pixels,
386 CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size,
387 cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size));
388 }
389 }
390 }
391
392 int icon_size;
393 cairo_surface_t *icon;
394 if (sni->icon) {
395 int actual_size = cairo_image_surface_get_height(sni->icon);
396 icon_size = actual_size < ideal_size ?
397 actual_size*(ideal_size/actual_size) : ideal_size;
398 icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size);
399 } else { // draw a sad face
400 icon_size = ideal_size*0.8;
401 icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size);
402 cairo_t *cairo_icon = cairo_create(icon);
403 cairo_set_source_u32(cairo_icon, 0xFF0000FF);
404 cairo_translate(cairo_icon, icon_size/2, icon_size/2);
405 cairo_scale(cairo_icon, icon_size/2, icon_size/2);
406 cairo_arc(cairo_icon, 0, 0, 1, 0, 7);
407 cairo_fill(cairo_icon);
408 cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR);
409 cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7);
410 cairo_fill(cairo_icon);
411 cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7);
412 cairo_fill(cairo_icon);
413 cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469);
414 cairo_set_line_width(cairo_icon, 0.1);
415 cairo_stroke(cairo_icon);
416 cairo_destroy(cairo_icon);
417 }
418
419 int padded_size = icon_size + 2*padding;
420 *x -= padded_size;
421 int y = floor((height - padded_size) / 2.0);
422
423 cairo_operator_t op = cairo_get_operator(cairo);
424 cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
425 cairo_set_source_surface(cairo, icon, *x + padding, y + padding);
426 cairo_rectangle(cairo, *x, y, padded_size, padded_size);
427 cairo_fill(cairo);
428 cairo_set_operator(cairo, op);
429
430 cairo_surface_destroy(icon);
431
432 struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot));
433 hotspot->x = *x;
434 hotspot->y = 0;
435 hotspot->width = height;
436 hotspot->height = height;
437 hotspot->callback = icon_hotspot_callback;
438 hotspot->destroy = free;
439 hotspot->data = strdup(sni->watcher_id);
440 wl_list_insert(&output->hotspots, &hotspot->link);
441
442 return output->height;
443}
diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c
new file mode 100644
index 00000000..acc300af
--- /dev/null
+++ b/swaybar/tray/tray.c
@@ -0,0 +1,127 @@
1#include <cairo.h>
2#include <stdint.h>
3#include <stdlib.h>
4#include <string.h>
5#include "swaybar/config.h"
6#include "swaybar/bar.h"
7#include "swaybar/tray/icon.h"
8#include "swaybar/tray/host.h"
9#include "swaybar/tray/item.h"
10#include "swaybar/tray/tray.h"
11#include "swaybar/tray/watcher.h"
12#include "list.h"
13#include "log.h"
14
15static int handle_lost_watcher(sd_bus_message *msg,
16 void *data, sd_bus_error *error) {
17 char *service, *old_owner, *new_owner;
18 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
19 if (ret < 0) {
20 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
21 return ret;
22 }
23
24 if (!*new_owner) {
25 struct swaybar_tray *tray = data;
26 if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) {
27 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
28 } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) {
29 tray->watcher_kde = create_watcher("kde", tray->bus);
30 }
31 }
32
33 return 0;
34}
35
36struct swaybar_tray *create_tray(struct swaybar *bar) {
37 wlr_log(WLR_DEBUG, "Initializing tray");
38
39 sd_bus *bus;
40 int ret = sd_bus_open_user(&bus);
41 if (ret < 0) {
42 wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret));
43 return NULL;
44 }
45
46 struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray));
47 if (!tray) {
48 return NULL;
49 }
50 tray->bar = bar;
51 tray->bus = bus;
52 tray->fd = sd_bus_get_fd(tray->bus);
53
54 tray->watcher_xdg = create_watcher("freedesktop", tray->bus);
55 tray->watcher_kde = create_watcher("kde", tray->bus);
56
57 ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus",
58 "/org/freedesktop/DBus", "org.freedesktop.DBus",
59 "NameOwnerChanged", handle_lost_watcher, tray);
60 if (ret < 0) {
61 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
62 strerror(-ret));
63 }
64
65 tray->items = create_list();
66
67 init_host(&tray->host_xdg, "freedesktop", tray);
68 init_host(&tray->host_kde, "kde", tray);
69
70 init_themes(&tray->themes, &tray->basedirs);
71
72 return tray;
73}
74
75void destroy_tray(struct swaybar_tray *tray) {
76 if (!tray) {
77 return;
78 }
79 finish_host(&tray->host_xdg);
80 finish_host(&tray->host_kde);
81 for (int i = 0; i < tray->items->length; ++i) {
82 destroy_sni(tray->items->items[0]);
83 }
84 list_free(tray->items);
85 destroy_watcher(tray->watcher_xdg);
86 destroy_watcher(tray->watcher_kde);
87 sd_bus_flush_close_unref(tray->bus);
88 finish_themes(tray->themes, tray->basedirs);
89 free(tray);
90}
91
92void tray_in(int fd, short mask, void *data) {
93 sd_bus *bus = data;
94 int ret;
95 while ((ret = sd_bus_process(bus, NULL)) > 0) {
96 // This space intentionally left blank
97 }
98 if (ret < 0) {
99 wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret));
100 }
101}
102
103static int cmp_output(const void *item, const void *cmp_to) {
104 return strcmp(item, cmp_to);
105}
106
107uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) {
108 struct swaybar_config *config = output->bar->config;
109 if (config->tray_outputs) {
110 if (list_seq_find(config->tray_outputs, cmp_output, output->name) == -1) {
111 return 0;
112 }
113 } // else display on all
114
115 if ((int) output->height*output->scale <= 2*config->tray_padding) {
116 return 2*config->tray_padding + 1;
117 }
118
119 uint32_t max_height = 0;
120 struct swaybar_tray *tray = output->bar->tray;
121 for (int i = 0; i < tray->items->length; ++i) {
122 uint32_t h = render_sni(cairo, output, x, tray->items->items[i]);
123 max_height = h > max_height ? h : max_height;
124 }
125
126 return max_height;
127}
diff --git a/swaybar/tray/watcher.c b/swaybar/tray/watcher.c
new file mode 100644
index 00000000..198c6c85
--- /dev/null
+++ b/swaybar/tray/watcher.c
@@ -0,0 +1,212 @@
1#define _POSIX_C_SOURCE 200809L
2#include <stdbool.h>
3#include <stddef.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include "list.h"
8#include "log.h"
9#include "swaybar/tray/watcher.h"
10
11static const char *obj_path = "/StatusNotifierWatcher";
12
13static bool using_standard_protocol(struct swaybar_watcher *watcher) {
14 return watcher->interface[strlen("org.")] == 'f'; // freedesktop
15}
16
17static int cmp_id(const void *item, const void *cmp_to) {
18 return strcmp(item, cmp_to);
19}
20
21static int cmp_service(const void *item, const void *cmp_to) {
22 return strncmp(item, cmp_to, strlen(cmp_to));
23}
24
25static int handle_lost_service(sd_bus_message *msg,
26 void *data, sd_bus_error *error) {
27 char *service, *old_owner, *new_owner;
28 int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner);
29 if (ret < 0) {
30 wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret));
31 return ret;
32 }
33
34 if (!*new_owner) {
35 struct swaybar_watcher *watcher = data;
36 int idx = list_seq_find(watcher->items,
37 using_standard_protocol(watcher) ? cmp_id : cmp_service, service);
38 if (idx != -1) {
39 char *id = watcher->items->items[idx];
40 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id);
41 list_del(watcher->items, idx);
42 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
43 "StatusNotifierItemUnregistered", "s", id);
44 free(id);
45 }
46
47 idx = list_seq_find(watcher->hosts, cmp_id, service);
48 if (idx != -1) {
49 wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service);
50 free(watcher->hosts->items[idx]);
51 list_del(watcher->hosts, idx);
52 }
53 }
54
55 return 0;
56}
57
58static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) {
59 char *service_or_path, *id;
60 int ret = sd_bus_message_read(msg, "s", &service_or_path);
61 if (ret < 0) {
62 wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret));
63 return ret;
64 }
65
66 struct swaybar_watcher *watcher = data;
67 if (using_standard_protocol(watcher)) {
68 id = strdup(service_or_path);
69 } else {
70 const char *service, *path;
71 if (service_or_path[0] == '/') {
72 service = sd_bus_message_get_sender(msg);
73 path = service_or_path;
74 } else {
75 service = service_or_path;
76 path = "/StatusNotifierItem";
77 }
78 size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1;
79 id = malloc(id_len);
80 snprintf(id, id_len, "%s%s", service, path);
81 }
82
83 if (list_seq_find(watcher->items, cmp_id, id) == -1) {
84 wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id);
85 list_add(watcher->items, id);
86 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
87 "StatusNotifierItemRegistered", "s", id);
88 } else {
89 wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id);
90 free(id);
91 }
92
93 return sd_bus_reply_method_return(msg, "");
94}
95
96static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) {
97 char *service;
98 int ret = sd_bus_message_read(msg, "s", &service);
99 if (ret < 0) {
100 wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret));
101 return ret;
102 }
103
104 struct swaybar_watcher *watcher = data;
105 if (list_seq_find(watcher->hosts, cmp_id, service) == -1) {
106 wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service);
107 list_add(watcher->hosts, strdup(service));
108 sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface,
109 "StatusNotifierHostRegistered", "s", service);
110 } else {
111 wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service);
112 }
113
114 return sd_bus_reply_method_return(msg, "");
115}
116
117static int get_registered_snis(sd_bus *bus, const char *obj_path,
118 const char *interface, const char *property, sd_bus_message *reply,
119 void *data, sd_bus_error *error) {
120 struct swaybar_watcher *watcher = data;
121 list_add(watcher->items, NULL); // strv expects NULL-terminated string array
122 int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items);
123 list_del(watcher->items, watcher->items->length - 1);
124 return ret;
125}
126
127static int is_host_registered(sd_bus *bus, const char *obj_path,
128 const char *interface, const char *property, sd_bus_message *reply,
129 void *data, sd_bus_error *error) {
130 struct swaybar_watcher *watcher = data;
131 int val = watcher->hosts->length > 0; // dbus expects int rather than bool
132 return sd_bus_message_append_basic(reply, 'b', &val);
133}
134
135static const sd_bus_vtable watcher_vtable[] = {
136 SD_BUS_VTABLE_START(0),
137 SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni,
138 SD_BUS_VTABLE_UNPRIVILEGED),
139 SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host,
140 SD_BUS_VTABLE_UNPRIVILEGED),
141 SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis,
142 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
143 SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered,
144 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
145 SD_BUS_PROPERTY("ProtocolVersion", "i", NULL,
146 offsetof(struct swaybar_watcher, version),
147 SD_BUS_VTABLE_PROPERTY_CONST),
148 SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0),
149 SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0),
150 SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0),
151 SD_BUS_VTABLE_END
152};
153
154struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) {
155 struct swaybar_watcher *watcher =
156 calloc(1, sizeof(struct swaybar_watcher));
157 if (!watcher) {
158 return NULL;
159 }
160
161 size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1;
162 watcher->interface = malloc(len);
163 snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol);
164
165 sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL;
166 int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path,
167 watcher->interface, watcher_vtable, watcher);
168 if (ret < 0) {
169 wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret));
170 goto error;
171 }
172
173 ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus",
174 "/org/freedesktop/DBus", "org.freedesktop.DBus",
175 "NameOwnerChanged", handle_lost_service, watcher);
176 if (ret < 0) {
177 wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s",
178 strerror(-ret));
179 goto error;
180 }
181
182 ret = sd_bus_request_name(bus, watcher->interface, 0);
183 if (ret < 0) {
184 wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret));
185 goto error;
186 }
187
188 sd_bus_slot_set_floating(signal_slot, 0);
189 sd_bus_slot_set_floating(vtable_slot, 0);
190
191 watcher->bus = bus;
192 watcher->hosts = create_list();
193 watcher->items = create_list();
194 watcher->version = 0;
195 wlr_log(WLR_DEBUG, "Registered %s", watcher->interface);
196 return watcher;
197error:
198 sd_bus_slot_unref(signal_slot);
199 sd_bus_slot_unref(vtable_slot);
200 destroy_watcher(watcher);
201 return NULL;
202}
203
204void destroy_watcher(struct swaybar_watcher *watcher) {
205 if (!watcher) {
206 return;
207 }
208 list_free_items_and_destroy(watcher->hosts);
209 list_free_items_and_destroy(watcher->items);
210 free(watcher->interface);
211 free(watcher);
212}
diff --git a/swayidle/main.c b/swayidle/main.c
index 9a76e58c..41eecc41 100644
--- a/swayidle/main.c
+++ b/swayidle/main.c
@@ -1,6 +1,7 @@
1#define _POSIX_C_SOURCE 200809L 1#define _POSIX_C_SOURCE 200809L
2#include <assert.h> 2#include <assert.h>
3#include <errno.h> 3#include <errno.h>
4#include <fcntl.h>
4#include <getopt.h> 5#include <getopt.h>
5#include <pthread.h> 6#include <pthread.h>
6#include <signal.h> 7#include <signal.h>
@@ -104,9 +105,21 @@ static void acquire_sleep_lock(void) {
104 if (ret < 0) { 105 if (ret < 0) {
105 wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s", 106 wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s",
106 strerror(-ret)); 107 strerror(-ret));
108 sd_bus_error_free(&error);
109 sd_bus_message_unref(msg);
110 return;
107 } else { 111 } else {
108 wlr_log(WLR_INFO, "Got sleep lock: %d", lock_fd); 112 wlr_log(WLR_INFO, "Got sleep lock: %d", lock_fd);
109 } 113 }
114
115 // sd_bus_message_unref closes the file descriptor so we need
116 // to copy it beforehand
117 lock_fd = fcntl(lock_fd, F_DUPFD_CLOEXEC, 3);
118 if (lock_fd < 0) {
119 wlr_log(WLR_ERROR, "Failed to copy sleep lock fd: %s",
120 strerror(errno));
121 }
122
110 sd_bus_error_free(&error); 123 sd_bus_error_free(&error);
111 sd_bus_message_unref(msg); 124 sd_bus_message_unref(msg);
112} 125}
diff --git a/swaylock/password.c b/swaylock/password.c
index 3059203a..3bd113ad 100644
--- a/swaylock/password.c
+++ b/swaylock/password.c
@@ -146,14 +146,6 @@ void swaylock_handle_key(struct swaylock_state *state,
146 schedule_indicator_clear(state); 146 schedule_indicator_clear(state);
147 break; 147 break;
148 case XKB_KEY_Caps_Lock: 148 case XKB_KEY_Caps_Lock:
149 /* The state is getting active after this
150 * so we need to manually toggle it */
151 state->xkb.caps_lock = !state->xkb.caps_lock;
152 state->auth_state = AUTH_STATE_INPUT_NOP;
153 damage_state(state);
154 schedule_indicator_clear(state);
155 schedule_password_clear(state);
156 break;
157 case XKB_KEY_Shift_L: 149 case XKB_KEY_Shift_L:
158 case XKB_KEY_Shift_R: 150 case XKB_KEY_Shift_R:
159 case XKB_KEY_Control_L: 151 case XKB_KEY_Control_L:
diff --git a/swaylock/seat.c b/swaylock/seat.c
index 7b72114f..f0b1385e 100644
--- a/swaylock/seat.c
+++ b/swaylock/seat.c
@@ -63,8 +63,12 @@ static void keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
63 struct swaylock_state *state = data; 63 struct swaylock_state *state = data;
64 xkb_state_update_mask(state->xkb.state, 64 xkb_state_update_mask(state->xkb.state,
65 mods_depressed, mods_latched, mods_locked, 0, 0, group); 65 mods_depressed, mods_latched, mods_locked, 0, 0, group);
66 state->xkb.caps_lock = xkb_state_mod_name_is_active(state->xkb.state, 66 int caps_lock = xkb_state_mod_name_is_active(state->xkb.state,
67 XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); 67 XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED);
68 if (caps_lock != state->xkb.caps_lock) {
69 state->xkb.caps_lock = caps_lock;
70 damage_state(state);
71 }
68 state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state, 72 state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state,
69 XKB_MOD_NAME_CTRL, 73 XKB_MOD_NAME_CTRL,
70 XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); 74 XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED);