diff options
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 @@ # sway -"**S**irCmpwn's **Way**land compositor" е в процес на разработка, съвместим с i3, [Wayland](http://wayland.freedesktop.org/) композитор. -Прочетете [FAQ](https://github.com/swaywm/sway/wiki). Присъединете се в -[IRC канала](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на +Sway е в процес на разработка, съвместим с i3, +[Wayland](http://wayland.freedesktop.org/) композитор. Прочетете +[FAQ](https://github.com/swaywm/sway/wiki). Присъединете се в [IRC +канала](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на irc.freenode.net). [](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 @@ Der Fortschritt dieser Übersetzung kann [hier](https://github.com/swaywm/sway/issues/1318) eingesehen werden. -"**S**irCmpwn's **Way**land compositor" ist ein i3-kompatibler -[Wayland](http://wayland.freedesktop.org/)-Kompositor. Lies die -[FAQ](https://github.com/swaywm/sway/wiki#faq). Tritt dem -[IRC-Channel](http://webchat.freenode.net/?channels=sway&uio=d4) bei (#sway in irc.freenode.net). +Sway ist ein i3-kompatibler +[Wayland](http://wayland.freedesktop.org/)-Kompositor. Lies die +[FAQ](https://github.com/swaywm/sway/wiki#faq). Tritt dem +[IRC-Channel](http://webchat.freenode.net/?channels=sway&uio=d4) bei (#sway in +irc.freenode.net). [](https://sr.ht/ICd5.png) 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 @@ # sway -"Ο Sway (**S**irCmpwn's **Way**land) είναι ένας **υπό ανάπτυξη** [Wayland](http://wayland.freedesktop.org/) διαχειριστής παραθύρων συμβατός με τον αντίστοιχο διαχειριστή παραθύρων i3 για τον X11. +Sway είναι ένας **υπό ανάπτυξη** [Wayland](http://wayland.freedesktop.org/) διαχειριστής παραθύρων συμβατός με τον αντίστοιχο διαχειριστή παραθύρων i3 για τον X11. Διαβάστε τις [Συνήθεις Ερωτήσεις](https://github.com/swaywm/sway/wiki). Συνδεθείτε στο [κανάλι μας στο IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway στο irc.freenode.net). 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 @@ # sway -"**S**irCmpwn's **Way**land compositor" est un compositeur [Wayland](http://wayland.freedesktop.org/) -compatible avec i3, **en cours de développement**. -Lisez la [FAQ](https://github.com/swaywm/sway/wiki). Rejoignez le -[canal IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway sur +Sway est un compositeur [Wayland](http://wayland.freedesktop.org/) compatible +avec i3, **en cours de développement**. Lisez la +[FAQ](https://github.com/swaywm/sway/wiki). Rejoignez le [canal +IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway sur irc.freenode.net). [](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 @@ # sway -"**S**irCmpwn's **Way**land compositor" è un compositor -[Wayland](http://wayland.freedesktop.org/) **in via di sviluppo** -compatibile con i3. -Leggi le [FAQ (in Inglese)](https://github.com/swaywm/sway/wiki). Unisciti al -[canale IRC (in Inglese)](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on +Sway è un compositor [Wayland](http://wayland.freedesktop.org/) **in via di +sviluppo** compatibile con i3. Leggi le [FAQ (in +Inglese)](https://github.com/swaywm/sway/wiki). Unisciti al [canale IRC (in +Inglese)](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net). [](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 @@ # sway -"**S**irCmpwn's **Way**land compositor"は**開発中**の -i3互換な[Wayland](http://wayland.freedesktop.org/)コンポジタです。 +Swayは**開発中**のi3互換な[Wayland](http://wayland.freedesktop.org/)コンポジタです。 [FAQ](https://github.com/swaywm/sway/wiki)も合わせてご覧ください。 [IRC チャンネル](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net)もあります。 @@ -3,10 +3,10 @@ [**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--) - [Русский](https://github.com/swaywm/sway/blob/master/README.ru.md#sway--) - [Български](https://github.com/swaywm/sway/blob/master/README.bg.md#sway--) -"**S**irCmpwn's **Way**land compositor" is a **work in progress** -i3-compatible [Wayland](http://wayland.freedesktop.org/) compositor. -Read the [FAQ](https://github.com/swaywm/sway/wiki). Join the -[IRC channel](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on +sway is a **work in progress** i3-compatible +[Wayland](http://wayland.freedesktop.org/) compositor. Read the +[FAQ](https://github.com/swaywm/sway/wiki). Join the [IRC +channel](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway on irc.freenode.net). If 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 @@ # sway -"**S**irCmpwn's **Way**land compositor" é um compositor [Wayland](http://wayland.freedesktop.org/) -compatível com o i3. Leia o [FAQ](https://github.com/swaywm/sway/wiki). Participe do -[canal IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway no +Sway é um compositor [Wayland](http://wayland.freedesktop.org/) compatível com o +i3. Leia o [FAQ](https://github.com/swaywm/sway/wiki). Participe do [canal +IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway no irc.freenode.net). [](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 @@ # sway -"**S**irCmpwn's **Way**land compositor" на данный момент **(в разработке)** -i3-совместимый [Wayland](http://wayland.freedesktop.org/) композитор. -Прочитайте [FAQ](https://github.com/swaywm/sway/wiki). Присоединяйтесь к -[IRC каналу](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на +Sway на данный момент **(в разработке)** i3-совместимый +[Wayland](http://wayland.freedesktop.org/) композитор. Прочитайте +[FAQ](https://github.com/swaywm/sway/wiki). Присоединяйтесь к [IRC +каналу](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на irc.freenode.net). [](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 @@ # sway -**Sway** ("**S**irCmpwn's **Way**land compositor") це сумісний з i3 композитор -[Wayland](http://wayland.freedesktop.org/) (**у стані розробки**). -Ознайомтесь з [ЧаПами](https://github.com/swaywm/sway/wiki). -Приєднуйтесь до [спільноти в IRC](http://webchat.freenode.net/?channels=sway&uio=d4) -(#sway на irc.freenode.net). +**Sway** це сумісний з i3 композитор [Wayland](http://wayland.freedesktop.org/) +(**у стані розробки**). Ознайомтесь з +[ЧаПами](https://github.com/swaywm/sway/wiki). Приєднуйтесь до [спільноти в +IRC](http://webchat.freenode.net/?channels=sway&uio=d4) (#sway на +irc.freenode.net). [](https://sr.ht/ICd5.png) 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 @@ +# swaybar(1) completion + +_swaybar() +{ + local cur prev + _get_comp_words_by_ref cur prev + + short=( + -h + -v + -s + -b + -d + ) + + long=( + --help + --version + --socket + --bar_id + --debug + ) + + case $prev in + -s|--socket) + _filedir + return + ;; + -b|--bar_id) + bars=($(swaymsg -t get_bar_config | jq -r '.[]')) + COMPREPLY=($(compgen -W "${bars[*]}" -- "$cur")) + return + ;; + esac + + if [[ $cur == --* ]]; then + COMPREPLY=($(compgen -W "${long[*]}" -- "$cur")) + else + COMPREPLY=($(compgen -W "${short[*]}" -- "$cur")) + COMPREPLY+=($(compgen -W "${long[*]}" -- "$cur")) + fi + +} && +complete -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 @@ # # ------------------------------- _arguments -s \ - '(-v --version)'{-v,--version}'[shows version]' \ - '(-h --help)'{-h,--help}'[shows help message]' \ + '(-v --version)'{-v,--version}'[Show the version number and quit]' \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ '(-c --config)'{-c,--config}'[Specify a config file]:files:_files' \ '(-C --validate)'{-C,--validate}'[Check validity of the config file, then exit]' \ '(-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 @@ +#compdef swaybar +# +# Completion script for swaybar +# + +local bars=($(swaymsg -t get_bar_config | jq -r '.[]')) + +_arguments -s \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-v --version)'{-v,--version}'[Show version and quit]' \ + '(-s --socket)'{-s,--socket}'[Connect to sway via socket]:filename:_files' \ + '(-b --bar_id)'{-b,--bar-id}'[Bar ID for which to get the configuration]:filename:($bars)'\ + '(-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 @@ +#compdef swayidle +# +# Completion script for swayidle +# + +local events=('timeout:Execute timeout command if there is no activity for timeout seconds' + 'before-sleep:Execute before-sleep command before sleep') +local resume=('resume:Execute command when there is activity again') + +if (($#words <= 2)); then + _arguments -C \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-d)'-d'[Enable debug output]' + _describe -t "events" 'swayidle' events + +elif [[ "$words[-3]" == before-sleep || "$words[-3]" == resume ]]; then + _describe -t "events" 'swayidle' events + +elif [[ "$words[-4]" == timeout ]]; then + _describe -t "events" 'swayidle' events + _describe -t "resume" 'swayidle' resume +fi 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 @@ # _arguments -s \ - '(-v --version)'{-v,--version}'[Show the version number and quit]' \ - '(-h --help)'{-h,--help}'[Show help message and quit]' \ - '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal]' \ - '(-c --color)'{-c,--color}'[Specify a color (rrggbb)]' \ - '(-i --image)'{-i,--image}'[Display an image]:files:_files' \ - '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \ + '(-C --config)'{-C,--config}'[Path to the config file]:filename:_files' \ + '(-c --color)'{-c,--color}'[Turn the screen into the given color instead of white]:color:' \ + '(-e --ignore-empty-password)'{-e,--ignore-empty-password}'[When an empty password is provided, do not validate it]' \ + '(-f --daemonize)'{-f,--daemonize}'[Detach from the controlling terminal after locking]' \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-i --image)'{-i,--image}'[Display an image]:filename:_files' \ + '(-s --scaling)'{-s,--scaling}'[Scaling mode]:mode:(stretch fill fit center tile)' \ + '(-t --tiling)'{-t,--tiling}'[Same as --scaling=tile]' \ '(-u --no-unlock-indicator)'{-u,--no-unlock-indicator}'[Disable the unlock indicator]' \ - '(--socket)'--socket'[Use the specified socket path.]:files:_files' \ + '(-v --version)'{-v,--version}'[Show the version number and quit]' \ + '(--bs-hl-color)'--bs-hl-color'[Sets the color of backspace highlights segments]:color:' \ + '(--font)'--font'[Sets the font of the text]:font:' \ + '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \ + '(--indicator-thickness)'--indicator-thickness'[Sets the indicator thickness]:thickness:' \ + '(--inside-color)'--inside-color'[Sets the color of the inside of the indicator]:color:' \ + '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when cleared]:color:' \ + '(--inside-clear-color)'--inside-clear-color'[Sets the color of the inside of the indicator when verifying]:color:' \ + '(--inside-wrong-color)'--inside-wrong-color'[Sets the color of the inside of the indicator when invalid]:color:' \ + '(--key-hl-color)'--key-hl-color'[Sets the color of the key press highlight segments]:color:' \ + '(--line-color)'--line-color'[Sets the color of the line between the inside and ring]:color:' \ + '(--line-clear-color)'--line-clear-color'[Sets the color of the line between the inside and ring when cleared]:color:' \ + '(--line-ver-color)'--line-ver-color'[Sets the color of the line between the inside and ring when verifying]:color:' \ + '(--line-wrong-color)'--line-wrong-color'[Sets the color of the line between the inside and ring when invalid]:color:' \ + '(-n --line-uses-inside)'{-n,--line-uses-inside}'[Use the inside color for the line between the inside and ring]' \ + '(-r --line-uses-ring)'{-r,--line--uses-ring}'[Use the ring color for the line between the inside and ring]' \ + '(--ring-color)'--ring-color'[Sets the color of the ring of the indicator]:color:' \ + '(--ring-clear-color)'--ring-clear-color'[Sets the color of the ring of the indicator when cleared]:color:' \ + '(--ring-ver-color)'--ring-ver-color'[Sets the color of the ring of the indicator when verifying]:color:' \ + '(--ring-wrong-color)'--ring-wrong-color'[Sets the color of the ring of the indicator when invalid]:color:' \ + '(--separator-color)'--separator-color'[Sets the color of the lines that separate highlight segments]:color:' \ + '(--text-color)'--text-color'[Sets the color of the text]:color:' \ + '(--text-clear-color)'--text-clear-color'[Sets the color of the text when cleared]:color:' \ + '(--text-ver-color)'--text-ver-color'[Sets the color of the text when verifying]:color:' \ + '(--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=( ) _arguments -s \ - '(-v --version)'{-v,--version}'[Print the version (of swaymsg) and quit]' \ - '(-h --help)'{-h,--help}'[Shows help message]' \ - '(-q --quiet)'{-q,--quiet}'[Sends the IPC message but does not print the response from sway]' \ - '(-s --socket)'{-s,--socket}'[Use the specified socket path.]:files:_files' \ - '(-t --type)'{-t,--type}'[Specify the type of IPC message.]:type:{_describe "type" types}' + '(-v --version)'{-v,--version}'[Show the version number and quit]' \ + '(-m --monitor)'{-m,--monitor}'[Monitor until killed (-t SUBSCRIBE only)]' \ + '(-h --help)'{-h,--help}'[Show help message and quit]' \ + '(-q --quiet)'{-q,--quiet}'[Be quiet]' \ + '(-r --raw)'{-r,--raw}'[Use raw output even if using a tty]' \ + '(-s --socket)'{-s,--socket}'[Use the specified socket path]:files:_files' \ + '(-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; sway_cmd cmd_force_focus_wrapping; sway_cmd cmd_fullscreen; sway_cmd cmd_gaps; -sway_cmd cmd_hide_cursor; sway_cmd cmd_hide_edge_borders; sway_cmd cmd_include; sway_cmd cmd_input; @@ -173,6 +172,7 @@ sway_cmd cmd_swaybg_command; sway_cmd cmd_swaynag_command; sway_cmd cmd_swap; sway_cmd cmd_tiling_drag; +sway_cmd cmd_tiling_drag_threshold; sway_cmd cmd_title_align; sway_cmd cmd_title_format; sway_cmd cmd_titlebar_border_thickness; @@ -183,11 +183,9 @@ sway_cmd cmd_workspace; sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_workspace_layout; -sway_cmd bar_cmd_activate_button; sway_cmd bar_cmd_binding_mode_indicator; sway_cmd bar_cmd_bindsym; sway_cmd bar_cmd_colors; -sway_cmd bar_cmd_context_button; sway_cmd bar_cmd_font; sway_cmd bar_cmd_gaps; sway_cmd bar_cmd_mode; @@ -198,13 +196,13 @@ sway_cmd bar_cmd_hidden_state; sway_cmd bar_cmd_icon_theme; sway_cmd bar_cmd_id; sway_cmd bar_cmd_position; -sway_cmd bar_cmd_secondary_button; sway_cmd bar_cmd_separator_symbol; sway_cmd bar_cmd_status_command; sway_cmd bar_cmd_pango_markup; sway_cmd bar_cmd_strip_workspace_numbers; sway_cmd bar_cmd_strip_workspace_name; sway_cmd bar_cmd_swaybar_command; +sway_cmd bar_cmd_tray_bindsym; sway_cmd bar_cmd_tray_output; sway_cmd bar_cmd_tray_padding; sway_cmd bar_cmd_wrap_scroll; @@ -260,8 +258,9 @@ sway_cmd output_cmd_scale; sway_cmd output_cmd_transform; sway_cmd seat_cmd_attach; -sway_cmd seat_cmd_fallback; sway_cmd seat_cmd_cursor; +sway_cmd seat_cmd_fallback; +sway_cmd seat_cmd_hide_cursor; sway_cmd cmd_ipc_cmd; sway_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 @@ #include <time.h> #include <wlr/types/wlr_box.h> #include <xkbcommon/xkbcommon.h> +#include "../include/config.h" #include "list.h" #include "swaynag.h" #include "tree/container.h" @@ -140,6 +141,7 @@ struct seat_config { char *name; int fallback; // -1 means not set list_t *attachments; // list of seat_attachment configs + int hide_cursor_timeout; }; enum config_dpms { @@ -252,6 +254,13 @@ struct bar_config { char *binding_mode_bg; char *binding_mode_text; } colors; + +#if HAVE_TRAY + char *icon_theme; + const char *tray_bindings[10]; // mouse buttons 0-9 + list_t *tray_outputs; // char * + int tray_padding; +#endif }; struct bar_binding { @@ -417,7 +426,9 @@ struct sway_config { bool auto_back_and_forth; bool show_marks; enum alignment title_align; + bool tiling_drag; + int tiling_drag_threshold; bool smart_gaps; int gaps_inner; @@ -436,8 +447,6 @@ struct sway_config { enum edge_border_types hide_edge_borders; enum edge_border_types saved_edge_borders; - int hide_cursor_timeout; - // border colors struct { 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); */ void cursor_rebase(struct sway_cursor *cursor); +void cursor_handle_activity(struct sway_cursor *cursor); +void cursor_unhide(struct sway_cursor *cursor); +int cursor_get_timeout(struct sway_cursor *cursor); + /** * Like cursor_rebase, but also allows focus to change when the cursor enters a * 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); struct sway_seat *input_manager_get_seat(const char *seat_name); /** + * If none of the seat configs have a fallback setting (either true or false), + * create the default seat (if needed) and set it as the fallback + */ +void input_manager_verify_fallback_seat(void); + +/** * Gets the last seat the user interacted with */ struct 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 { OP_NONE, OP_DOWN, OP_MOVE_FLOATING, + OP_MOVE_TILING_THRESHOLD, OP_MOVE_TILING, OP_RESIZE_FLOATING, OP_RESIZE_TILING, @@ -174,6 +175,8 @@ void seat_apply_config(struct sway_seat *seat, struct seat_config *seat_config); struct seat_config *seat_get_config(struct sway_seat *seat); +struct seat_config *seat_get_config_by_name(const char *name); + bool seat_is_input_allowed(struct sway_seat *seat, struct wlr_surface *surface); void 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, void seat_begin_move_floating(struct sway_seat *seat, struct sway_container *con, uint32_t button); +void seat_begin_move_tiling_threshold(struct sway_seat *seat, + struct sway_container *con, uint32_t button); + void seat_begin_move_tiling(struct sway_seat *seat, struct sway_container *con, uint32_t button); 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 @@ #ifndef _SWAYBAR_BAR_H #define _SWAYBAR_BAR_H #include <wayland-client.h> +#include "config.h" #include "input.h" #include "pool-buffer.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -8,6 +9,9 @@ struct swaybar_config; struct swaybar_output; +#if HAVE_TRAY +struct swaybar_tray; +#endif struct swaybar_workspace; struct loop; @@ -38,6 +42,10 @@ struct swaybar { int ipc_socketfd; struct wl_list outputs; // swaybar_output::link + +#if HAVE_TRAY + struct swaybar_tray *tray; +#endif }; struct swaybar_output { @@ -62,6 +70,8 @@ struct swaybar_output { struct pool_buffer *current_buffer; bool dirty; bool frame_scheduled; + + uint32_t output_height, output_width, output_x, output_y; }; struct swaybar_workspace { @@ -78,6 +88,8 @@ bool bar_setup(struct swaybar *bar, const char *socket_path); void bar_run(struct swaybar *bar); void bar_teardown(struct swaybar *bar); +void set_bar_dirty(struct swaybar *bar); + /* * Determines whether the bar should be visible and changes it to be so. * 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 @@ #include <stdbool.h> #include <stdint.h> #include <wayland-client.h> +#include "../include/config.h" #include "list.h" #include "util.h" @@ -64,6 +65,14 @@ struct swaybar_config { struct box_colors urgent_workspace; struct box_colors binding_mode; } colors; + +#if HAVE_TRAY + char *icon_theme; + char *tray_bindings[10]; // mouse buttons 0-9 + bool tray_hidden; + list_t *tray_outputs; // char * + int tray_padding; +#endif }; struct 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 @@ +#ifndef _SWAYBAR_TRAY_HOST_H +#define _SWAYBAR_TRAY_HOST_H + +#include <stdbool.h> + +struct swaybar_tray; + +struct swaybar_host { + struct swaybar_tray *tray; + char *service; + char *watcher_interface; +}; + +bool init_host(struct swaybar_host *host, char *protocol, struct swaybar_tray *tray); +void finish_host(struct swaybar_host *host); + +#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 @@ -#ifndef _SWAYBAR_ICON_H -#define _SWAYBAR_ICON_H +#ifndef _SWAYBAR_TRAY_ICON_H +#define _SWAYBAR_TRAY_ICON_H -#include <stdint.h> -#include <stdbool.h> -#include <client/cairo.h> +#include "list.h" -/** - * Returns the image found by `name` that is closest to `size` - */ -cairo_surface_t *find_icon(const char *name, int size); +enum subdir_type { + THRESHOLD, + SCALABLE, + FIXED +}; + +struct icon_theme_subdir { + char *name; + int size; + enum subdir_type type; + int max_size; + int min_size; + int threshold; +}; + +struct icon_theme { + char *name; + char *comment; + char *inherits; + list_t *directories; // char * -/* Struct used internally only */ -struct subdir; + char *dir; + list_t *subdirs; // struct icon_theme_subdir * +}; + +void init_themes(list_t **themes, list_t **basedirs); +void finish_themes(list_t *themes, list_t *basedirs); + +/* + * Finds an icon of a specified size given a list of themes and base directories. + * If the icon is found, the pointers min_size & max_size are set to minimum & + * maximum size that the icon can be scaled to, respectively. + * Returns: path of icon (which should be freed), or NULL if the icon is not found. + */ +char *find_icon(list_t *themes, list_t *basedirs, char *name, int size, + char *theme, int *min_size, int *max_size); +char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size); -#endif /* _SWAYBAR_ICON_H */ +#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 @@ +#ifndef _SWAYBAR_TRAY_ITEM_H +#define _SWAYBAR_TRAY_ITEM_H + +#include <cairo.h> +#include <stdbool.h> +#include <stdint.h> +#include "swaybar/tray/tray.h" +#include "list.h" + +struct swaybar_output; + +struct swaybar_pixmap { + int size; + unsigned char pixels[]; +}; + +struct swaybar_sni { + // icon properties + struct swaybar_tray *tray; + cairo_surface_t *icon; + int min_size; + int max_size; + + // dbus properties + char *watcher_id; + char *service; + char *path; + char *interface; + + char *status; + char *icon_name; + list_t *icon_pixmap; // struct swaybar_pixmap * + char *attention_icon_name; + list_t *attention_icon_pixmap; // struct swaybar_pixmap * + bool item_is_menu; + char *menu; + char *icon_theme_path; // non-standard KDE property +}; + +struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray); +void destroy_sni(struct swaybar_sni *sni); +uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x, + struct swaybar_sni *sni); + +#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 @@ -#ifndef _SWAYBAR_SNI_H -#define _SWAYBAR_SNI_H - -#include <stdbool.h> -#include <client/cairo.h> - -struct StatusNotifierItem { - /* Name registered to sni watcher */ - char *name; - /* Unique bus name, needed for determining signal origins */ - char *unique_name; - bool kde_special_snowflake; - - cairo_surface_t *image; - bool dirty; -}; - -/* Each output holds an sni_icon_ref of each item to render */ -struct sni_icon_ref { - cairo_surface_t *icon; - struct StatusNotifierItem *ref; -}; - -struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, - int height); - -void sni_icon_ref_free(struct sni_icon_ref *sni_ref); - -/** - * Will return a new item and get its icon. (see warning below) - * May return `NULL` if `name` is not valid. - */ -struct StatusNotifierItem *sni_create(const char *name); - -/** - * `item` must be a struct StatusNotifierItem * - * `str` must be a NUL terminated char * - * - * Returns 0 if `item` has a name of `str` - */ -int sni_str_cmp(const void *item, const void *str); - -/** - * Returns 0 if `item` has a unique name of `str` or if - * `item->unique_name == NULL` - */ -int sni_uniq_cmp(const void *item, const void *str); - -/** - * Gets an icon for the given item if found. - * - * XXX - * This function keeps a reference to the item until it gets responses, make - * sure that the reference and item are valid during this time. - */ -void get_icon(struct StatusNotifierItem *item); - -/** - * Calls the "activate" method on the given StatusNotifierItem - * - * x and y should be where the item was clicked - */ -void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y); - -/** - * Asks the item to draw a context menu at the given x and y coords - */ -void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y); - -/** - * Calls the "secondary activate" method on the given StatusNotifierItem - * - * x and y should be where the item was clicked - */ -void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y); - -/** - * Deconstructs `item` - */ -void sni_free(struct StatusNotifierItem *item); - -#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 @@ +#ifndef _SWAYBAR_TRAY_TRAY_H +#define _SWAYBAR_TRAY_TRAY_H + +#include "config.h" +#ifdef HAVE_SYSTEMD +#include <systemd/sd-bus.h> +#elif HAVE_ELOGIND +#include <elogind/sd-bus.h> +#endif +#include <cairo.h> +#include <stdint.h> +#include "swaybar/tray/host.h" +#include "list.h" + +struct swaybar; +struct swaybar_output; +struct swaybar_watcher; + +struct swaybar_tray { + struct swaybar *bar; + + int fd; + sd_bus *bus; + + struct swaybar_host host_xdg; + struct swaybar_host host_kde; + list_t *items; // struct swaybar_sni * + struct swaybar_watcher *watcher_xdg; + struct swaybar_watcher *watcher_kde; + + list_t *basedirs; // char * + list_t *themes; // struct swaybar_theme * +}; + +struct swaybar_tray *create_tray(struct swaybar *bar); +void destroy_tray(struct swaybar_tray *tray); +void tray_in(int fd, short mask, void *data); +uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x); + +#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 @@ +#ifndef _SWAYBAR_TRAY_WATCHER_H +#define _SWAYBAR_TRAY_WATCHER_H + +#include "swaybar/tray/tray.h" +#include "list.h" + +struct swaybar_watcher { + char *interface; + sd_bus *bus; + list_t *hosts; + list_t *items; + int version; +}; + +struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus); +void destroy_watcher(struct swaybar_watcher *watcher); + +#endif diff --git a/meson.build b/meson.build index e1e0fc2d..b1353b59 100644 --- a/meson.build +++ b/meson.build @@ -66,6 +66,7 @@ endif conf_data.set10('HAVE_GDK_PIXBUF', gdk_pixbuf.found()) conf_data.set10('HAVE_SYSTEMD', systemd.found()) conf_data.set10('HAVE_ELOGIND', elogind.found()) +conf_data.set10('HAVE_TRAY', get_option('enable-tray') and (systemd.found() or elogind.found())) if not systemd.found() and not elogind.found() warning('The sway binary must be setuid when compiled without (e)logind') @@ -212,6 +213,7 @@ endif if (get_option('bash-completions')) bash_files = files( 'completions/bash/sway', + 'completions/bash/swaybar', 'completions/bash/swayidle', 'completions/bash/swaylock', '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 option('bash-completions', type: 'boolean', value: true, description: 'Install bash shell completions.') option('fish-completions', type: 'boolean', value: true, description: 'Install fish shell completions.') option('enable-xwayland', type: 'boolean', value: true, description: 'Enable support for X11 applications') +option('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[] = { { "force_focus_wrapping", cmd_force_focus_wrapping }, { "fullscreen", cmd_fullscreen }, { "gaps", cmd_gaps }, - { "hide_cursor", cmd_hide_cursor }, { "hide_edge_borders", cmd_hide_edge_borders }, { "include", cmd_include }, { "input", cmd_input }, @@ -88,6 +87,7 @@ static struct cmd_handler handlers[] = { { "smart_borders", cmd_smart_borders }, { "smart_gaps", cmd_smart_gaps }, { "tiling_drag", cmd_tiling_drag }, + { "tiling_drag_threshold", cmd_tiling_drag_threshold }, { "title_align", cmd_title_align }, { "titlebar_border_thickness", cmd_titlebar_border_thickness }, { "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 @@ // Must be in alphabetical order for bsearch static struct cmd_handler bar_handlers[] = { - { "activate_button", bar_cmd_activate_button }, { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, { "bindsym", bar_cmd_bindsym }, { "colors", bar_cmd_colors }, - { "context_button", bar_cmd_context_button }, { "font", bar_cmd_font }, { "gaps", bar_cmd_gaps }, { "height", bar_cmd_height }, @@ -23,11 +21,11 @@ static struct cmd_handler bar_handlers[] = { { "output", bar_cmd_output }, { "pango_markup", bar_cmd_pango_markup }, { "position", bar_cmd_position }, - { "secondary_button", bar_cmd_secondary_button }, { "separator_symbol", bar_cmd_separator_symbol }, { "status_command", bar_cmd_status_command }, { "strip_workspace_name", bar_cmd_strip_workspace_name }, { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, + { "tray_bindsym", bar_cmd_tray_bindsym }, { "tray_output", bar_cmd_tray_output }, { "tray_padding", bar_cmd_tray_padding }, { "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 @@ -#include <stdlib.h> -#include "sway/commands.h" -#include "log.h" - -struct cmd_results *bar_cmd_activate_button(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "activate_button", "TODO TRAY"); -} 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 @@ -#include <stdlib.h> -#include "sway/commands.h" -#include "log.h" - -struct cmd_results *bar_cmd_context_button(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "context_button", "TODO TRAY"); -} 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 @@ +#define _POSIX_C_SOURCE 200809L #include <string.h> +#include "config.h" #include "sway/commands.h" +#include "sway/config.h" +#include "log.h" struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "icon_theme", "TODO TRAY"); +#if HAVE_TRAY + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "icon_theme", EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined."); + } + + wlr_log(WLR_DEBUG, "[Bar %s] Setting icon theme to %s", + config->current_bar->id, argv[0]); + free(config->current_bar->icon_theme); + config->current_bar->icon_theme = strdup(argv[0]); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#else + return cmd_results_new(CMD_INVALID, "icon_theme", + "Sway has been compiled without tray support"); +#endif } 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 @@ -#include <stdlib.h> -#include "sway/commands.h" -#include "log.h" - -struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "secondary_button", "TODO TRAY"); -} 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 @@ +#include <strings.h> +#include "config.h" +#include "sway/commands.h" +#include "sway/config.h" +#include "log.h" + +struct cmd_results *bar_cmd_tray_bindsym(int argc, char **argv) { +#if HAVE_TRAY + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "tray_bindsym", EXPECTED_EQUAL_TO, 2))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, "tray_bindsym", "No bar defined."); + } + + int button = 0; + if (strncasecmp(argv[0], "button", strlen("button")) == 0 && + strlen(argv[0]) == strlen("button0")) { + button = argv[0][strlen("button")] - '0'; + } + if (button < 1 || button > 9) { + return cmd_results_new(CMD_FAILURE, "tray_bindsym", + "[Bar %s] Only buttons 1 to 9 are supported", + config->current_bar->id); + } + + static const char *commands[] = { + "ContextMenu", + "Activate", + "SecondaryActivate", + "ScrollDown", + "ScrollLeft", + "ScrollRight", + "ScrollUp", + "nop" + }; + + for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i) { + if (strcasecmp(argv[1], commands[i]) == 0) { + wlr_log(WLR_DEBUG, "[Bar %s] Binding button %d to %s", + config->current_bar->id, button, commands[i]); + config->current_bar->tray_bindings[button] = commands[i]; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } + } + + return cmd_results_new(CMD_INVALID, "tray_bindsym", + "[Bar %s] Invalid command %s", config->current_bar->id, argv[1]); +#else + return cmd_results_new(CMD_INVALID, "tray_bindsym", + "Sway has been compiled without tray support"); +#endif +} 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 @@ +#define _POSIX_C_SOURCE 200809L #include <string.h> +#include "config.h" #include "sway/commands.h" +#include "sway/config.h" +#include "list.h" +#include "log.h" struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "tray_output", "TODO TRAY"); +#if HAVE_TRAY + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "tray_output", EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, "tray_output", "No bar defined."); + } + + list_t *outputs = config->current_bar->tray_outputs; + if (!outputs) { + config->current_bar->tray_outputs = outputs = create_list(); + } + + if (strcmp(argv[0], "none") == 0) { + wlr_log(WLR_DEBUG, "Hiding tray on bar: %s", config->current_bar->id); + for (int i = 0; i < outputs->length; ++i) { + free(outputs->items[i]); + } + outputs->length = 0; + } else { + wlr_log(WLR_DEBUG, "Showing tray on output '%s' for bar: %s", argv[0], + config->current_bar->id); + } + list_add(outputs, strdup(argv[0])); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#else + return cmd_results_new(CMD_INVALID, "tray_output", + "Sway has been compiled without tray support"); +#endif } 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 @@ #include <stdlib.h> #include <strings.h> +#include "config.h" #include "sway/commands.h" +#include "sway/config.h" #include "log.h" struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { - // TODO TRAY - return cmd_results_new(CMD_INVALID, "tray_padding", "TODO TRAY"); +#if HAVE_TRAY + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) { + return error; + } + if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_MOST, 2))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined."); + } + struct bar_config *bar = config->current_bar; + + char *end; + int padding = strtol(argv[0], &end, 10); + if (padding < 0 || (*end != '\0' && strcasecmp(end, "px") != 0)) { + return cmd_results_new(CMD_INVALID, "tray_padding", + "[Bar %s] Invalid tray padding value: %s", bar->id, argv[0]); + } + + if (argc == 2 && strcasecmp(argv[1], "px") != 0) { + return cmd_results_new(CMD_INVALID, "tray_padding", + "Expected 'tray_padding <px> [px]'"); + } + + wlr_log(WLR_DEBUG, "[Bar %s] Setting tray padding to %d", bar->id, padding); + config->current_bar->tray_padding = padding; + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#else + return cmd_results_new(CMD_INVALID, "tray_padding", + "Sway has been compiled without tray support"); +#endif } 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[] = { { "attach", seat_cmd_attach }, { "cursor", seat_cmd_cursor }, { "fallback", seat_cmd_fallback }, + { "hide_cursor", seat_cmd_hide_cursor }, }; struct 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 @@ #include <string.h> #include "sway/commands.h" #include "sway/config.h" -#include "sway/input/cursor.h" #include "sway/input/seat.h" #include "stringop.h" -struct cmd_results *cmd_hide_cursor(int argc, char **argv) { +struct cmd_results *seat_cmd_hide_cursor(int argc, char **argv) { struct cmd_results *error = NULL; if ((error = checkarg(argc, "hide_cursor", EXPECTED_EQUAL_TO, 1))) { return error; } + if (!config->handler_context.seat_config) { + return cmd_results_new(CMD_FAILURE, "hide_cursor", "No seat defined"); + } char *end; int timeout = strtol(argv[0], &end, 10); @@ -21,13 +23,7 @@ struct cmd_results *cmd_hide_cursor(int argc, char **argv) { if (timeout < 100 && timeout != 0) { timeout = 100; } - config->hide_cursor_timeout = timeout; - - struct sway_seat *seat; - wl_list_for_each(seat, &server.input->seats, link) { - wl_event_source_timer_update(seat->cursor->hide_source, - config->hide_cursor_timeout); - } + config->handler_context.seat_config->hide_cursor_timeout = timeout; return cmd_results_new(CMD_SUCCESS, NULL, NULL); } 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 @@ +#include <string.h> +#include "sway/commands.h" +#include "sway/config.h" +#include "log.h" + +struct cmd_results *cmd_tiling_drag_threshold(int argc, char **argv) { + struct cmd_results *error = NULL; + if ((error = checkarg(argc, "tiling_drag_threshold", EXPECTED_EQUAL_TO, 1))) { + return error; + } + + char *inv; + int value = strtol(argv[0], &inv, 10); + if (*inv != '\0' || value < 0) { + return cmd_results_new(CMD_INVALID, "tiling_drag_threshold", + "Invalid threshold specified"); + } + + config->tiling_drag_threshold = value; + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +} 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) { config->show_marks = true; config->title_align = ALIGN_LEFT; config->tiling_drag = true; + config->tiling_drag_threshold = 9; config->smart_gaps = false; config->gaps_inner = 0; @@ -257,8 +258,6 @@ static void config_defaults(struct sway_config *config) { config->hide_edge_borders = E_NONE; config->saved_edge_borders = E_NONE; - config->hide_cursor_timeout = 0; - // border colors set_color(config->border_colors.focused.border, 0x4C7899); set_color(config->border_colors.focused.background, 0x285577); @@ -465,6 +464,7 @@ bool load_main_config(const char *file, bool is_active, bool validating) { if (config->swaynag_config_errors.pid > 0) { swaynag_show(&config->swaynag_config_errors); } + input_manager_verify_fallback_seat(); } 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 @@ #include <signal.h> #include "sway/config.h" #include "sway/output.h" +#include "config.h" #include "stringop.h" #include "list.h" #include "log.h" @@ -77,6 +78,10 @@ void free_bar_config(struct bar_config *bar) { free(bar->colors.binding_mode_border); free(bar->colors.binding_mode_bg); free(bar->colors.binding_mode_text); +#if HAVE_TRAY + list_free_items_and_destroy(bar->tray_outputs); + free(bar->icon_theme); +#endif free(bar); } @@ -165,6 +170,10 @@ struct bar_config *default_bar_config(void) { bar->colors.binding_mode_bg = NULL; bar->colors.binding_mode_text = NULL; +#if HAVE_TRAY + bar->tray_padding = 2; +#endif + list_add(config->bars, bar); return bar; cleanup: 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) { free(seat); return NULL; } + seat->hide_cursor_timeout = -1; return seat; } @@ -137,6 +138,10 @@ void merge_seat_config(struct seat_config *dest, struct seat_config *source) { } } } + + if (source->hide_cursor_timeout != -1) { + dest->hide_cursor_timeout = source->hide_cursor_timeout; + } } struct 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, if (fullscreen_con->view) { if (fullscreen_con->view->saved_buffer) { render_saved_view(fullscreen_con->view, output, damage, 1.0f); - } else { + } else if (fullscreen_con->view->surface) { render_view_toplevels(fullscreen_con->view, output, damage, 1.0f); } 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) { static int handle_timeout(void *data) { struct sway_transaction *transaction = data; - wlr_log(WLR_DEBUG, "Transaction %p timed out (%li waiting)", + wlr_log(WLR_DEBUG, "Transaction %p timed out (%zi waiting)", transaction, transaction->num_waiting); transaction->num_waiting = 0; transaction_progress_queue(); @@ -472,7 +472,7 @@ static void set_instruction_ready( struct timespec *start = &transaction->commit_time; float ms = (now.tv_sec - start->tv_sec) * 1000 + (now.tv_nsec - start->tv_nsec) / 1000000.0; - wlr_log(WLR_DEBUG, "Transaction %p: %li/%li ready in %.1fms (%s)", + wlr_log(WLR_DEBUG, "Transaction %p: %zi/%zi ready in %.1fms (%s)", transaction, transaction->num_configures - transaction->num_waiting + 1, 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, desktop_damage_box(&seat->op_drop_box); } +static void handle_move_tiling_threshold_motion(struct sway_seat *seat, + struct sway_cursor *cursor) { + double cx = seat->cursor->cursor->x; + double cy = seat->cursor->cursor->y; + double sx = seat->op_ref_lx; + double sy = seat->op_ref_ly; + + // Get the scaled threshold for the output. Even if the operation goes + // across multiple outputs of varying scales, just use the scale for the + // output that the cursor is currently on for simplicity. + struct wlr_output *wlr_output = wlr_output_layout_output_at( + root->output_layout, cx, cy); + double output_scale = wlr_output ? wlr_output->scale : 1; + double threshold = config->tiling_drag_threshold * output_scale; + threshold *= threshold; + + // If the threshold has been exceeded, start the actual drag + if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) { + seat->operation = OP_MOVE_TILING; + cursor_set_image(cursor, "grab", NULL); + handle_move_tiling_motion(seat, cursor); + } +} + static void calculate_floating_constraints(struct sway_container *con, int *min_width, int *max_width, int *min_height, int *max_height) { if (config->floating_minimum_width == -1) { // no minimum @@ -597,21 +621,40 @@ static int hide_notify(void *data) { return 1; } -static void handle_activity(struct sway_cursor *cursor) { - wl_event_source_timer_update(cursor->hide_source, - config->hide_cursor_timeout); +int cursor_get_timeout(struct sway_cursor *cursor){ + struct seat_config *sc = seat_get_config(cursor->seat); + if (!sc) { + sc = seat_get_config_by_name("*"); + } + int timeout = sc ? sc->hide_cursor_timeout : 0; + if (timeout < 0) { + timeout = 0; + } + return timeout; +} + +void cursor_handle_activity(struct sway_cursor *cursor) { + wl_event_source_timer_update( + cursor->hide_source, cursor_get_timeout(cursor)); + wlr_idle_notify_activity(server.idle, cursor->seat->wlr_seat); if (cursor->hidden) { - cursor->hidden = false; - if (cursor->image_surface) { - cursor_set_image_surface(cursor, cursor->image_surface, - cursor->hotspot_x, cursor->hotspot_y, - cursor->image_client); - } else { - const char *image = cursor->image; - cursor->image = NULL; - cursor_set_image(cursor, image, cursor->image_client); - } + cursor_unhide(cursor); + } +} + +void cursor_unhide(struct sway_cursor *cursor) { + cursor->hidden = false; + if (cursor->image_surface) { + cursor_set_image_surface(cursor, + cursor->image_surface, + cursor->hotspot_x, + cursor->hotspot_y, + cursor->image_client); + } else { + const char *image = cursor->image; + cursor->image = NULL; + cursor_set_image(cursor, image, cursor->image_client); } } @@ -632,6 +675,9 @@ void cursor_send_pointer_motion(struct sway_cursor *cursor, case OP_MOVE_FLOATING: handle_move_floating_motion(seat, cursor); break; + case OP_MOVE_TILING_THRESHOLD: + handle_move_tiling_threshold_motion(seat, cursor); + break; case OP_MOVE_TILING: handle_move_tiling_motion(seat, cursor); break; @@ -709,7 +755,7 @@ static void handle_cursor_motion(struct wl_listener *listener, void *data) { wlr_cursor_move(cursor->cursor, event->device, event->delta_x, event->delta_y); cursor_send_pointer_motion(cursor, event->time_msec); - handle_activity(cursor); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -720,7 +766,7 @@ static void handle_cursor_motion_absolute( struct wlr_event_pointer_motion_absolute *event = data; wlr_cursor_warp_absolute(cursor->cursor, event->device, event->x, event->y); cursor_send_pointer_motion(cursor, event->time_msec); - handle_activity(cursor); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -976,12 +1022,21 @@ void dispatch_cursor_button(struct sway_cursor *cursor, if (config->tiling_drag && (mod_pressed || on_titlebar) && state == WLR_BUTTON_PRESSED && !is_floating_or_child && cont && !cont->is_fullscreen) { - if (on_titlebar) { + struct sway_container *focus = seat_get_focused_container(seat); + bool focused = focus == cont || container_has_ancestor(focus, cont); + if (on_titlebar && !focused) { node = seat_get_focus_inactive(seat, &cont->node); seat_set_focus(seat, node); } + seat_pointer_notify_button(seat, time_msec, button, state); - seat_begin_move_tiling(seat, cont, button); + + // If moving a container by it's title bar, use a threshold for the drag + if (!mod_pressed && config->tiling_drag_threshold > 0) { + seat_begin_move_tiling_threshold(seat, cont, button); + } else { + seat_begin_move_tiling(seat, cont, button); + } return; } @@ -1009,7 +1064,7 @@ static void handle_cursor_button(struct wl_listener *listener, void *data) { struct wlr_event_pointer_button *event = data; dispatch_cursor_button(cursor, event->device, event->time_msec, event->button, event->state); - handle_activity(cursor); + cursor_handle_activity(cursor); transaction_commit_dirty(); } @@ -1119,7 +1174,7 @@ static void handle_cursor_axis(struct wl_listener *listener, void *data) { struct sway_cursor *cursor = wl_container_of(listener, cursor, axis); struct wlr_event_pointer_axis *event = data; dispatch_cursor_axis(cursor, event); - handle_activity(cursor); + cursor_handle_activity(cursor); transaction_commit_dirty(); } 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) { return false; } +void input_manager_verify_fallback_seat(void) { + struct sway_seat *seat = NULL; + if (!input_has_seat_fallback_configuration()) { + wlr_log(WLR_DEBUG, "no fallback seat config - creating default"); + seat = input_manager_get_default_seat(); + struct seat_config *sc = new_seat_config(seat->wlr_seat->name); + sc->fallback = true; + sc = store_seat_config(sc); + input_manager_apply_seat_config(sc); + } +} + static void input_manager_libinput_config_keyboard( struct sway_input_device *input_device) { 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) { wl_signal_add(&device->events.destroy, &input_device->device_destroy); input_device->device_destroy.notify = handle_device_destroy; - struct sway_seat *seat = NULL; - if (!input_has_seat_fallback_configuration()) { - wlr_log(WLR_DEBUG, "no seat config - creating default seat config"); - seat = input_manager_get_default_seat(); - struct seat_config *sc = new_seat_config(seat->wlr_seat->name); - sc->fallback = true; - store_seat_config(sc); - } + input_manager_verify_fallback_seat(); bool added = false; + struct sway_seat *seat = NULL; wl_list_for_each(seat, &input->seats, link) { struct seat_config *seat_config = seat_get_config(seat); 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, wl_list_for_each(seat_device, &seat->devices, link) { seat_configure_device(seat, seat_device->input_device); } + + cursor_handle_activity(seat->cursor); } struct seat_config *seat_get_config(struct sway_seat *seat) { @@ -1009,6 +1011,18 @@ struct seat_config *seat_get_config(struct sway_seat *seat) { return NULL; } +struct seat_config *seat_get_config_by_name(const char *name) { + struct seat_config *seat_config = NULL; + for (int i = 0; i < config->seat_configs->length; ++i ) { + seat_config = config->seat_configs->items[i]; + if (strcmp(name, seat_config->name) == 0) { + return seat_config; + } + } + + return NULL; +} + void seat_begin_down(struct sway_seat *seat, struct sway_container *con, uint32_t button, double sx, double sy) { seat->operation = OP_DOWN; @@ -1038,6 +1052,17 @@ void seat_begin_move_floating(struct sway_seat *seat, cursor_set_image(seat->cursor, "grab", NULL); } +void seat_begin_move_tiling_threshold(struct sway_seat *seat, + struct sway_container *con, uint32_t button) { + seat->operation = OP_MOVE_TILING_THRESHOLD; + seat->op_container = con; + seat->op_button = button; + seat->op_target_node = NULL; + seat->op_target_edge = 0; + seat->op_ref_lx = seat->cursor->cursor->x; + seat->op_ref_ly = seat->cursor->cursor->y; +} + void seat_begin_move_tiling(struct sway_seat *seat, struct sway_container *con, uint32_t button) { seat->operation = OP_MOVE_TILING; @@ -1206,4 +1231,8 @@ void seat_consider_warp_to_focus(struct sway_seat *seat) { } else { cursor_warp_to_workspace(seat->cursor, focus->sway_workspace); } + if (seat->cursor->hidden){ + cursor_unhide(seat->cursor); + wl_event_source_timer_update(seat->cursor->hide_source, cursor_get_timeout(seat->cursor)); + } } 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 @@ #include <json-c/json.h> #include <stdio.h> #include <ctype.h> +#include "config.h" #include "log.h" #include "sway/config.h" #include "sway/ipc-json.h" @@ -785,5 +786,41 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) { } json_object_object_add(json, "outputs", outputs); } +#if HAVE_TRAY + // Add tray outputs if defined + if (bar->tray_outputs && bar->tray_outputs->length > 0) { + json_object *tray_outputs = json_object_new_array(); + for (int i = 0; i < bar->tray_outputs->length; ++i) { + const char *name = bar->tray_outputs->items[i]; + json_object_array_add(tray_outputs, json_object_new_string(name)); + } + json_object_object_add(json, "tray_outputs", tray_outputs); + } + + json_object *tray_bindings = json_object_new_array(); + for (int i = 0; i < 10; ++i) { + if (bar->tray_bindings[i]) { + json_object *bind = json_object_new_object(); + json_object_object_add(bind, "input_code", + json_object_new_int(i)); + json_object_object_add(bind, "command", + json_object_new_string(bar->tray_bindings[i])); + json_object_array_add(tray_bindings, bind); + } + } + if (json_object_array_length(tray_bindings) > 0) { + json_object_object_add(json, "tray_bindings", tray_bindings); + } else { + json_object_put(tray_bindings); + } + + if (bar->icon_theme) { + json_object_object_add(json, "icon_theme", + json_object_new_string(bar->icon_theme)); + } + + json_object_object_add(json, "tray_padding", + json_object_new_int(bar->tray_padding)); +#endif return json; } 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( 'commands/force_focus_wrapping.c', 'commands/fullscreen.c', 'commands/gaps.c', - 'commands/hide_cursor.c', 'commands/hide_edge_borders.c', 'commands/kill.c', 'commands/mark.c', @@ -79,6 +78,7 @@ sway_sources = files( 'commands/seat/attach.c', 'commands/seat/cursor.c', 'commands/seat/fallback.c', + 'commands/seat/hide_cursor.c', 'commands/set.c', 'commands/show_marks.c', 'commands/smart_borders.c', @@ -89,6 +89,7 @@ sway_sources = files( 'commands/swaynag_command.c', 'commands/swap.c', 'commands/tiling_drag.c', + 'commands/tiling_drag_threshold.c', 'commands/title_align.c', 'commands/title_format.c', 'commands/titlebar_border_thickness.c', @@ -99,11 +100,9 @@ sway_sources = files( 'commands/workspace_layout.c', 'commands/ws_auto_back_and_forth.c', - 'commands/bar/activate_button.c', 'commands/bar/binding_mode_indicator.c', 'commands/bar/bindsym.c', 'commands/bar/colors.c', - 'commands/bar/context_button.c', 'commands/bar/font.c', 'commands/bar/gaps.c', 'commands/bar/height.c', @@ -115,12 +114,12 @@ sway_sources = files( 'commands/bar/output.c', 'commands/bar/pango_markup.c', 'commands/bar/position.c', - 'commands/bar/secondary_button.c', 'commands/bar/separator_symbol.c', 'commands/bar/status_command.c', 'commands/bar/strip_workspace_numbers.c', 'commands/bar/strip_workspace_name.c', 'commands/bar/swaybar_command.c', + 'commands/bar/tray_bindsym.c', 'commands/bar/tray_output.c', 'commands/bar/tray_padding.c', '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 @@ #include <wlr/backend/session.h> #include <wlr/render/wlr_renderer.h> #include <wlr/types/wlr_compositor.h> +#include <wlr/types/wlr_data_control_v1.h> #include <wlr/types/wlr_export_dmabuf_v1.h> #include <wlr/types/wlr_gamma_control_v1.h> #include <wlr/types/wlr_gamma_control.h> @@ -140,6 +141,7 @@ bool server_init(struct sway_server *server) { wlr_export_dmabuf_manager_v1_create(server->wl_display); wlr_screencopy_manager_v1_create(server->wl_display); + wlr_data_control_manager_v1_create(server->wl_display); server->socket = wl_display_add_socket_auto(server->wl_display); 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. The _button_ argument in all cases is a platform-specific button code. On Linux you can find a list of these at linux/input-event-codes.h. -*activate\_button* <button> - Sets the button to be used for the _activate_ (primary click) tray item - event. The default is BTN\_LEFT (0x110). - -*context\_button* <button> - Sets the button to be used for the _context menu_ (right click) tray item - event. The default is BTN\_RIGHT (0x111). - -*secondary\_button* <button> - Sets the button to be used for the _secondary_ (middle click) tray item - event. The default is BTN\_MIDDLE (0x112). - -*tray\_output* none|all|<output> - Sets the output that the tray will appear on or none. Unlike i3bar, swaybar - is able to show icons on any number of bars and outputs without races. - The default is _all_. +*tray\_bindsym* button<n> ContextMenu|Activate|SecondaryActivate|ScrollDown|ScrollLeft|ScrollRight|ScrollUp|nop + Binds mouse button _n_ (1 to 9) to the specified action. Use the command + _nop_ to disable the default action (Activate for button 1, ContextMenu for + button 2 and SecondaryActivate for button 3). *tray\_padding* <px> [px] Sets the pixel padding of the system tray. This padding will surround the tray on all sides and between each item. The default value for _px_ is 2. +*tray\_output* none|<output> + Restrict the tray to a certain output, can be specified multiple times. If + omitted, the tray will be displayed on all outputs. Unlike i3bar, swaybar + can show icons on any number of bars and outputs without races. + *icon\_theme* <name> Sets the icon theme that sway will look for item icons in. This option has 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"). Set this seat as the fallback seat. A fallback seat will attach any device not explicitly attached to another seat (similar to a "default" seat). +*seat* <name> hide\_cursor <timeout> + Hides the cursor image after the specified _timeout_ (in milliseconds) + has elapsed with no activity on that cursor. A timeout of 0 (default) + disables hiding the cursor. The minimal timeout is 100 and any value less + than that (aside from 0), will be increased to 100. + # SEE ALSO *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: Places the specified output at the specific position in the global coordinate space. If scaling is active, it has to be considered when positioning. For example, if the scaling factor for the left output is 2, - the relative position for the right output has to be divided by 2. + the relative position for the right output has to be divided by 2. The + reference point is the top left corner so if you want the bottoms aligned + this has to be considered as well. Example: output HDMI1 scale 2 - output HDMI1 pos 0 0 res 3200x1800 + output HDMI1 pos 0 1020 res 3200x1800 output eDP1 pos 1600 0 res 1920x1080 - Note that the x-pos of eDP1 is 1600 = 3200/2. + Note that the left x-pos of eDP1 is 1600 = 3200/2 and the bottom y-pos is + 1020 + (1800 / 2) = 1920 = 0 + 1920 *output* <name> scale <factor> Scales the specified output by the specified scale _factor_. An integer is @@ -57,7 +60,8 @@ must be separated by one space. For example: represent the contents of your windows - they will be rendered at the next highest integral scale factor and downscaled. You may be better served by setting an integral scale factor and adjusting the font size of your - applications to taste. + applications to taste. HiDPI isn't supported with Xwayland clients (windows + will blur). *output* <name> background|bg <file> <mode> [<fallback\_color>] 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). The following environment variables have an effect on sway: -_SWAY\_CURSOR\_THEME_ - Specifies the name of the cursor theme to use. - -_SWAY\_CURSOR\_SIZE_ - Specifies the size of the cursor to use. - _SWAYSOCK_ Specifies the path to the sway IPC socket. _XKB\_DEFAULT\_RULES_, _XKB\_DEFAULT\_MODEL_, _XKB\_DEFAULT\_LAYOUT_, _XKB\_DEFAULT\_VARIANT_, _XKB\_DEFAULT\_OPTIONS_ - Configures the xkb keyboard settings. See *xkeyboard-config*(7). + Configures the xkb keyboard settings. See *xkeyboard-config*(7). The + preferred way to configure the keyboard is via the configuration file, see + *sway-input*(5). # AUTHORS 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. A view that does not have focus. *client.urgent* - A view with an urgency hint. *Note*: This is not currently implemented. + A view with an urgency hint. *Note*: Native Wayland windows do not + support urgency. Urgency only works for Xwayland windows. The meaning of each color is: @@ -431,7 +432,7 @@ The default colors are: *focus\_follows\_mouse* yes|no|always If set to _yes_, moving your mouse over a window will focus that window. If - set to _always_, the window under the cursor will always be focused, even + set to _always_, the window under the cursor will always be focused, even after switching between workspaces. *focus\_wrapping* yes|no|force @@ -450,11 +451,11 @@ The default colors are: Thickness of the titlebar border in pixels *titlebar\_padding* <horizontal> [<vertical>] - Padding of the text in the titlebar. _horizontal_ value affects horizontal - padding of the text while _vertical_ value affects vertical padding (space - above and below text). Padding includes titlebar borders so their value + Padding of the text in the titlebar. _horizontal_ value affects horizontal + padding of the text while _vertical_ value affects vertical padding (space + above and below text). Padding includes titlebar borders so their value should be greater than titlebar\_border\_thickness. If _vertical_ value is - not specified it is set to the _horizontal_ value. + not specified it is set to the _horizontal_ value. *for\_window* <criteria> <command> Whenever a window that matches _criteria_ appears, run list of commands. @@ -477,12 +478,6 @@ The default colors are: This affects new workspaces only, and is used when the workspace doesn't have its own gaps settings (see: workspace <ws> gaps ...). -*hide\_cursor* <timeout> - Hides the cursor image after the specified _timeout_ (in milliseconds) - has elapsed with no activity on that cursor. A timeout of 0 (default) - disables hiding the cursor. The minimal timeout is 100 and any value less - than that (aside from 0), will be increased to 100. - *hide\_edge\_borders* none|vertical|horizontal|both|smart|smart\_no\_gaps Hides window borders adjacent to the screen edges. Default is _none_. @@ -578,6 +573,14 @@ The default colors are: the _floating\_mod_ will also allow the container to be dragged. _toggle_ should not be used in the config file. +*tiling\_drag\_threshold* <threshold> + Sets the threshold that must be exceeded for a container to be dragged by + its titlebar. This has no effect if _floating\_mod_ is used or if + _tiling\_drag_ is set to _disable_. Once the threshold has been exceeded + once, the drag starts and the cursor can come back inside the threshold + without stopping the drag. _threshold_ is multiplied by the scale of the + output that the cursor on. The default is 9. + *title\_align* left|center|right Sets the title alignment. If _right_ is selected and _show\_marks_ is set 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, int width = 0; int height = con->title_height * scale; - cairo_t *c = cairo_create(NULL); + // We must use a non-nil cairo_t for cairo_set_font_options to work. + // Therefore, we cannot use cairo_create(NULL). + cairo_surface_t *dummy_surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, 0, 0); + cairo_t *c = cairo_create(dummy_surface); + cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); + cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); + cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(output->wlr_output->subpixel)); + cairo_set_font_options(c, fo); get_text_size(c, config->font, &width, NULL, NULL, scale, config->pango_markup, "%s", con->formatted_title); + cairo_surface_destroy(dummy_surface); cairo_destroy(c); cairo_surface_t *surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height); cairo_t *cairo = cairo_create(surface); cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); - cairo_font_options_t *fo = cairo_font_options_create(); - cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); - cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); - cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(output->wlr_output->subpixel)); cairo_set_font_options(cairo, fo); cairo_font_options_destroy(fo); 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) { struct sway_seat *seat; wl_list_for_each(seat, &server.input->seats, link) { - if (config->mouse_warping == WARP_CONTAINER) { - struct sway_node *node = seat_get_focus(seat); - if (node && node->type == N_CONTAINER) { - cursor_warp_to_container(seat->cursor, node->sway_container); - } else if (node && node->type == N_WORKSPACE) { - cursor_warp_to_workspace(seat->cursor, node->sway_workspace); - } - } + seat->cursor->image_surface = NULL; + seat_consider_warp_to_focus(seat); } 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 @@ #include <wayland-client.h> #include <wayland-cursor.h> #include <wlr/util/log.h> +#include "config.h" #include "swaybar/bar.h" #include "swaybar/config.h" #include "swaybar/i3bar.h" @@ -18,6 +19,9 @@ #include "swaybar/ipc.h" #include "swaybar/status_line.h" #include "swaybar/render.h" +#if HAVE_TRAY +#include "swaybar/tray/tray.h" +#endif #include "ipc-client.h" #include "list.h" #include "log.h" @@ -120,7 +124,7 @@ static void destroy_layer_surface(struct swaybar_output *output) { output->frame_scheduled = false; } -static void set_bar_dirty(struct swaybar *bar) { +void set_bar_dirty(struct swaybar *bar) { struct swaybar_output *output; wl_list_for_each(output, &bar->outputs, link) { set_output_dirty(output); @@ -211,12 +215,16 @@ struct wl_output_listener output_listener = { static void xdg_output_handle_logical_position(void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - // Who cares + struct swaybar_output *output = data; + output->output_x = x; + output->output_y = y; } static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { - // Who cares + struct swaybar_output *output = data; + output->output_height = height; + output->output_width = width; } static void xdg_output_handle_done(void *data, @@ -362,6 +370,12 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) { pointer->cursor_surface = wl_compositor_create_surface(bar->compositor); assert(pointer->cursor_surface); +#if HAVE_TRAY + if (!bar->config->tray_hidden) { + bar->tray = create_tray(bar); + } +#endif + if (bar->config->workspace_buttons) { ipc_get_workspaces(bar); } @@ -403,6 +417,11 @@ void bar_run(struct swaybar *bar) { loop_add_fd(bar->eventloop, bar->status->read_fd, POLLIN, status_in, bar); } +#if HAVE_TRAY + if (bar->tray) { + loop_add_fd(bar->eventloop, bar->tray->fd, POLLIN, tray_in, bar->tray->bus); + } +#endif while (1) { errno = 0; if (wl_display_flush(bar->display) == -1 && errno != EAGAIN) { @@ -420,6 +439,9 @@ static void free_outputs(struct wl_list *list) { } void bar_teardown(struct swaybar *bar) { +#if HAVE_TRAY + destroy_tray(bar->tray); +#endif free_outputs(&bar->outputs); if (bar->config) { 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 @@ #include <wlr/util/log.h> #include "swaybar/config.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include "config.h" #include "stringop.h" #include "list.h" @@ -73,6 +74,10 @@ struct swaybar_config *init_config(void) { config->colors.binding_mode.background = 0x900000FF; config->colors.binding_mode.text = 0xFFFFFFFF; +#if HAVE_TRAY + config->tray_padding = 2; +#endif + return config; } @@ -102,5 +107,12 @@ void free_config(struct swaybar_config *config) { free(coutput->name); free(coutput); } +#if HAVE_TRAY + list_free_items_and_destroy(config->tray_outputs); + for (int i = 0; i < 10; ++i) { + free(config->tray_bindings[i]); + } + free(config->icon_theme); +#endif free(config); } 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 @@ #include <wlr/util/log.h> #include "swaybar/config.h" #include "swaybar/ipc.h" +#include "config.h" #include "ipc-client.h" #include "list.h" @@ -282,6 +283,40 @@ static bool ipc_parse_config( ipc_parse_colors(config, colors); } +#if HAVE_TRAY + json_object *tray_outputs, *tray_padding, *tray_bindings, *icon_theme; + + if ((json_object_object_get_ex(bar_config, "tray_outputs", &tray_outputs))) { + config->tray_outputs = create_list(); + int length = json_object_array_length(tray_outputs); + for (int i = 0; i < length; ++i) { + json_object *o = json_object_array_get_idx(tray_outputs, i); + list_add(config->tray_outputs, strdup(json_object_get_string(o))); + } + config->tray_hidden = strcmp(config->tray_outputs->items[0], "none") == 0; + } + + if ((json_object_object_get_ex(bar_config, "tray_padding", &tray_padding))) { + config->tray_padding = json_object_get_int(tray_padding); + } + + if ((json_object_object_get_ex(bar_config, "tray_bindings", &tray_bindings))) { + int length = json_object_array_length(tray_bindings); + for (int i = 0; i < length; ++i) { + json_object *bind = json_object_array_get_idx(tray_bindings, i); + json_object *button, *command; + json_object_object_get_ex(bind, "input_code", &button); + json_object_object_get_ex(bind, "command", &command); + config->tray_bindings[json_object_get_int(button)] = + strdup(json_object_get_string(command)); + } + } + + if ((json_object_object_get_ex(bar_config, "icon_theme", &icon_theme))) { + config->icon_theme = strdup(json_object_get_string(icon_theme)); + } +#endif + json_object_put(bar_config); return true; } 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 @@ +tray_files = get_option('enable-tray') ? [ + 'tray/host.c', + 'tray/icon.c', + 'tray/item.c', + 'tray/tray.c', + 'tray/watcher.c' +] : [] + +swaybar_deps = [ + cairo, + client_protos, + gdk_pixbuf, + jsonc, + math, + pango, + pangocairo, + rt, + wayland_client, + wayland_cursor, + wlroots, +] +if get_option('enable-tray') + if systemd.found() + swaybar_deps += systemd + elif elogind.found() + swaybar_deps += elogind + endif +endif + executable( 'swaybar', [ 'bar.c', @@ -8,21 +37,10 @@ executable( 'main.c', 'render.c', 'status_line.c', + tray_files ], include_directories: [sway_inc], - dependencies: [ - cairo, - client_protos, - gdk_pixbuf, - jsonc, - math, - pango, - pangocairo, - rt, - wayland_client, - wayland_cursor, - wlroots, - ], + dependencies: swaybar_deps, link_with: [lib_sway_common, lib_sway_client], install_rpath : rpathdir, 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 @@ #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/status_line.h" +#if HAVE_TRAY +#include "swaybar/tray/tray.h" +#endif #include "wlr-layer-shell-unstable-v1-client-protocol.h" static const int WS_HORIZONTAL_PADDING = 5; @@ -453,6 +456,12 @@ static uint32_t render_to_cairo(cairo_t *cairo, struct swaybar_output *output) { * utilize the available space. */ double x = output->width * output->scale; +#if HAVE_TRAY + if (bar->tray) { + uint32_t h = render_tray(cairo, output, &x); + max_height = h > max_height ? h : max_height; + } +#endif if (bar->status) { uint32_t h = render_status_line(cairo, output, &x); 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) { } free(status->read); free(status->write); - free((char*) status->text); free(status->buffer); free(status); } 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 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "swaybar/bar.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "list.h" +#include "log.h" + +static const char *watcher_path = "/StatusNotifierWatcher"; + +static int cmp_sni_id(const void *item, const void *cmp_to) { + const struct swaybar_sni *sni = item; + return strcmp(sni->watcher_id, cmp_to); +} + +static void add_sni(struct swaybar_tray *tray, char *id) { + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id); + struct swaybar_sni *sni = create_sni(id, tray); + if (sni) { + list_add(tray->items, sni); + } + } +} + +static int handle_sni_registered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + add_sni(tray, id); + + return ret; +} + +static int handle_sni_unregistered(sd_bus_message *msg, void *data, + sd_bus_error *error) { + char *id; + int ret = sd_bus_message_read(msg, "s", &id); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse unregister SNI message: %s", strerror(-ret)); + } + + struct swaybar_tray *tray = data; + int idx = list_seq_find(tray->items, cmp_sni_id, id); + if (idx != -1) { + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id); + destroy_sni(tray->items->items[idx]); + list_del(tray->items, idx); + set_bar_dirty(tray->bar); + } + return ret; +} + +static int get_registered_snis_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + if (sd_bus_message_is_method_error(msg, NULL)) { + sd_bus_error err = *sd_bus_message_get_error(msg); + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", err.message); + return -sd_bus_error_get_errno(&err); + } + + int ret = sd_bus_message_enter_container(msg, 'v', NULL); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + char **ids; + ret = sd_bus_message_read_strv(msg, &ids); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to read registered SNIs: %s", strerror(-ret)); + return ret; + } + + if (ids) { + struct swaybar_tray *tray = data; + for (char **id = ids; *id; ++id) { + add_sni(tray, *id); + } + } + + return ret; +} + +static bool register_to_watcher(struct swaybar_host *host) { + // this is called asynchronously in case the watcher is owned by this process + int ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, host->watcher_interface, + "RegisterStatusNotifierHost", NULL, NULL, "s", host->service); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to send register call: %s", strerror(-ret)); + return false; + } + + ret = sd_bus_call_method_async(host->tray->bus, NULL, + host->watcher_interface, watcher_path, + "org.freedesktop.DBus.Properties", "Get", + get_registered_snis_callback, host->tray, "ss", + host->watcher_interface, "RegisteredStatusNotifierItems"); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to get registered SNIs: %s", strerror(-ret)); + } + + return ret >= 0; +} + +static int handle_new_watcher(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*old_owner) { + struct swaybar_host *host = data; + if (strcmp(service, host->watcher_interface) == 0) { + register_to_watcher(host); + } + } + + return 0; +} + +bool init_host(struct swaybar_host *host, char *protocol, + struct swaybar_tray *tray) { + size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; + host->watcher_interface = malloc(len); + if (!host->watcher_interface) { + return false; + } + snprintf(host->watcher_interface, len, "org.%s.StatusNotifierWatcher", protocol); + + sd_bus_slot *reg_slot = NULL, *unreg_slot = NULL, *watcher_slot = NULL; + int ret = sd_bus_match_signal(tray->bus, ®_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemRegistered", handle_sni_registered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to registering events: %s", + strerror(-ret)); + goto error; + } + ret = sd_bus_match_signal(tray->bus, &unreg_slot, host->watcher_interface, + watcher_path, host->watcher_interface, + "StatusNotifierItemUnregistered", handle_sni_unregistered, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + ret = sd_bus_match_signal(tray->bus, &watcher_slot, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", + handle_new_watcher, host); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + pid_t pid = getpid(); + size_t service_len = snprintf(NULL, 0, "org.%s.StatusNotifierHost-%d", + protocol, pid) + 1; + host->service = malloc(service_len); + if (!host->service) { + goto error; + } + snprintf(host->service, service_len, "org.%s.StatusNotifierHost-%d", protocol, pid); + ret = sd_bus_request_name(tray->bus, host->service, 0); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to acquire service name: %s", strerror(-ret)); + goto error; + } + + host->tray = tray; + if (!register_to_watcher(host)) { + goto error; + } + + sd_bus_slot_set_floating(reg_slot, 1); + sd_bus_slot_set_floating(unreg_slot, 1); + sd_bus_slot_set_floating(watcher_slot, 1); + + wlr_log(WLR_DEBUG, "Registered %s", host->service); + return true; +error: + sd_bus_slot_unref(reg_slot); + sd_bus_slot_unref(unreg_slot); + sd_bus_slot_unref(watcher_slot); + finish_host(host); + return false; +} + +void finish_host(struct swaybar_host *host) { + sd_bus_release_name(host->tray->bus, host->service); + free(host->service); + free(host->watcher_interface); +} 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 @@ +#define _POSIX_C_SOURCE 200809L +#include <ctype.h> +#include <dirent.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <wordexp.h> +#include "swaybar/tray/icon.h" +#include "config.h" +#include "list.h" +#include "log.h" +#include "stringop.h" + +static bool dir_exists(char *path) { + struct stat sb; + return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); +} + +static list_t *get_basedirs(void) { + list_t *basedirs = create_list(); + list_add(basedirs, strdup("$HOME/.icons")); // deprecated + + char *data_home = getenv("XDG_DATA_HOME"); + list_add(basedirs, strdup(data_home && *data_home ? + "$XDG_DATA_HOME/icons" : "$HOME/.local/share/icons")); + + list_add(basedirs, strdup("/usr/share/pixmaps")); + + char *data_dirs = getenv("XDG_DATA_DIRS"); + if (!(data_dirs && *data_dirs)) { + data_dirs = "/usr/local/share:/usr/share"; + } + data_dirs = strdup(data_dirs); + char *dir = strtok(data_dirs, ":"); + do { + size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/icons", dir); + list_add(basedirs, path); + } while ((dir = strtok(NULL, ":"))); + free(data_dirs); + + list_t *basedirs_expanded = create_list(); + for (int i = 0; i < basedirs->length; ++i) { + wordexp_t p; + if (wordexp(basedirs->items[i], &p, WRDE_UNDEF) == 0) { + if (dir_exists(p.we_wordv[0])) { + list_add(basedirs_expanded, strdup(p.we_wordv[0])); + } + wordfree(&p); + } + } + + list_free_items_and_destroy(basedirs); + + return basedirs_expanded; +} + +static void destroy_theme(struct icon_theme *theme) { + if (!theme) { + return; + } + free(theme->name); + free(theme->comment); + free(theme->inherits); + list_free_items_and_destroy(theme->directories); + free(theme->dir); + + for (int i = 0; i < theme->subdirs->length; ++i) { + struct icon_theme_subdir *subdir = theme->subdirs->items[i]; + free(subdir->name); + free(subdir); + } + list_free(theme->subdirs); + free(theme); +} + +static int cmp_group(const void *item, const void *cmp_to) { + return strcmp(item, cmp_to); +} + +static bool group_handler(char *old_group, char *new_group, + struct icon_theme *theme) { + if (!old_group) { // first group must be "Icon Theme" + return strcmp(new_group, "Icon Theme"); + } + + if (strcmp(old_group, "Icon Theme") == 0) { + if (!(theme->name && theme->comment && theme->directories)) { + return true; + } + } else { + if (theme->subdirs->length == 0) { // skip + return false; + } + + struct icon_theme_subdir *subdir = + theme->subdirs->items[theme->subdirs->length - 1]; + if (!subdir->size) return true; + + switch (subdir->type) { + case FIXED: subdir->max_size = subdir->min_size = subdir->size; + break; + case SCALABLE: { + if (!subdir->max_size) subdir->max_size = subdir->size; + if (!subdir->min_size) subdir->min_size = subdir->size; + break; + } + case THRESHOLD: + subdir->max_size = subdir->size + subdir->threshold; + subdir->min_size = subdir->size - subdir->threshold; + } + } + + if (new_group && list_seq_find(theme->directories, cmp_group, new_group) != -1) { + struct icon_theme_subdir *subdir = calloc(1, sizeof(struct icon_theme_subdir)); + if (!subdir) { + return true; + } + subdir->name = strdup(new_group); + subdir->threshold = 2; + list_add(theme->subdirs, subdir); + } + + return false; +} + +static int entry_handler(char *group, char *key, char *value, + struct icon_theme *theme) { + if (strcmp(group, "Icon Theme") == 0) { + if (strcmp(key, "Name") == 0) { + theme->name = strdup(value); + } else if (strcmp(key, "Comment") == 0) { + theme->comment = strdup(value); + } else if (strcmp(key, "Inherists") == 0) { + theme->inherits = strdup(value); + } else if (strcmp(key, "Directories") == 0) { + theme->directories = split_string(value, ","); + } // Ignored: ScaledDirectories, Hidden, Example + } else { + if (theme->subdirs->length == 0) { // skip + return false; + } + + struct icon_theme_subdir *subdir = + theme->subdirs->items[theme->subdirs->length - 1]; + if (strcmp(subdir->name, group) != 0) { // skip + return false; + } + + char *end; + int n = strtol(value, &end, 10); + if (strcmp(key, "Size") == 0) { + subdir->size = n; + return *end != '\0'; + } else if (strcmp(key, "Type") == 0) { + if (strcmp(value, "Fixed") == 0) { + subdir->type = FIXED; + } else if (strcmp(value, "Scalable") == 0) { + subdir->type = SCALABLE; + } else if (strcmp(value, "Threshold") == 0) { + subdir->type = THRESHOLD; + } else { + return true; + } + } else if (strcmp(key, "MaxSize") == 0) { + subdir->max_size = n; + return *end != '\0'; + } else if (strcmp(key, "MinSize") == 0) { + subdir->min_size = n; + return *end != '\0'; + } else if (strcmp(key, "Threshold") == 0) { + subdir->threshold = n; + return *end != '\0'; + } // Ignored: Scale, Applications + } + return false; +} + +/* + * This is a Freedesktop Desktop Entry parser (essentially INI) + * It calls entry_handler for every entry + * and group_handler between every group (as well as at both ends) + * Handlers return whether an error occured, which stops parsing + */ +static struct icon_theme *read_theme_file(char *basedir, char *theme_name) { + // look for index.theme file + size_t path_len = snprintf(NULL, 0, "%s/%s/index.theme", basedir, + theme_name) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/%s/index.theme", basedir, theme_name); + FILE *theme_file = fopen(path, "r"); + if (!theme_file) { + return NULL; + } + + struct icon_theme *theme = calloc(1, sizeof(struct icon_theme)); + if (!theme) { + return NULL; + } + theme->subdirs = create_list(); + + bool error = false; + char *group = NULL; + char *full_line = NULL; + size_t full_len = 0; + ssize_t nread; + while ((nread = getline(&full_line, &full_len, theme_file)) != -1) { + char *line = full_line - 1; + while (isspace(*++line)) {} // remove leading whitespace + if (!*line || line[0] == '#') continue; // ignore blank lines & comments + + int len = nread - (line - full_line); + while (isspace(line[--len])) {} + line[++len] = '\0'; // remove trailing whitespace + + if (line[0] == '[') { // group header + // check well-formed + if (line[--len] != ']') { + error = true; + break; + } + int i = 1; + for (; !iscntrl(line[i]) && line[i] != '[' && line[i] != ']'; ++i) {} + if (i < len) { + error = true; + break; + } + + // call handler + line[len] = '\0'; + error = group_handler(group, &line[1], theme); + if (error) { + break; + } + free(group); + group = strdup(&line[1]); + } else { // key-value pair + // check well-formed + int eok = 0; + for (; isalnum(line[eok]) || line[eok] == '-'; ++eok) {} // TODO locale? + int i = eok - 1; + while (isspace(line[++i])) {} + if (line[i] != '=') { + error = true; + break; + } + + line[eok] = '\0'; // split into key-value pair + char *value = &line[i]; + while (isspace(*++value)) {} + // TODO unescape value + error = entry_handler(group, line, value, theme); + if (error) { + break; + } + } + } + + if (!error && group) { + error = group_handler(group, NULL, theme); + } + + free(group); + free(full_line); + fclose(theme_file); + + if (!error) { + theme->dir = strdup(theme_name); + return theme; + } else { + destroy_theme(theme); + return NULL; + } +} + +static list_t *load_themes_in_dir(char *basedir) { + DIR *dir; + if (!(dir = opendir(basedir))) { + return NULL; + } + + list_t *themes = create_list(); + struct dirent *entry; + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') continue; + + struct icon_theme *theme = read_theme_file(basedir, entry->d_name); + if (theme) { + list_add(themes, theme); + } + } + return themes; +} + +void init_themes(list_t **themes, list_t **basedirs) { + *basedirs = get_basedirs(); + + *themes = create_list(); + for (int i = 0; i < (*basedirs)->length; ++i) { + list_t *dir_themes = load_themes_in_dir((*basedirs)->items[i]); + list_cat(*themes, dir_themes); + list_free(dir_themes); + } + + list_t *theme_names = create_list(); + for (int i = 0; i < (*themes)->length; ++i) { + struct icon_theme *theme = (*themes)->items[i]; + list_add(theme_names, theme->name); + } + wlr_log(WLR_DEBUG, "Loaded themes: %s", join_list(theme_names, ", ")); + list_free(theme_names); +} + +void finish_themes(list_t *themes, list_t *basedirs) { + for (int i = 0; i < themes->length; ++i) { + destroy_theme(themes->items[i]); + } + list_free(themes); + list_free_items_and_destroy(basedirs); +} + +static char *find_icon_in_subdir(char *name, char *basedir, char *theme, + char *subdir) { + static const char *extensions[] = { +#if HAVE_GDK_PIXBUF + "svg", +#endif + "png", +#if HAVE_GDK_PIXBUF + "xpm" +#endif + }; + + size_t path_len = snprintf(NULL, 0, "%s/%s/%s/%s.EXT", basedir, theme, + subdir, name) + 1; + char *path = malloc(path_len); + + for (size_t i = 0; i < sizeof(extensions) / sizeof(*extensions); ++i) { + snprintf(path, path_len, "%s/%s/%s/%s.%s", basedir, theme, subdir, + name, extensions[i]); + if (access(path, R_OK) == 0) { + return path; + } + } + + free(path); + return NULL; +} + +static bool theme_exists_in_basedir(char *theme, char *basedir) { + size_t path_len = snprintf(NULL, 0, "%s/%s", basedir, theme) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/%s", basedir, theme); + bool ret = dir_exists(path); + free(path); + return ret; +} + +static char *find_icon_with_theme(list_t *basedirs, list_t *themes, char *name, + int size, char *theme_name, int *min_size, int *max_size) { + struct icon_theme *theme = NULL; + for (int i = 0; i < themes->length; ++i) { + theme = themes->items[i]; + if (strcmp(theme->name, theme_name) == 0) { + break; + } + theme = NULL; + } + if (!theme) return NULL; + + char *icon = NULL; + for (int i = 0; i < basedirs->length; ++i) { + if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) { + continue; + } + // search backwards to hopefully hit scalable/larger icons first + for (int j = theme->subdirs->length - 1; j >= 0; --j) { + struct icon_theme_subdir *subdir = theme->subdirs->items[j]; + if (size >= subdir->min_size && size <= subdir->max_size) { + if ((icon = find_icon_in_subdir(name, basedirs->items[i], + theme->dir, subdir->name))) { + *min_size = subdir->min_size; + *max_size = subdir->max_size; + return icon; + } + } + } + } + + // inexact match + unsigned smallest_error = -1; // UINT_MAX + for (int i = 0; i < basedirs->length; ++i) { + if (!theme_exists_in_basedir(theme->dir, basedirs->items[i])) { + continue; + } + for (int j = theme->subdirs->length - 1; j >= 0; --j) { + struct icon_theme_subdir *subdir = theme->subdirs->items[j]; + unsigned error = (size > subdir->max_size ? size - subdir->max_size : 0) + + (size < subdir->min_size ? subdir->min_size - size : 0); + if (error < smallest_error) { + char *test_icon = find_icon_in_subdir(name, basedirs->items[i], + theme->dir, subdir->name); + if (test_icon) { + icon = test_icon; + smallest_error = error; + *min_size = subdir->min_size; + *max_size = subdir->max_size; + } + } + } + } + + if (!icon && theme->inherits) { + icon = find_icon_with_theme(basedirs, themes, name, size, + theme->inherits, min_size, max_size); + } + + return icon; +} + +char *find_icon_in_dir(char *name, char *dir, int *min_size, int *max_size) { + char *icon = find_icon_in_subdir(name, dir, "", ""); + if (icon) { + *min_size = 1; + *max_size = 512; + } + return icon; + +} + +static char *find_fallback_icon(list_t *basedirs, char *name, int *min_size, + int *max_size) { + for (int i = 0; i < basedirs->length; ++i) { + char *icon = find_icon_in_dir(name, basedirs->items[i], min_size, max_size); + if (icon) { + return icon; + } + } + return NULL; +} + +char *find_icon(list_t *themes, list_t *basedirs, char *name, int size, + char *theme, int *min_size, int *max_size) { + // TODO https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#implementation_notes + char *icon = NULL; + if (theme) { + icon = find_icon_with_theme(basedirs, themes, name, size, theme, + min_size, max_size); + } + if (!icon) { + icon = find_icon_with_theme(basedirs, themes, name, size, "Hicolor", + min_size, max_size); + } + if (!icon) { + icon = find_fallback_icon(basedirs, name, min_size, max_size); + } + return icon; +} 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 @@ +#define _POSIX_C_SOURCE 200809L +#include <cairo.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "swaybar/input.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/icon.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "background-image.h" +#include "cairo.h" +#include "list.h" +#include "log.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + +// TODO menu + +static bool sni_ready(struct swaybar_sni *sni) { + return sni->status && (sni->status[0] == 'N' ? + sni->attention_icon_name || sni->attention_icon_pixmap : + sni->icon_name || sni->icon_pixmap); +} + +static void set_sni_dirty(struct swaybar_sni *sni) { + if (sni_ready(sni)) { + sni->min_size = sni->max_size = 0; // invalidate previous icon + set_bar_dirty(sni->tray->bar); + } +} + +static int read_pixmap(sd_bus_message *msg, struct swaybar_sni *sni, + const char *prop, list_t **dest) { + int ret = sd_bus_message_enter_container(msg, 'a', "(iiay)"); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + return ret; + } + + if (sd_bus_message_at_end(msg, 0)) { + return ret; + } + + list_t *pixmaps = create_list(); + if (!pixmaps) { + return -12; // -ENOMEM + } + + while (!sd_bus_message_at_end(msg, 0)) { + ret = sd_bus_message_enter_container(msg, 'r', "iiay"); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + int size; + ret = sd_bus_message_read(msg, "ii", NULL, &size); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + const void *pixels; + size_t npixels; + ret = sd_bus_message_read_array(msg, 'y', &pixels, &npixels); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto error; + } + + struct swaybar_pixmap *pixmap = + malloc(sizeof(struct swaybar_pixmap) + npixels); + pixmap->size = size; + memcpy(pixmap->pixels, pixels, npixels); + list_add(pixmaps, pixmap); + + sd_bus_message_exit_container(msg); + } + *dest = pixmaps; + + return ret; +error: + list_free_items_and_destroy(pixmaps); + return ret; +} + +struct get_property_data { + struct swaybar_sni *sni; + const char *prop; + const char *type; + void *dest; +}; + +static int get_property_callback(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct get_property_data *d = data; + struct swaybar_sni *sni = d->sni; + const char *prop = d->prop; + const char *type = d->type; + void *dest = d->dest; + + int ret; + if (sd_bus_message_is_method_error(msg, NULL)) { + sd_bus_error err = *sd_bus_message_get_error(msg); + wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, err.message); + ret = -sd_bus_error_get_errno(&err); + goto cleanup; + } + + ret = sd_bus_message_enter_container(msg, 'v', type); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, strerror(-ret)); + goto cleanup; + } + + if (!type) { + ret = read_pixmap(msg, sni, prop, dest); + if (ret < 0) { + goto cleanup; + } + } else { + ret = sd_bus_message_read(msg, type, dest); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read property %s: %s", prop, + strerror(-ret)); + goto cleanup; + } else if (*type == 's' || *type == 'o') { + char **str = dest; + *str = strdup(*str); + } + } + + if (strcmp(prop, "Status") == 0 || (sni->status && (sni->status[0] == 'N' ? + prop[0] == 'A' : strncmp(prop, "Icon", 4) == 0))) { + set_sni_dirty(sni); + } +cleanup: + free(data); + return ret; +} + +static void sni_get_property_async(struct swaybar_sni *sni, const char *prop, + const char *type, void *dest) { + struct get_property_data *data = malloc(sizeof(struct get_property_data)); + data->sni = sni; + data->prop = prop; + data->type = type; + data->dest = dest; + int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, + sni->path, "org.freedesktop.DBus.Properties", "Get", + get_property_callback, data, "ss", sni->interface, prop); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to get property %s: %s", prop, strerror(-ret)); + } +} + +static int handle_new_icon(sd_bus_message *msg, void *data, sd_bus_error *error) { + struct swaybar_sni *sni = data; + wlr_log(WLR_DEBUG, "%s has new IconName", sni->watcher_id); + + free(sni->icon_name); + sni->icon_name = NULL; + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + + list_free_items_and_destroy(sni->icon_pixmap); + sni->icon_pixmap = NULL; + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + + return 0; +} + +static int handle_new_attention_icon(sd_bus_message *msg, void *data, + sd_bus_error *error) { + struct swaybar_sni *sni = data; + wlr_log(WLR_DEBUG, "%s has new AttentionIconName", sni->watcher_id); + + free(sni->attention_icon_name); + sni->attention_icon_name = NULL; + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + + list_free_items_and_destroy(sni->attention_icon_pixmap); + sni->attention_icon_pixmap = NULL; + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + + return 0; +} + +static int handle_new_status(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *status; + int ret = sd_bus_message_read(msg, "s", &status); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to read new status message: %s", strerror(-ret)); + } else { + struct swaybar_sni *sni = data; + free(sni->status); + sni->status = strdup(status); + wlr_log(WLR_DEBUG, "%s has new Status '%s'", sni->watcher_id, status); + set_sni_dirty(sni); + } + return ret; +} + +static void sni_match_signal(struct swaybar_sni *sni, char *signal, + sd_bus_message_handler_t callback) { + int ret = sd_bus_match_signal(sni->tray->bus, NULL, sni->service, sni->path, + sni->interface, signal, callback, sni); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to subscribe to signal %s: %s", signal, + strerror(-ret)); + } +} + +struct swaybar_sni *create_sni(char *id, struct swaybar_tray *tray) { + struct swaybar_sni *sni = calloc(1, sizeof(struct swaybar_sni)); + if (!sni) { + return NULL; + } + sni->tray = tray; + sni->watcher_id = strdup(id); + char *path_ptr = strchr(id, '/'); + if (!path_ptr) { + sni->service = strdup(id); + sni->path = strdup("/StatusNotifierItem"); + sni->interface = "org.freedesktop.StatusNotifierItem"; + } else { + sni->service = strndup(id, path_ptr - id); + sni->path = strdup(path_ptr); + sni->interface = "org.kde.StatusNotifierItem"; + sni_get_property_async(sni, "IconThemePath", "s", &sni->icon_theme_path); + } + + // Ignored: Category, Id, Title, WindowId, OverlayIconName, + // OverlayIconPixmap, AttentionMovieName, ToolTip + sni_get_property_async(sni, "Status", "s", &sni->status); + sni_get_property_async(sni, "IconName", "s", &sni->icon_name); + sni_get_property_async(sni, "IconPixmap", NULL, &sni->icon_pixmap); + sni_get_property_async(sni, "AttentionIconName", "s", &sni->attention_icon_name); + sni_get_property_async(sni, "AttentionIconPixmap", NULL, &sni->attention_icon_pixmap); + sni_get_property_async(sni, "ItemIsMenu", "b", &sni->item_is_menu); + sni_get_property_async(sni, "Menu", "o", &sni->menu); + + sni_match_signal(sni, "NewIcon", handle_new_icon); + sni_match_signal(sni, "NewAttentionIcon", handle_new_attention_icon); + sni_match_signal(sni, "NewStatus", handle_new_status); + + return sni; +} + +void destroy_sni(struct swaybar_sni *sni) { + if (!sni) { + return; + } + + free(sni->watcher_id); + free(sni->service); + free(sni->path); + free(sni->status); + free(sni->icon_name); + free(sni->icon_pixmap); + free(sni->attention_icon_name); + free(sni->menu); + free(sni); +} + +static void handle_click(struct swaybar_sni *sni, int x, int y, + enum x11_button button, int delta) { + const char *method = sni->tray->bar->config->tray_bindings[button]; + if (!method) { + static const char *default_bindings[10] = { + "nop", + "Activate", + "SecondaryActivate", + "ContextMenu", + "ScrollUp", + "ScrollDown", + "ScrollLeft", + "ScrollRight", + "nop", + "nop" + }; + method = default_bindings[button]; + } + if (strcmp(method, "nop") == 0) { + return; + } + if (sni->item_is_menu && strcmp(method, "Activate") == 0) { + method = "ContextMenu"; + } + + if (strncmp(method, "Scroll", strlen("Scroll")) == 0) { + char dir = method[strlen("Scroll")]; + char *orientation = (dir = 'U' || dir == 'D') ? "vertical" : "horizontal"; + int sign = (dir == 'U' || dir == 'L') ? -1 : 1; + + int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, + sni->path, sni->interface, "Scroll", NULL, NULL, "is", + delta*sign, orientation); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to scroll on SNI: %s", strerror(-ret)); + } + } else { + int ret = sd_bus_call_method_async(sni->tray->bus, NULL, sni->service, + sni->path, sni->interface, method, NULL, NULL, "ii", x, y); + if (ret < 0) { + wlr_log(WLR_DEBUG, "Failed to click on SNI: %s", strerror(-ret)); + } + } +} + +static int cmp_sni_id(const void *item, const void *cmp_to) { + const struct swaybar_sni *sni = item; + return strcmp(sni->watcher_id, cmp_to); +} + +static enum hotspot_event_handling icon_hotspot_callback( + struct swaybar_output *output, struct swaybar_hotspot *hotspot, + int x, int y, enum x11_button button, void *data) { + wlr_log(WLR_DEBUG, "Clicked on Status Notifier Item '%s'", (char *)data); + + struct swaybar_tray *tray = output->bar->tray; + int idx = list_seq_find(tray->items, cmp_sni_id, data); + + if (idx != -1) { + struct swaybar_sni *sni = tray->items->items[idx]; + // guess global position since wayland doesn't expose it + struct swaybar_config *config = tray->bar->config; + int global_x = output->output_x + config->gaps.left + x; + bool top_bar = config->position & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + int global_y = output->output_y + (top_bar ? config->gaps.top + y: + (int) output->output_height - config->gaps.bottom - y); + + wlr_log(WLR_DEBUG, "Guessing click at (%d, %d)", global_x, global_y); + handle_click(sni, global_x, global_y, button, 1); // TODO get delta from event + return HOTSPOT_IGNORE; + } else { + wlr_log(WLR_DEBUG, "but it doesn't exist"); + } + + return HOTSPOT_PROCESS; +} + +uint32_t render_sni(cairo_t *cairo, struct swaybar_output *output, double *x, + struct swaybar_sni *sni) { + uint32_t height = output->height * output->scale; + int padding = output->bar->config->tray_padding; + int ideal_size = height - 2*padding; + if ((ideal_size < sni->min_size || ideal_size > sni->max_size) && sni_ready(sni)) { + bool icon_found = false; + char *icon_name = sni->status[0] == 'N' ? + sni->attention_icon_name : sni->icon_name; + if (icon_name) { + char *icon_path = find_icon(sni->tray->themes, sni->tray->basedirs, + icon_name, ideal_size, output->bar->config->icon_theme, + &sni->min_size, &sni->max_size); + if (!icon_path && sni->icon_theme_path) { + icon_path = find_icon_in_dir(icon_name, sni->icon_theme_path, + &sni->min_size, &sni->max_size); + } + if (icon_path) { + cairo_surface_destroy(sni->icon); + sni->icon = load_background_image(icon_path); + free(icon_path); + icon_found = true; + } + } + if (!icon_found) { + list_t *pixmaps = sni->status[0] == 'N' ? + sni->attention_icon_pixmap : sni->icon_pixmap; + if (pixmaps) { + int idx = -1; + unsigned smallest_error = -1; // UINT_MAX + for (int i = 0; i < pixmaps->length; ++i) { + struct swaybar_pixmap *pixmap = pixmaps->items[i]; + unsigned error = (ideal_size - pixmap->size) * + (ideal_size < pixmap->size ? -1 : 1); + if (error < smallest_error) { + smallest_error = error; + idx = i; + } + } + struct swaybar_pixmap *pixmap = pixmaps->items[idx]; + cairo_surface_destroy(sni->icon); + sni->icon = cairo_image_surface_create_for_data(pixmap->pixels, + CAIRO_FORMAT_ARGB32, pixmap->size, pixmap->size, + cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, pixmap->size)); + } + } + } + + int icon_size; + cairo_surface_t *icon; + if (sni->icon) { + int actual_size = cairo_image_surface_get_height(sni->icon); + icon_size = actual_size < ideal_size ? + actual_size*(ideal_size/actual_size) : ideal_size; + icon = cairo_image_surface_scale(sni->icon, icon_size, icon_size); + } else { // draw a sad face + icon_size = ideal_size*0.8; + icon = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, icon_size, icon_size); + cairo_t *cairo_icon = cairo_create(icon); + cairo_set_source_u32(cairo_icon, 0xFF0000FF); + cairo_translate(cairo_icon, icon_size/2, icon_size/2); + cairo_scale(cairo_icon, icon_size/2, icon_size/2); + cairo_arc(cairo_icon, 0, 0, 1, 0, 7); + cairo_fill(cairo_icon); + cairo_set_operator(cairo_icon, CAIRO_OPERATOR_CLEAR); + cairo_arc(cairo_icon, 0.35, -0.3, 0.1, 0, 7); + cairo_fill(cairo_icon); + cairo_arc(cairo_icon, -0.35, -0.3, 0.1, 0, 7); + cairo_fill(cairo_icon); + cairo_arc(cairo_icon, 0, 0.75, 0.5, 3.71238898038469, 5.71238898038469); + cairo_set_line_width(cairo_icon, 0.1); + cairo_stroke(cairo_icon); + cairo_destroy(cairo_icon); + } + + int padded_size = icon_size + 2*padding; + *x -= padded_size; + int y = floor((height - padded_size) / 2.0); + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, icon, *x + padding, y + padding); + cairo_rectangle(cairo, *x, y, padded_size, padded_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + cairo_surface_destroy(icon); + + struct swaybar_hotspot *hotspot = calloc(1, sizeof(struct swaybar_hotspot)); + hotspot->x = *x; + hotspot->y = 0; + hotspot->width = height; + hotspot->height = height; + hotspot->callback = icon_hotspot_callback; + hotspot->destroy = free; + hotspot->data = strdup(sni->watcher_id); + wl_list_insert(&output->hotspots, &hotspot->link); + + return output->height; +} 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 @@ +#include <cairo.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "swaybar/config.h" +#include "swaybar/bar.h" +#include "swaybar/tray/icon.h" +#include "swaybar/tray/host.h" +#include "swaybar/tray/item.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/watcher.h" +#include "list.h" +#include "log.h" + +static int handle_lost_watcher(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*new_owner) { + struct swaybar_tray *tray = data; + if (strcmp(service, "org.freedesktop.StatusNotifierWatcher") == 0) { + tray->watcher_xdg = create_watcher("freedesktop", tray->bus); + } else if (strcmp(service, "org.kde.StatusNotifierWatcher") == 0) { + tray->watcher_kde = create_watcher("kde", tray->bus); + } + } + + return 0; +} + +struct swaybar_tray *create_tray(struct swaybar *bar) { + wlr_log(WLR_DEBUG, "Initializing tray"); + + sd_bus *bus; + int ret = sd_bus_open_user(&bus); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to connect to user bus: %s", strerror(-ret)); + return NULL; + } + + struct swaybar_tray *tray = calloc(1, sizeof(struct swaybar_tray)); + if (!tray) { + return NULL; + } + tray->bar = bar; + tray->bus = bus; + tray->fd = sd_bus_get_fd(tray->bus); + + tray->watcher_xdg = create_watcher("freedesktop", tray->bus); + tray->watcher_kde = create_watcher("kde", tray->bus); + + ret = sd_bus_match_signal(bus, NULL, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_lost_watcher, tray); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + } + + tray->items = create_list(); + + init_host(&tray->host_xdg, "freedesktop", tray); + init_host(&tray->host_kde, "kde", tray); + + init_themes(&tray->themes, &tray->basedirs); + + return tray; +} + +void destroy_tray(struct swaybar_tray *tray) { + if (!tray) { + return; + } + finish_host(&tray->host_xdg); + finish_host(&tray->host_kde); + for (int i = 0; i < tray->items->length; ++i) { + destroy_sni(tray->items->items[0]); + } + list_free(tray->items); + destroy_watcher(tray->watcher_xdg); + destroy_watcher(tray->watcher_kde); + sd_bus_flush_close_unref(tray->bus); + finish_themes(tray->themes, tray->basedirs); + free(tray); +} + +void tray_in(int fd, short mask, void *data) { + sd_bus *bus = data; + int ret; + while ((ret = sd_bus_process(bus, NULL)) > 0) { + // This space intentionally left blank + } + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to process bus: %s", strerror(-ret)); + } +} + +static int cmp_output(const void *item, const void *cmp_to) { + return strcmp(item, cmp_to); +} + +uint32_t render_tray(cairo_t *cairo, struct swaybar_output *output, double *x) { + struct swaybar_config *config = output->bar->config; + if (config->tray_outputs) { + if (list_seq_find(config->tray_outputs, cmp_output, output->name) == -1) { + return 0; + } + } // else display on all + + if ((int) output->height*output->scale <= 2*config->tray_padding) { + return 2*config->tray_padding + 1; + } + + uint32_t max_height = 0; + struct swaybar_tray *tray = output->bar->tray; + for (int i = 0; i < tray->items->length; ++i) { + uint32_t h = render_sni(cairo, output, x, tray->items->items[i]); + max_height = h > max_height ? h : max_height; + } + + return max_height; +} 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 @@ +#define _POSIX_C_SOURCE 200809L +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "list.h" +#include "log.h" +#include "swaybar/tray/watcher.h" + +static const char *obj_path = "/StatusNotifierWatcher"; + +static bool using_standard_protocol(struct swaybar_watcher *watcher) { + return watcher->interface[strlen("org.")] == 'f'; // freedesktop +} + +static int cmp_id(const void *item, const void *cmp_to) { + return strcmp(item, cmp_to); +} + +static int cmp_service(const void *item, const void *cmp_to) { + return strncmp(item, cmp_to, strlen(cmp_to)); +} + +static int handle_lost_service(sd_bus_message *msg, + void *data, sd_bus_error *error) { + char *service, *old_owner, *new_owner; + int ret = sd_bus_message_read(msg, "sss", &service, &old_owner, &new_owner); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse owner change message: %s", strerror(-ret)); + return ret; + } + + if (!*new_owner) { + struct swaybar_watcher *watcher = data; + int idx = list_seq_find(watcher->items, + using_standard_protocol(watcher) ? cmp_id : cmp_service, service); + if (idx != -1) { + char *id = watcher->items->items[idx]; + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Item '%s'", id); + list_del(watcher->items, idx); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierItemUnregistered", "s", id); + free(id); + } + + idx = list_seq_find(watcher->hosts, cmp_id, service); + if (idx != -1) { + wlr_log(WLR_DEBUG, "Unregistering Status Notifier Host '%s'", service); + free(watcher->hosts->items[idx]); + list_del(watcher->hosts, idx); + } + } + + return 0; +} + +static int register_sni(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *service_or_path, *id; + int ret = sd_bus_message_read(msg, "s", &service_or_path); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register SNI message: %s", strerror(-ret)); + return ret; + } + + struct swaybar_watcher *watcher = data; + if (using_standard_protocol(watcher)) { + id = strdup(service_or_path); + } else { + const char *service, *path; + if (service_or_path[0] == '/') { + service = sd_bus_message_get_sender(msg); + path = service_or_path; + } else { + service = service_or_path; + path = "/StatusNotifierItem"; + } + size_t id_len = snprintf(NULL, 0, "%s%s", service, path) + 1; + id = malloc(id_len); + snprintf(id, id_len, "%s%s", service, path); + } + + if (list_seq_find(watcher->items, cmp_id, id) == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Item '%s'", id); + list_add(watcher->items, id); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierItemRegistered", "s", id); + } else { + wlr_log(WLR_DEBUG, "Status Notifier Item '%s' already registered", id); + free(id); + } + + return sd_bus_reply_method_return(msg, ""); +} + +static int register_host(sd_bus_message *msg, void *data, sd_bus_error *error) { + char *service; + int ret = sd_bus_message_read(msg, "s", &service); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to parse register host message: %s", strerror(-ret)); + return ret; + } + + struct swaybar_watcher *watcher = data; + if (list_seq_find(watcher->hosts, cmp_id, service) == -1) { + wlr_log(WLR_DEBUG, "Registering Status Notifier Host '%s'", service); + list_add(watcher->hosts, strdup(service)); + sd_bus_emit_signal(watcher->bus, obj_path, watcher->interface, + "StatusNotifierHostRegistered", "s", service); + } else { + wlr_log(WLR_DEBUG, "Status Notifier Host '%s' already registered", service); + } + + return sd_bus_reply_method_return(msg, ""); +} + +static int get_registered_snis(sd_bus *bus, const char *obj_path, + const char *interface, const char *property, sd_bus_message *reply, + void *data, sd_bus_error *error) { + struct swaybar_watcher *watcher = data; + list_add(watcher->items, NULL); // strv expects NULL-terminated string array + int ret = sd_bus_message_append_strv(reply, (char **)watcher->items->items); + list_del(watcher->items, watcher->items->length - 1); + return ret; +} + +static int is_host_registered(sd_bus *bus, const char *obj_path, + const char *interface, const char *property, sd_bus_message *reply, + void *data, sd_bus_error *error) { + struct swaybar_watcher *watcher = data; + int val = watcher->hosts->length > 0; // dbus expects int rather than bool + return sd_bus_message_append_basic(reply, 'b', &val); +} + +static const sd_bus_vtable watcher_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("RegisterStatusNotifierItem", "s", "", register_sni, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RegisterStatusNotifierHost", "s", "", register_host, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("RegisteredStatusNotifierItems", "as", get_registered_snis, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("IsStatusNotifierHostRegistered", "b", is_host_registered, + 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ProtocolVersion", "i", NULL, + offsetof(struct swaybar_watcher, version), + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_SIGNAL("StatusNotifierItemRegistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierItemUnregistered", "s", 0), + SD_BUS_SIGNAL("StatusNotifierHostRegistered", NULL, 0), + SD_BUS_VTABLE_END +}; + +struct swaybar_watcher *create_watcher(char *protocol, sd_bus *bus) { + struct swaybar_watcher *watcher = + calloc(1, sizeof(struct swaybar_watcher)); + if (!watcher) { + return NULL; + } + + size_t len = snprintf(NULL, 0, "org.%s.StatusNotifierWatcher", protocol) + 1; + watcher->interface = malloc(len); + snprintf(watcher->interface, len, "org.%s.StatusNotifierWatcher", protocol); + + sd_bus_slot *signal_slot = NULL, *vtable_slot = NULL; + int ret = sd_bus_add_object_vtable(bus, &vtable_slot, obj_path, + watcher->interface, watcher_vtable, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to add object vtable: %s", strerror(-ret)); + goto error; + } + + ret = sd_bus_match_signal(bus, &signal_slot, "org.freedesktop.DBus", + "/org/freedesktop/DBus", "org.freedesktop.DBus", + "NameOwnerChanged", handle_lost_service, watcher); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to subscribe to unregistering events: %s", + strerror(-ret)); + goto error; + } + + ret = sd_bus_request_name(bus, watcher->interface, 0); + if (ret < 0) { + wlr_log(WLR_ERROR, "Failed to acquire service name: %s", strerror(-ret)); + goto error; + } + + sd_bus_slot_set_floating(signal_slot, 0); + sd_bus_slot_set_floating(vtable_slot, 0); + + watcher->bus = bus; + watcher->hosts = create_list(); + watcher->items = create_list(); + watcher->version = 0; + wlr_log(WLR_DEBUG, "Registered %s", watcher->interface); + return watcher; +error: + sd_bus_slot_unref(signal_slot); + sd_bus_slot_unref(vtable_slot); + destroy_watcher(watcher); + return NULL; +} + +void destroy_watcher(struct swaybar_watcher *watcher) { + if (!watcher) { + return; + } + list_free_items_and_destroy(watcher->hosts); + list_free_items_and_destroy(watcher->items); + free(watcher->interface); + free(watcher); +} 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 @@ #define _POSIX_C_SOURCE 200809L #include <assert.h> #include <errno.h> +#include <fcntl.h> #include <getopt.h> #include <pthread.h> #include <signal.h> @@ -104,9 +105,21 @@ static void acquire_sleep_lock(void) { if (ret < 0) { wlr_log(WLR_ERROR, "Failed to parse D-Bus response for Inhibit: %s", strerror(-ret)); + sd_bus_error_free(&error); + sd_bus_message_unref(msg); + return; } else { wlr_log(WLR_INFO, "Got sleep lock: %d", lock_fd); } + + // sd_bus_message_unref closes the file descriptor so we need + // to copy it beforehand + lock_fd = fcntl(lock_fd, F_DUPFD_CLOEXEC, 3); + if (lock_fd < 0) { + wlr_log(WLR_ERROR, "Failed to copy sleep lock fd: %s", + strerror(errno)); + } + sd_bus_error_free(&error); sd_bus_message_unref(msg); } 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, schedule_indicator_clear(state); break; case XKB_KEY_Caps_Lock: - /* The state is getting active after this - * so we need to manually toggle it */ - state->xkb.caps_lock = !state->xkb.caps_lock; - state->auth_state = AUTH_STATE_INPUT_NOP; - damage_state(state); - schedule_indicator_clear(state); - schedule_password_clear(state); - break; case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: 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, struct swaylock_state *state = data; xkb_state_update_mask(state->xkb.state, mods_depressed, mods_latched, mods_locked, 0, 0, group); - state->xkb.caps_lock = xkb_state_mod_name_is_active(state->xkb.state, + int caps_lock = xkb_state_mod_name_is_active(state->xkb.state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED); + if (caps_lock != state->xkb.caps_lock) { + state->xkb.caps_lock = caps_lock; + damage_state(state); + } state->xkb.control = xkb_state_mod_name_is_active(state->xkb.state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED | XKB_STATE_MODS_LATCHED); |